Readonly field that could be assigned a value outside constructor

生来就可爱ヽ(ⅴ<●) 提交于 2019-12-02 00:18:53

The short answer is no. The only place you can have a one-time assignment that is compiler checked is in the constructor. You could create a system where you'd get a run-time error if an assignment was attempted more than once but there is no C# construct to do this

You won't get compile time error, but you can accomplish what you're looking for with a property. In the setter, only set the value if an internal bool/flag is false. Once you set it the first time, set the flag to true. And if the setter is invoked again by another piece of code, you can throw an InvalidOperationException to let them know it has already been initialized

private bool fieldAlreadySet = false;
private string field;

public string Field
{
   get {return field;}
   set
   {
       if (fieldAlreadySet) throw new InvalidOperationException("field already set");
       this.field = value;
       fieldAlreadySet = true;
   }
}

You could create a generic struct to automate the process:

public struct WriteOnce<T>
{
    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<T>
{
    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<T> 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<int> foo = default(WriteOnce<int>);

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<int> _someInt = default(WriteOnce<int>);
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.

You can use a private property that checks to see if it was assigned to and only assign a value if it hasn't been.

int? _writeOnceField;

private int? WriteOnce
{
   set
   {
      if (!_writeOnceFiled.HasValue)
      {
        writeOnceFiled = value;
      }
      else
      {
        // Throw exception
      }
   }
}

There's no language support for that, but you could make a setter, which implemented the desired behavior. Obviously that would not give you compile time errors, but without the language support that is probably the best you can do.

This looks like a good candidate for declaration assignment or singleton.

public class SomeClass {
    private readonly Type t = typeof(SomeClass);
}

Or otherwise, like the others said, make an internal or so property with only a setter, then assign a value to it.

public class SomeClass {
    private Type t;

    internal Type Type {
        set {
            if (t == null) t = value;
        }
    }
}

I think I achieved a way to do it, if it is done in the constructor. It could also be on the variable declaration line. If done outside those contexts, you will receive an error: A readonly field cannot be assigned to (except in a constructor or a variable initializer)

public class BillLine
{
    public BillLine(Bill bill)
    {
        m_Bill = bill;
    }

    private readonly Bill m_Bill;

    public Bill Bill
    {
        get { return m_Bill; }

        //supposed to be set only in the constructor
    }

    //(...)
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!