Development Tip

C #에서 한 번만 속성을 설정하는 방법이 있습니까?

yourdevel 2020. 11. 24. 20:01
반응형

C #에서 한 번만 속성을 설정하는 방법이 있습니까?


C # 개체의 속성을 한 번만 설정할 수있는 방법을 찾고 있습니다. 이 작업을 수행하는 코드를 작성하는 것은 쉽지만 표준 메커니즘이있는 경우 차라리 사용합니다.

public OneShot <int> SetOnceProperty {get; 세트; }

내가 원하는 것은 속성이 아직 설정되지 않은 경우 설정할 수 있지만 이전에 설정된 경우 예외를 throw하는 것입니다. 설정되었는지 여부를 확인할 수있는 Nullable 값처럼 작동해야합니다.


.NET 4.0의 TPL에서 이에 대한 직접적인 지원이 있습니다.

(편집 : 위의 문장은 System.Threading.WriteOnce<T>당시 사용 가능한 "미리보기"비트에 존재할 것으로 예상하여 작성 되었지만 TPL이 RTM / GA에 도달하기 전에 사라진 것으로 보입니다)

그때까지는 직접 확인하세요 ... 제가 기억하는 바에 따르면 줄이 많지 않습니다 ...

같은 것 :

public sealed class WriteOnce<T>
{
    private T value;
    private bool hasValue;
    public override string ToString()
    {
        return hasValue ? Convert.ToString(value) : "";
    }
    public T Value
    {
        get
        {
            if (!hasValue) throw new InvalidOperationException("Value not set");
            return value;
        }
        set
        {
            if (hasValue) throw new InvalidOperationException("Value already set");
            this.value = value;
            this.hasValue = true;
        }
    }
    public T ValueOrDefault { get { return value; } }

    public static implicit operator T(WriteOnce<T> value) { return value.Value; }
}

그런 다음 예를 들어 다음을 사용하십시오.

readonly WriteOnce<string> name = new WriteOnce<string>();
public WriteOnce<string> Name { get { return name; } }

직접 롤링 할 수 있습니다 (스레드 안전하고 기본값을 지원하는보다 강력한 구현은 답변의 끝 부분 참조).

public class SetOnce<T>
{
    private bool set;
    private T value;

    public T Value
    {
        get { return value; }
        set
        {
            if (set) throw new AlreadySetException(value);
            set = true;
            this.value = value;
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}

다음과 같이 사용할 수 있습니다.

public class Foo
{
    private readonly SetOnce<int> toBeSetOnce = new SetOnce<int>();

    public int ToBeSetOnce
    {
        get { return toBeSetOnce; }
        set { toBeSetOnce.Value = value; }
    }
}

아래에서보다 강력한 구현

public class SetOnce<T>
{
    private readonly object syncLock = new object();
    private readonly bool throwIfNotSet;
    private readonly string valueName;
    private bool set;
    private T value;

    public SetOnce(string valueName)
    {
        this.valueName = valueName;
        throwIfGet = true;
    }

    public SetOnce(string valueName, T defaultValue)
    {
        this.valueName = valueName;
        value = defaultValue;
    }

    public T Value
    {
        get
        {
            lock (syncLock)
            {
                if (!set && throwIfNotSet) throw new ValueNotSetException(valueName);
                return value;
            }
        }
        set
        {
            lock (syncLock)
            {
                if (set) throw new AlreadySetException(valueName, value);
                set = true;
                this.value = value;
            }
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}


public class NamedValueException : InvalidOperationException
{
    private readonly string valueName;

    public NamedValueException(string valueName, string messageFormat)
        : base(string.Format(messageFormat, valueName))
    {
        this.valueName = valueName;
    }

    public string ValueName
    {
        get { return valueName; }
    }
}

public class AlreadySetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has already been set.";

    public AlreadySetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}

public class ValueNotSetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has not yet been set.";

    public ValueNotSetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}

이것은 플래그를 조작하여 수행 할 수 있습니다.

private OneShot<int> setOnce;
private bool setOnceSet;

public OneShot<int> SetOnce
{
    get { return setOnce; }
    set
    {
        if(setOnceSet)
            throw new InvalidOperationException();

        setOnce = value;
        setOnceSet = true;
    }
}

잠재적으로 런타임 오류를 수신 할 수 있기 때문에 좋지 않습니다. 컴파일 타임에이 동작을 적용하는 것이 훨씬 좋습니다.

public class Foo
{
    private readonly OneShot<int> setOnce;        

    public OneShot<int> SetOnce
    {
        get { return setOnce; }
    }

    public Foo() :
        this(null)
    {
    }

    public Foo(OneShot<int> setOnce)
    {
        this.setOnce = setOnce;
    }
}

그런 다음 생성자를 사용하십시오.


C #에는 이러한 기능이 없습니다 (3.5 기준). 직접 코딩해야합니다.


Marc가 말했듯이 .Net에서는 기본적으로이를 수행 할 수있는 방법이 없지만 직접 추가하는 것은 그리 어렵지 않습니다.

public class SetOnceValue<T> { 
  private T m_value;
  private bool m_isSet;
  public bool IsSet { get { return m_isSet; }}
  public T Value { get {
    if ( !IsSet ) {
       throw new InvalidOperationException("Value not set");
    }
    return m_value;
  }
  public T ValueOrDefault { get { return m_isSet ? m_value : default(T); }}
  public SetOnceValue() { }
  public void SetValue(T value) {
    if ( IsSet ) {
      throw new InvalidOperationException("Already set");
    }
    m_value = value;
    m_isSet = true;
  }
}

그런 다음이를 특정 자산의 뒷받침으로 사용할 수 있습니다.


이것에 대한 나의 견해는 다음과 같습니다.

public class ReadOnly<T> // or WriteOnce<T> or whatever name floats your boat
{
    private readonly TaskCompletionSource<T> _tcs = new TaskCompletionSource<T>();

    public Task<T> ValueAsync => _tcs.Task;
    public T Value => _tcs.Task.Result;

    public bool TrySetInitialValue(T value)
    {
        try
        {
            _tcs.SetResult(value);
            return true;
        }
        catch (InvalidOperationException)
        {
            return false;
        }
    }

    public void SetInitialValue(T value)
    {
        if (!TrySetInitialValue(value))
            throw new InvalidOperationException("The value has already been set.");
    }

    public static implicit operator T(ReadOnly<T> readOnly) => readOnly.Value;
    public static implicit operator Task<T>(ReadOnly<T> readOnly) => readOnly.ValueAsync;
}

Marc의 대답 은 TPL이이 기능을 제공 하고 그가 의미 한 바라고 생각 TaskCompletionSource<T> 하지만 확신 할 수 없습니다.

내 솔루션의 몇 가지 좋은 속성 :

  • TaskCompletionSource<T> 구현을 단순화하는 공식적으로 지원되는 MS 클래스입니다.
  • 값을 동 기적으로 또는 비동기 적으로 가져 오도록 선택할 수 있습니다.
  • 이 클래스의 인스턴스는 저장하는 값의 유형으로 암시 적으로 변환됩니다. 값을 전달해야 할 때 코드를 약간 정리할 수 있습니다.

읽기 전용을 고려 했습니까? http://en.csharp-online.net/const,_static_and_readonly

초기화 중에 만 설정할 수 있지만 찾고있는 것일 수 있습니다.


/// <summary>
/// Wrapper for once inizialization
/// </summary>
public class WriteOnce<T>
{
    private T _value;
    private Int32 _hasValue;

    public T Value
    {
        get { return _value; }
        set
        {
            if (Interlocked.CompareExchange(ref _hasValue, 1, 0) == 0)
                _value = value;
            else
                throw new Exception(String.Format("You can't inizialize class instance {0} twice", typeof(WriteOnce<T>)));
        }
    }

    public WriteOnce(T defaultValue)
    {
        _value = defaultValue;
    }

    public static implicit operator T(WriteOnce<T> value)
    {
        return value.Value;
    }
}

interface IFoo {

    int Bar { get; }
}

class Foo : IFoo {

    public int Bar { get; set; }
}

class Program {

    public static void Main() {

        IFoo myFoo = new Foo() {
            Bar = 5 // valid
        };

        int five = myFoo.Bar; // valid

        myFoo.Bar = 6; // compilation error
    }
}

myFoo는 IFoo로 선언되지만 Foo로 인스턴스화됩니다.

This means that Bar can be set within the initializer block, but not through a later reference to myFoo.


The answers assume that objects that receive a reference to an object in the future will not try to change it. If you want to protect against this, you need to make your write-once code only work for types that implement ICloneable or are primitives. the String type implements ICloneable for example. then you would return a clone of the data or new instance of the primitive instead of the actual data.

Generics for primitives only: T GetObject where T: struct;

This is not needed if you know that objects that get a reference to the data will never overwrite it.

Also, consider if the ReadOnlyCollection will work for your application. an exception is thrown whenever a change is attempted on the data.


While the accepted and top-rated answers most directly answer this (older) question, another strategy would be to build a class hierarchy such that you can construct children via parents, plus the new properties:

public class CreatedAtPointA 
{
    public int ExamplePropOne { get; }
    public bool ExamplePropTwo { get; }

    public CreatedAtPointA(int examplePropOne, bool examplePropTwo)
    {
        ExamplePropOne = examplePropOne;
        ExamplePropTwo = examplePropTwo;
    }
}

public class CreatedAtPointB : CreatedAtPointA
{
    public string ExamplePropThree { get; }

    public CreatedAtPointB(CreatedAtPointA dataFromPointA, string examplePropThree) 
        : base(dataFromPointA.ExamplePropOne, dataFromPointA.ExamplePropTwo)
    {
        ExamplePropThree = examplePropThree;
    }
}

By relying on constructors, you can spray some Febreeze on the code smell, though it's still tedious and a potentially expensive strategy.


I created a type which allows a value to be set on construction, then after that the value can only be set/overridden once, otherwise an exception is thrown.

public class SetOnce<T>
{
    bool set;
    T value;

    public SetOnce(T init) =>
        this.value = init;

    public T Value
    {
        get => this.value;
        set
        {
            if (this.set) throw new AlreadySetException($"Not permitted to override {this.Value}.");
            this.set = true;
            this.value = value;
        }
    }

    public static implicit operator T(SetOnce<T> setOnce) =>
        setOnce.value;

    class AlreadySetException : Exception
    {
        public AlreadySetException(string message) : base(message){}
    }
}

You can do this but is not a clear solution and code readability is not the best. If you are doing code design you can have a look at singleton realization in tandem with AOP to intercept setters. The realization is just 123 :)

참고URL : https://stackoverflow.com/questions/839788/is-there-a-way-of-setting-a-property-once-only-in-c-sharp

반응형