【问题标题】:How do I set a readonly field in an initialize method that gets called from the constructor?如何在从构造函数调用的初始化方法中设置只读字段?
【发布时间】:2010-09-16 15:58:15
【问题描述】:

我确定我在某处看到我可以通过使用 Init() 方法上方的属性来执行以下操作,它告诉编译器只能从构造函数调用 Init() 方法,从而允许要设置的只读字段。我忘记了这个属性叫什么,我似乎在谷歌上找不到它。

public class Class
{
    private readonly int readonlyField;

    public Class()
    {
        Init();
    }

    // Attribute here that tells the compiler that this method must be called only from a constructor
    private void Init()
    {
        readonlyField = 1;
    }
}

【问题讨论】:

    标签: c# constructor readonly


    【解决方案1】:

    Rob's answer 在我的书中是这样做的。如果您需要初始化多个字段,您可以使用out 参数来完成:

    public class Class
    {
        private readonly int readonlyField1;
        private readonly int readonlyField2;
    
        public Class()
        {
            Init(out readonlyField1, out readonlyField2);
        }
    
        protected virtual void Init(out int field1, out int field2)
        {
            field1 = 1;
            field2 = 2;
        }
    }
    

    我个人认为这在某些情况下是有意义的,例如当您希望您的字段为 readonly 但您希望能够在派生类中以不同方式设置它们(无需通过一些protected 构造函数链接大量参数)。但也许这只是我。

    【讨论】:

    • @Chuck:嘿,我从没说过它漂亮。
    • @Derar:该死的,伙计...我认为反射绝对不是解决这个问题的方法。当所有代码都在你面前时,为什么要在你自己的类上使用反射?
    • 我错误地否决了您的解决方案。我只是觉得每个人都说这是不可行的。这就是我添加基于反射的解决方案的原因。很好的解决方案老兄!
    • 这打破了非常基本的规则“不要在构造函数中调用虚方法”:)
    【解决方案2】:

    与其使用 Initialize 方法,不如通过所有其他构造函数继承一个基本构造函数。 即

    public class MyClass
    {
        readonly int field1;
        readonly double field2;
        public MyClass(int field1, double field2)
        {
            //put whatever initialization logic you need here...
            field1 = 10;
            field2 = 30.2;
        }
        public MyClass(int field1, double field2p1, double field2p2)
            : this(field1, (field2p1 + field2p2))
        {
            //put anything extra in here
        }
    }
    

    这可能有点晚才能到达最初需要的人,但似乎这会彻底解决问题......无需使用任何令人讨厌的反射或输出参数。

    【讨论】:

    • 这绝对是构造函数之间共享代码的更聪明的方式。
    【解决方案3】:

    我能想到的唯一解决方案是从Init()方法中返回需要分配readonly字段的值:

    public class Class
    {
        private readonly int readonlyField;
    
        public Class()
        {
            readonlyField = Init();
        }
    
        private int Init()
        {
            return 1;
        }
    }
    

    【讨论】:

    • 我确信我看到了这一切。我的大脑一定在欺骗我。谢谢大家
    • @BenAnderson,我知道你的意思 - 几天前我花了几个小时在网上搜索,因为我确信我在某处读到 SMB2 允许从一台远程服务器复制文件到另一个不通过发出请求的客户端计算机进行路由。浪费了几个小时,我找不到任何证据,尽管我坚定我读到这是可能的! :S
    【解决方案4】:

    贾里德是对的;这是不可能的。我能想到的解决方法是:

    1. 初始化声明中的字段。
    2. 在构造函数中初始化字段(手动内联Init 方法)。
    3. 将字段分配给方法返回的值,例如:_myField = GetInitialMyFieldValue();
    4. 使用out 修饰符将字段传递给Init 方法。如果您有许多依赖于构造函数参数的字段要初始化,这可能很有用。例如

     private readonly int _x;
     private readonly string _y;
    
     private void Init(int someConstructorParam, out int x, out string y){ .. }
    
     public Class(int someConstructorParam)
     {
         Init(someConstructorParam, out _x, out _y);
     } 
    

    【讨论】:

      【解决方案5】:

      我最终在当前技术(C# 7.x)中做的是使用值元组系统:

      public class MyClass
      {
          private readonly int x;
          private readonly int y;
          private readonly int z;
      
          public MyClass(int x)
          {
              this.x = x;
              (y, z) = InitYandZ();
          }
      
          private (int, int) InitYandZ()
          {
              return (5, 10);
          }
      }
      

      也不是最干净的,但对我来说似乎更干净。

      【讨论】:

        【解决方案6】:

        这是无法做到的。带有readonly标签的字段只能在构造函数中设置

        【讨论】:

        • 并不是完全错误,但与这里的其他答案相比,这个答案确实显得苍白无力。一个简单的“无法完成”一句话的答案还有很多不足之处。也许这是第一个答案之一?
        【解决方案7】:

        我认为如果使用反射,它会起作用。实际上这对我有用:

            public class Class
                {
                    private readonly int readonlyField;
                public int MyField()
                {
                     return readonlyField;
                }
                public Class()
                {
                    readonlyField = 9;
                }
            }
        

        static void Main(string[] args)
                {
        
                    Class classObj = new Class();
                    Console.WriteLine(classObj.MyField());//9
        
                    Misc.SetVariableyByName(classObj, "readonlyField", 20);//20
                    Console.WriteLine(classObj.MyField());
                 }
        

        这是 SetVariableByName():

        public static b
        
        ool SetVariableyByName(object obj, string var_name, object value)
                    {
                        FieldInfo info = obj.GetType().GetField(var_name, BindingFlags.NonPublic| BindingFlags.Instance);
                        if (info == null)
                        return false;
                    /* ELSE */
                    info.SetValue(obj, value);
                    return true;          
                }
        

        唯一的是 readonlyField 是公共的而不是私有的。我知道您可以编辑私有字段,但不知道为什么它不适合我!

        【讨论】:

        • 仅仅因为你可以这样做并不意味着你应该......当然在很多情况下你不会有权这样做。
        • 我不认为这就是反射的意义所在。不必要的性能损失...
        • 好吧,伙计们,真的!但是,如果您没有选择权,并且必须处理这种情况,那么您必须做点什么。反射不是最好的技术,但它可以解决问题。
        • 为什么会有人这样做?似乎违反了任何可能存在的类不变量。一个非常糟糕的主意。
        • 这是一种怪物解决方案,让一些老 C 程序员认为 OOP 是邪恶的......哈哈
        【解决方案8】:

        C# 编译器仅允许您在内联初始化它们时设置只读字段:

        private readonly int readonlyField = 1;
        

        或来自构造函数:

        public Class()
        {
            readonlyField = 1;
        }
        

        【讨论】:

          【解决方案9】:

          仅使用 getter 的初始化属性怎么样(从 C# 6.0 开始)?

              private int MyProperty { get; } = 0;
          

          【讨论】:

            【解决方案10】:

            我知道这已经很晚了,但是如果需要初始化多个字段,那么使用类作为返回值而不是使用 out 参数呢,有什么缺点吗?恕我直言,这比使用嵌套构造函数更方便且更具可读性。

            public class MyInitClass
            {
                public int Field1 { get; set; }
                public int Field2 { get; set; }
            }
            
            public class Class
            {
                private readonly int readonlyField1;
                private readonly int readonlyField2;
            
                public Class()
                {
                    var init = Init();
                    readonlyField1 = init.Field1;
                    readonlyField2 = init.Field2;
                }
            
                private MyInitClass Init()
                {
                    return new MyInitClass() { Field1 = 1, Field2 = 2 };
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2011-02-15
              • 1970-01-01
              • 2010-11-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-01-18
              相关资源
              最近更新 更多