考虑以下代码:
public class Progressor
{
private IProgress<int> progress = new Progress<int>(OnProgress);
private void OnProgress(int value)
{
//whatever
}
}
这会在编译时出现以下错误:
字段初始值设定项不能引用非静态字段,方法或属性&#39; Progressor.OnProgress(int)&#39;
我理解它所抱怨的限制,但我不明白为什么这是一个问题,但该字段可以在构造函数中初始化,而不是如下:
public class Progressor
{
private IProgress<int> progress;
public Progressor()
{
progress = new Progress<int>(OnProgress);
}
private void OnProgress(int value)
{
//whatever
}
}
关于字段初始化与构造函数初始化需要此限制的C#有什么区别?
答案 0 :(得分:22)
字段初始化在基类构造函数调用之前进行,因此它不是有效对象。此时使用this
作为参数的任何方法调用都会导致无法验证的代码,如果不允许无法验证的代码则抛出VerificationException
。例如:在安全透明代码中。
- 10.11.2实例变量初始值设定项
当实例构造函数没有构造函数初始化程序,或者它具有形式为base(...)的构造函数初始值设定项时,该构造函数隐式执行由其类中声明的实例字段的变量初始值设定项指定的初始化。这对应于在进入构造函数之后和直接调用直接基类构造函数之前立即执行的赋值序列。变量初始值设定项以它们出现在类声明中的文本顺序执行。- 10.11.3构造函数执行
变量初始值设定项转换为赋值语句,这些赋值语句在调用基类实例构造函数之前执行。此排序可确保在执行有权访问该实例的任何语句之前,所有实例字段均由其变量初始值设定项初始化。
答案 1 :(得分:18)
我的答案中的所有内容都只是我的想法,为什么允许这种访问是危险的。我不知道这是否是限制它的真正原因。
C#规范说,字段初始化发生在订单字段中,在类中声明:
10.5.5.2。实例字段初始化
变量初始值设定项以文本顺序执行 它们出现在类声明中。
现在,让我们说你提到的代码是可能的 - 你可以从字段初始化中调用实例方法。这将使以下代码成为可能:
public class Progressor
{
private string _first = "something";
private string _second = GetMyString();
private string GetMyString()
{
return "this is really important string";
}
}
到目前为止一切顺利。但是,让我们的滥用权力稍微提高一点:
public class Progressor
{
private string _first = "something";
private string _second = GetMyString();
private string _third = "hey!";
private string GetMyString()
{
_third = "not hey!";
return "this is really important string";
}
}
因此,_second
在_third
之前进行了初始化。 GetMyString
运行,_third
得到&#34;不是嘿!&#34;分配的值,但稍后它自己的字段初始化运行,并且它被设置为'&#34;嘿!&#34;。不是真的有用也不可读,对吧?
您还可以在_third
方法中使用GetMyString
:
public class Progressor
{
private string _first = "something";
private string _second = GetMyString();
private string _third = "hey!";
private string GetMyString()
{
return _third.Substring(0, 1);
}
}
您期望_second
的价值是什么?那么,在字段初始化运行之前,所有字段都会获得默认值。对于string
,它将是null
,因此您会出现意外NullReferenceException
。
所以imo,设计师认为它更容易阻止人们犯这种错误。
你可以说,好吧,我们不允许访问属性和调用方法,但是让我们允许使用在你想要访问它的上面声明的字段。类似的东西:
public class Progressor
{
private string _first = "something";
private string _second = _first.ToUpperInvariant();
}
但不是
public class Progressor
{
private string _first = "something";
private string _second = _third.ToUpperInvariant();
private string _third = "another";
}
这似乎是有用和安全的。但仍有一种方法可以滥用它!
public class Progressor
{
private Lazy<string> _first = new Lazy<string>(GetMyString);
private string _second = _first.Value;
private string GetMyString()
{
// pick one from above examples
}
}
方法的所有问题都会再次出现。
答案 2 :(得分:9)
第10.5.5.2节:实例字段初始化描述了这种行为:
实例字段的变量初始值设定项无法引用 正在创建的实例。 因此,引用它是编译时错误 变量初始值设定项中的
this
,因为它是a的编译时错误 变量初始值设定项通过a引用任何实例成员 简单名称
此行为适用于您的代码,因为OnProgress
是对正在创建的实例的隐式引用。
答案 3 :(得分:7)
答案或多或少,C#的设计者更喜欢它。
由于所有字段初始化程序都被转换为构造函数中的指令,这些指令在构造函数中的任何其他语句之前,因此没有技术原因可以解释为什么这不可能。所以这是一个设计选择。
构造函数的好处在于它清楚地表明了赋值的执行顺序。
请注意,对于static
成员,C#设计者选择的方式不同。例如:
static int a = 10;
static int b = a;
是允许的,与此不同(也允许):
static int b = a;
static int a = 10;
这可能令人困惑。
如果你做:
partial class C
{
static int b = a;
}
和其他地方(在其他文件中):
partial class C
{
static int a = 10;
}
我甚至认为它没有明确定义会发生什么。
当然,对于您在实例字段初始值设定项中使用委托的特定示例:
Action<int> progress = OnProgress; // ILLEGAL (non-static method OnProgress)
确实没有问题,因为它不是非静态成员的读取或调用。而是使用方法信息,它不依赖于任何初始化。但根据C#语言规范,它仍然是一个编译时错误。