Readonly field that could be assigned a value outside constructor

后端 未结 7 1959
清酒与你
清酒与你 2021-01-22 16:19

Is there a way to have a private readonly field in a class that could be assigned a value anywhere in the class, but only once??

That is, I am looking for a private read

7条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2021-01-22 16:44

    You could create a generic struct to automate the process:

    public struct WriteOnce
    {
        private T? _value;
    
        public T Value
        {
            get { return _value; }
            set
            {
                if (_value.HasValue) throw new InvalidOperationException();
                _value = value;
            }
        }
    }
    

    EDIT I just realized the above won't even compile. The compiler doesn't let _value's type to be Nullable, because the T in Nullable must be a non-nullable type.

    Here's an implementation that will work a bit better:

    public struct WriteOnce
    {
        private T _value;
        private bool _hasValue;
    
        public bool HasValue { get { return _hasValue; } }
    
        public T Value
        {
            get { return _value; }
            set
            {
                if (HasValue) throw new InvalidOperationException();
                _value = value;
                _hasValue = true;
            }
        }
    
        public static implicit operator T(WriteOnce x)
        {
            return x.Value;
        }
    
        public WriteOnce(T val)
        {
            _value = val;
            _hasValue = true;
        }
    }
    

    Note I said it would work better - not that it would work well. There are still some gotchas on using it:

    First, the compiler will complain if it detects that you're trying to use it without assigning something to it first. Initializing it like so will take care of that:

    WriteOnce foo = default(WriteOnce);
    

    Second, even if it throws an exception on modification, C# will still happily let you overwrite it. So while this does encapsulate some of the functionality; you should still wrap it in a property to prevent misuse if it's going to end up exposed in an object's interface.

    private WriteOnce _someInt = default(WriteOnce);
    public int SomeInt
    { 
        get { return _someInt; }
        set { _someInt.Value = value; }
    }
    

    However, that still doesn't really get around the last egregious error I committed in the original snippet, which was creating a mutable struct. If you're doing a lot of something like this it might be worth violating that principle for the sake of not repeating yourself, but this is definitely a potential trap that would need to be commented carefully and kept out of any public interfaces.

    If it's just a one-off, though, what others have suggested about just directly implementing a property is less complicated and safer.

提交回复
热议问题