了解C#字段初始化要求

了解C#字段初始化要求,c#,initialization,C#,Initialization,考虑到以下准则: public class Progressor { private IProgress<int> progress = new Progress<int>(OnProgress); private void OnProgress(int value) { //whatever } } 公共类进程器 { 私有IProgress进度=新进度(OnProgress); 私有void OnProgress(in

考虑到以下准则:

public class Progressor
{
    private IProgress<int> progress = new Progress<int>(OnProgress);

    private void OnProgress(int value)
    {
        //whatever
    }
}
公共类进程器
{
私有IProgress进度=新进度(OnProgress);
私有void OnProgress(int值)
{
//随便
}
}
这会在编译时产生以下错误:

字段初始值设定项不能引用非静态字段、方法或属性“Progressor.OnProgress(int)”的值

我理解它所抱怨的限制,但我不理解为什么会有问题,但是可以在构造函数中初始化字段,如下所示:

public class Progressor
{
    private IProgress<int> progress;

    public Progressor()
    {
         progress =  new Progress<int>(OnProgress);
    }

    private void OnProgress(int value)
    {
        //whatever
    }
}
公共类进程器
{
私营部门的进步;
公共进步者()
{
进度=新进度(OnProgress);
}
私有void OnProgress(int值)
{
//随便
}
}

关于需要此限制的字段初始化和构造函数初始化,C#有什么区别?

第10.5.5.2节:实例字段初始化描述了此行为:

实例字段的变量初始值设定项不能引用 正在创建的实例<因此,引用是编译时错误
这是变量初始值设定项中的
,因为它是 变量初始值设定项,通过 简单名称


这种行为适用于您的代码,因为
OnProgress
是对正在创建的实例的隐式引用。

答案或多或少是这样的,C#的设计者更喜欢这种方式

由于所有字段初始值设定项都转换为构造函数中的指令,这些指令在构造函数中的任何其他语句之前,因此没有技术原因说明不可能这样做。因此,这是一种设计选择

构造函数的好处在于它清楚地说明了赋值的顺序

注意,对于
static
成员,C#设计器的选择不同。例如:

static int a = 10;
static int b = a;
是允许的,与此不同(也允许):

这可能会让人困惑

如果您做出以下决定:

partial class C
{
    static int b = a;
}
和其他地方(在其他文件中):

我甚至不认为会发生什么是明确的

当然,对于实例字段初始值设定项中包含委托的特定示例:

Action<int> progress = OnProgress; // ILLEGAL (non-static method OnProgress)
Action progress=OnProgress;//非法(非静态方法OnProgress)

实际上没有问题,因为它不是对非静态成员的读取或调用。而是使用方法信息,它不依赖于任何初始化。但根据C语言规范,这仍然是一个编译时错误。

我的答案中的所有内容都是我关于“为什么允许这种访问会很危险”的想法。我不知道这是否是它受到限制的真正原因。

C#spec说,字段初始化按照类中声明字段的顺序进行:

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";
    }
}
因此,
\u second
get在
\u third
之前初始化
GetMyString
运行,
\u third
get的“nothey!”值被赋值,但稍后它自己的字段初始化运行,并被设置为“hey!”。不是很有用也不可读,对吗

您还可以在
GetMyString
方法中使用
\u third

public class Progressor
{
    private string _first = "something";
    private string _second = GetMyString();
    private string _third = "hey!";

    private string GetMyString()
    {
        return _third.Substring(0, 1);
    }
}
您希望
\u秒的值是多少?在字段初始化运行之前,所有字段都会获得默认值。对于
string
它将是
null
,因此您将得到意外的
NullReferenceException

因此,在我看来,设计师们决定更容易防止人们犯这种错误

您可以说,好吧,我们不允许访问属性和调用方法,但允许使用声明在您想要访问它的字段之上的字段。比如:

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
    }
}
公共类进程器
{
private Lazy _first=new Lazy(GetMyString);
私有字符串_second=_first.Value;
私有字符串GetMyString()
{
//从上面的例子中选择一个
}
}

方法的所有问题都会再次出现。

字段初始化发生在基类构造函数调用之前,因此它不是有效的对象。此时以
this
作为参数的任何方法调用都会导致无法验证的代码,如果不允许使用无法验证的代码,则会抛出
VerificationException
。例如:在安全透明代码中

  • 10.11.2实例变量初始值设定项 当实例构造函数没有构造函数初始值设定项,或者它有一个形式为base(…)的构造函数初始值设定项时,该构造函数隐式执行由其类中声明的实例字段的变量初始值设定项指定的初始化。这对应于一系列赋值,这些赋值在进入构造函数之后和隐式调用直接基类构造函数之前立即执行。变量初始值设定项按照它们在类声明中出现的文本顺序执行
  • 10.11.3构造函数执行
    变量初始值设定项转换为赋值语句,这些赋值语句在调用基类实例构造函数之前执行。这种排序确保在执行任何有权访问该实例的语句之前,所有实例字段都由其变量初始值设定项初始化
    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
        }
    }