【问题标题】:Readonly field that could be assigned a value outside constructor可以在构造函数外部赋值的只读字段
【发布时间】:2010-11-25 16:50:00
【问题描述】:

有没有办法让类中的私有只读字段可以在类中的任何位置赋值,但只能赋值一次??

也就是说,我正在寻找一种私有只读类型的字段,它只能被赋值一次,但不一定在构造函数中。因此,如果一个值被重新分配给一个字段,那么它会显示编译时错误(我确信这要求太多了)。

如果有任何模式(不是语言功能)可以做同样的工作,我也很想知道。

感谢您的关注。

【问题讨论】:

    标签: c# readonly


    【解决方案1】:

    您不会收到编译时错误,但您可以使用属性完成您要查找的内容。在 setter 中,仅当内部 bool/flag 为 false 时才设置该值。首次设置后,将标志设置为 true。如果 setter 被另一段代码再次调用,你可以抛出一个 InvalidOperationException 让他们知道它已经被初始化了

    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;
       }
    }
    

    【讨论】:

      【解决方案2】:

      简短的回答是否定的。唯一可以进行编译器检查的一次性赋值的地方是在构造函数中。您可以创建一个系统,如果多次尝试分配但没有 C# 构造来执行此操作,则会出现运行时错误

      【讨论】:

        【解决方案3】:

        您可以创建一个通用结构来自动化该过程:

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

        编辑 我刚刚意识到上面甚至不会编译。编译器不会让 _value 的类型为 Nullable,因为 Nullable 中的 T 必须是不可为空的类型。

        这是一个效果更好的实现:

        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;
            }
        }
        

        请注意,我说它会更好地工作 - 并不是说​​它会很好地工作。使用它仍有一些问题:

        首先,如果编译器检测到您尝试使用它而没有先对其进行分配,它会报错。像这样初始化它会解决这个问题:

        WriteOnce<int> foo = default(WriteOnce<int>);
        

        其次,即使它在修改时抛出异常,C# 仍然会很乐意让你覆盖它。因此,虽然这确实封装了一些功能;如果它最终暴露在对象的接口中,您仍应将其包装在属性中以防止滥用。

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

        但是,这仍然没有真正解决我在原始 sn-p 中犯的最后一个严重错误,它创建了一个可变结构。如果您正在做很多这样的事情,可能为了不重复自己而违反该原则,但这绝对是一个潜在的陷阱,需要仔细评论并远离任何公共接口。

        但是,如果它只是一次性的,那么其他人所建议的关于直接实现属性的建议就不那么​​复杂和安全了。

        【讨论】:

          【解决方案4】:

          您可以使用私有属性来检查它是否已分配,如果尚未分配,则仅分配一个值。

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

          【讨论】:

            【解决方案5】:

            没有对此的语言支持,但您可以创建一个 setter 来实现所需的行为。显然,这不会给您带来编译时错误,但如果没有可能是您能做的最好的语言支持。

            【讨论】:

              【解决方案6】:

              这看起来很适合声明分配或单例。

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

              否则,就像其他人所说的那样,只使用一个 setter 来创建一个内部或 so 属性,然后为其分配一个值。

              public class SomeClass {
                  private Type t;
              
                  internal Type Type {
                      set {
                          if (t == null) t = value;
                      }
                  }
              }
              

              【讨论】:

                【解决方案7】:

                如果在构造函数中完成,我想我已经找到了一种方法。它也可以在变量声明行上。 如果在这些上下文之外完成,您将收到错误:无法分配只读字段(构造函数或变量初始化程序除外)

                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
                    }
                
                    //(...)
                }
                

                【讨论】:

                • 抱歉发了这个,我没注意标题的“外部构造函数”部分。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-02-25
                • 1970-01-01
                • 1970-01-01
                • 2017-11-11
                相关资源
                最近更新 更多