为什么我不能初始化初始化程序中的只读变量? 以下内容不起作用:
class Foo
{
public readonly int bar;
}
new Foo { bar=0; }; // does not work
这是由于CLR的某些技术限制吗?
修改
我知道new Foo { bar=0; }
与new Foo().bar=0;
相同,但是CLR强制执行“readonly”,还是只是编译器限制?
答案 0 :(得分:17)
初始化程序只是语法糖。当你写:
new Foo { bar=0; };
(顺便说一下,这是一个语法错误,应该是这个......)
new Foo { bar=0 }
实际发生的是:
var x = new Foo();
x.bar = 0;
由于该属性是只读的,因此该第二个语句无效。
修改:根据您的修改,问题有点不清楚。根据设计,readonly
属性不可设置。它建立在对象构造上。这由编译器和运行时强制执行。 (不可否认,我没有测试过后者,因为绕过前者需要一些技巧。)
请记住,有两个阶段的“编译”。在将C#代码编译为IL代码时强制执行,并在将IL代码编译为机器代码时强制执行。
这不是CLR的技术限制,并且在给定显式readonly
声明的情况下,它正在完全。构造对象后,您无法设置readonly
属性。
答案 1 :(得分:13)
允许在初始化程序中设置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
方法的一些复杂性,并且在一天结束时,我认为它只是选择的语言限制 (编译器实现语言)强制执行/允许编译时验证。
答案 2 :(得分:5)
由于readonly
变量必须在构造函数中初始化,并且属性初始值设定项在构造对象后执行,因此无效。
答案 3 :(得分:3)
因为初始化程序等同于
var foo = new Foo();
foo.bar=0;
这是幕后的重写。
答案 4 :(得分:2)
你要做的是:
class Foo
{
public readonly int bar;
Foo(int b)
{
bar = b; // readonly assignments only in constructor
}
}
Foo x = new Foo(0);
答案 5 :(得分:1)
因为您指定它是只读的。指定某些东西是只读的然后期望一个写语句可以工作是没有意义的。
答案 6 :(得分:0)
根据this page,CLR默认 - 在处理初始化列表之前构造对象,因此您分配给bar
两次(一次是默认构造,一次是初始化程序处理时)
答案 7 :(得分:0)
这是 readonly 关键字实施的结果。以下引用取自{{3>} 只读:
readonly关键字是一个可以在字段上使用的修饰符。当字段声明包含只读修饰符时,声明引入的字段的赋值只能作为声明的一部分或在同一类的构造函数中出现。
答案 8 :(得分:0)
我知道这不是发布者问题的直接答案,但是新版本的C#现在允许从属性本身直接进行初始化,如下所示。
public int bar { get; set; } = 0;
同样,我知道这并不能解决上面确定(并解决)的只读问题。
答案 9 :(得分:0)
C# 9.0 终于为我们带来了 init-only 属性设置器:
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 };
答案 10 :(得分:-1)
您的代码或假设没有太大的错误,例外情况可能是初始化程序列表的一个重要特征是不强加任何序列约束(特别是对C ++而言)。分号是一个排序运算符,因此初始化列表以逗号分隔。
除非你认为规范在定义上是正确的,否则我认为这里的语言规范是错误的。它部分地打破了语言的一个重要特征,即readonly的概念。其他答案中提到的歧义问题在我看来是一个单一的根本原因。 Readonly是一个非常具有侵入性的特性,对于const正确性的一半是很难正确的,更重要的是,它对开发的编码风格有害。
您正在寻找并可能在此期间找到的是命名参数:https://stackoverflow.com/a/21273185/2712726 这不是你要求的,而是靠近。
同样公平地说,我必须补充说,有很多知识渊博的作者完全不同意这些关于C ++开发人员经常拥有的const正确性的观点。 Eric Lippert无可否认地发表了这篇文章(对C ++开发人员感到恐惧):https://stackoverflow.com/a/3266579/2712726