【问题标题】:Why can't I initialize readonly variables in a initializer?为什么我不能在初始化程序中初始化只读变量?
【发布时间】:2011-05-26 17:51:32
【问题描述】:

为什么我不能在初始化程序中初始化只读变量? 以下内容无法正常工作:

class Foo
{
    public readonly int bar;
}

new Foo { bar=0; }; // does not work

这是因为 CLR 的一些技术限制吗?

编辑

我知道new Foo { bar=0; }new Foo().bar=0; 相同,但是CLR 强制执行“只读”,还是只是编译器限制?

【问题讨论】:

  • 根据您的最新编辑,我不知道您在问什么。是的,readonly 在运行时由 CLR 强制执行。我不明白它怎么可能是编译器限制。其他答案解释了为什么您尝试做的事情没有任何意义。
  • @Cody Gray -- 语言限制,不是编译器限制。编译器只是实现了语言。
  • @pst:呃,我说“我不明白它怎么可能是编译器限制。”我同意你的看法。

标签: c# .net c#-3.0 initialization readonly


【解决方案1】:

C# 9.0 终于为我们带来了仅初始化属性设置器:

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/init

struct Point
{
    public int X { get; init; }
    public int Y { get; init; }
}

var p = new Point() { X = 42, Y = 13 };

【讨论】:

  • 这是现代 C# 的正确答案。
【解决方案2】:

我知道这不是对发帖人问题的直接回答,但较新版本的 C# 现在允许从属性本身直接初始化,如下所示。

public int bar { get; set; } = 0;

同样,我知道这并不能解决上面确定(并已解决)的只读问题。

【讨论】:

  • 欢迎使用 stackoverflow。如果您知道它没有回答问题,但您想添加其他相关信息,那么评论更合适。
【解决方案3】:

您的代码或假设没有太多错误,但可能是初始化列表的一个重要特性是不施加序列约束(尤其是对于 C++)。分号是一个排序运算符,因此初始化列表是用逗号分隔的。

除非您认为规范在定义上是正确的,否则我认为这里的语言规范是错误的。它部分破坏了该语言的一个重要特征,即只读概念。在我看来,其他答案中提到的模棱两可问题有一个单一的根本原因。 Readonly 是一个非常侵入性的功能,在 const 正确性方面半途而废很难正确,更重要的是,对开发的编码风格有害。

您正在寻找并且可能在此期间找到的内容是命名参数:https://stackoverflow.com/a/21273185/2712726 这不是您要求的,而是接近的。

为了公平起见,我必须补充一点,有些知识渊博的作者完全不同意 C++ 开发人员经常持有的关于 const 正确性的这些观点。 Eric Lippert,公认的有出色的帖子,写了这个(让 C++ 开发人员感到恐惧)声明:https://stackoverflow.com/a/3266579/2712726

【讨论】:

    【解决方案4】:

    初始化器只是语法糖。当你写:

    new Foo { bar=0; };
    

    (顺便说一句,这是一个语法错误,应该是这个......)

    new Foo { bar=0 }
    

    实际发生的是:

    var x = new Foo();
    x.bar = 0;
    

    由于该属性是只读的,因此第二条语句无效。

    编辑:根据您的编辑,问题有点不清楚。 readonly 属性在设计上是不可设置的。它是在对象构造时构建的。这是由编译器和运行时强制执行的。 (诚​​然,我没有测试过后者,因为绕过前者需要一些技巧。)

    请记住,“编译”有两个阶段。将C#代码编译成IL代码时强制执行,将IL代码编译成机器码时强制执行。

    这不是 CLR 的技术限制,它在明确的 readonly 声明中完全正常工作。对象构造完成后,不能设置readonly属性。

    【讨论】:

    • 这个答案越来越好。当我意识到我已经看过它时,我已经看过两次并去投票了。
    • @Cody Gray:似乎有人不同意,有人反对。我很确定这个问题是重复的,我想。很难说出询问的意图。
    • 如果你使用反射没有限制,例如typeof(Foo).GetField("bar").SetValue(oldFooInstance, 42); 将更改只读字段的值。
    • @JeppeStigNielsen:这可能是“绕过编译器的一些诡计”。在许多情况下,这正是反射。例如,还可以通过反射访问私有成员。
    • 绝对!而且它可能会使对象出现故障,因为Foo 的作者没想到你会这样做。但也许提问者对此感兴趣,作为对您出色答案的评论。
    【解决方案5】:

    你想要做的是:

       class Foo
       {
            public readonly int bar;
    
            Foo(int b)
            {
                 bar = b;  // readonly assignments only in constructor
            }
       }
    
       Foo x = new Foo(0);
    

    【讨论】:

    • C# 一直以冗长和样板而闻名。我真的准备好让他们介绍记录类型了。
    【解决方案6】:

    这是 readonly 关键字实施的结果。下面的引用来自MSDN reference for readonly

    readonly 关键字是可以在字段上使用的修饰符。当字段声明包含只读修饰符时,对该声明引入的字段的赋值只能作为声明的一部分或在同一类的构造函数中发生。

    【讨论】:

      【解决方案7】:

      允许在初始化程序中设置readonly会引入无法在编译时强制执行的矛盾和复杂性。我想限制是为了避免歧义。关键是编译时验证。

      想象一下:

      class Foo
      {
          public readonly int bar;
          Foo () {
            // compiler can ensure that bar is set in an invoked ctor
            bar = 0;
          }
      }
      
      // compiler COULD know that `bar` was set in ctor
      // and therefore this is invalid
      new Foo { bar = 0; }
      

      现在,考虑:

      class Foo
      {
          public readonly int bar;
          Foo () {
            // imagine case where bar not set in ctor
          }
      }
      
      // compiler COULD know that `bar` is not bound yet
      // therefore, this COULD be valid
      new Foo { bar = 0; }
      
      // but this COULD be proved to never be valid
      new Foo();
      

      想象一下上述两种情况是统一的(比如,“通过编译器魔法”),但是,输入泛型:

      T G<T> () where T : new
      {
        // What in heck should happen *at compile time*?
        // (Consider both cases above.)
        // What happens if T (Foo) changes to include/not-include setting the
        // readonly variable in the ctor?
        // Consider intermediate code that invokes G<Foo>() and this other
        // code is NOT recompiled even though Foo is--
        //   Yet a binary incompatibility has been added!
        //   No thanks!
        return new T();
      }
      G<Foo>();
      

      我相信我概述的案例显示了使用“动态”readonly 方法的一些复杂性,归根结底,我相信这只是选择的语言限制(编译器实现语言)来强制/允许编译时验证。

      【讨论】:

        【解决方案8】:

        根据this page,CLR 在处理初始化程序列表之前首先默认构造对象,因此您分配给bar 两次(一次在默认构造时,一次在处理初始化程序时)。

        【讨论】:

          【解决方案9】:

          因为初始化器等价于

          var foo = new Foo();
          foo.bar=0;
          

          这是在幕后重写。

          【讨论】:

            【解决方案10】:

            由于readonly变量必须在构造函数中初始化,而属性初始化器在对象构造之后执行,这是无效的。

            【讨论】:

              【解决方案11】:

              因为您指定它是只读的。指定某些内容是只读的然后期望写入语句起作用是没有意义的。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2015-05-16
                • 2012-05-17
                • 1970-01-01
                • 2020-05-31
                • 2020-09-02
                • 2017-12-07
                • 1970-01-01
                相关资源
                最近更新 更多