了解C#字段初始化要求

Yis*_*hai 33 c# initialization

考虑以下代码:

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

    private void OnProgress(int value)
    {
        //whatever
    }
}
Run Code Online (Sandbox Code Playgroud)

这会在编译时出现以下错误:

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

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

public class Progressor
{
    private IProgress<int> progress;

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

    private void OnProgress(int value)
    {
        //whatever
    }
}
Run Code Online (Sandbox Code Playgroud)

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

use*_*407 22

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

  • 10.11.2实例变量初始值设定项
    当实例构造函数没有构造函数初始值设定项,或者它具有形式为base(...)的构造函数初始值设定项时,该构造函数隐式执行由其中声明的实例字段的变量初始值设定项指定的初始化.类.这对应于在进入构造函数之后和直接调用直接基类构造函数之前立即执行的赋值序列.变量初始值设定项以它们出现在类声明中的文本顺序执行.
  • 10.11.3构造函数执行将
    变量初始值设定项转换为赋值语句,并在调用基类实例构造函数之前执行这些赋值语句.此排序可确保在执行有权访问该实例的任何语句之前,所有实例字段均由其变量初始值设定项初始化.

  • 引用需要引用它会导致"VerificationException".实际上在一个新的控制台应用程序中测试它(手动生成正确的IL而不是从C#中尝试它)显示它工作得很好.它肯定有可能在某些环境中失败,但在其他环境中失败,但是你的回答应该仍然表明它不一定会失败. (3认同)

Mar*_*zek 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";
    }
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.但是让我们滥用这种力量:

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";
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,_second之前已经初始化了_third.GetMyString跑,_third得到"不嘿!" 分配的值,但稍后它自己的字段初始化运行,它被设置为"嘿!".不是真的有用也不可读,对吧?

你也可以_thirdGetMyString方法中使用:

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

    private string GetMyString()
    {
        return _third.Substring(0, 1);
    }
}
Run Code Online (Sandbox Code Playgroud)

你期望什么价值_second?那么,在字段初始化运行之前,所有字段都会获得默认值.因为string它会null,所以你会意外NullReferenceException.

因此,设计师们认为,防止人们犯这种错误更容易.

你可以说,OK让我们不允许访问属性和调用方法,但是让我们允许使用在你想要访问它的上面声明的字段.就像是:

public class Progressor
{
    private string _first = "something";
    private string _second = _first.ToUpperInvariant();
}
Run Code Online (Sandbox Code Playgroud)

但不是

public class Progressor
{
    private string _first = "something";
    private string _second = _third.ToUpperInvariant();
    private string _third = "another";
}
Run Code Online (Sandbox Code Playgroud)

这似乎是有用和安全的.但仍有一种方法可以滥用它!

public class Progressor
{
    private Lazy<string> _first = new Lazy<string>(GetMyString);
    private string _second = _first.Value;

    private string GetMyString()
    {
        // pick one from above examples
    }
}
Run Code Online (Sandbox Code Playgroud)

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


And*_*ker 9

部分10.5.5.2:实例字段初始化描述了此行为:

实例字段的变量初始值设定项无法引用正在创建的实例.因此,this在变量初始化程序中引用 是编译时错误,因为变量初始化程序通过简单名称引用任何实例成员是编译时错误

此行为适用于您的代码,因为它OnProgress是对正在创建的实例的隐式引用.

  • 好.我认为OP知道他为什么会得到错误.问题更多的是为什么需要这种限制. (5认同)
  • @MarcinJuraszek:当然,但如果没有C#语言设计师,这很难回答 (4认同)

Jep*_*sen 7

答案或多或少,C#的设计者更喜欢它.

由于所有字段初始值设定项都转换为构造函数中的指令,这些指令在构造函数中的任何其他语句之前,因此没有技术原因可以解释为什么这样做不可行.所以这是一个设计选择.

构造函数的好处在于它清楚地表明了赋值的完成顺序.

请注意,对于static成员,C#设计者选择的方式不同.例如:

static int a = 10;
static int b = a;
Run Code Online (Sandbox Code Playgroud)

是允许的,与此不同(也允许):

static int b = a;
static int a = 10;
Run Code Online (Sandbox Code Playgroud)

这可能令人困惑.

如果你做:

partial class C
{
    static int b = a;
}
Run Code Online (Sandbox Code Playgroud)

和其他地方(在其他文件中):

partial class C
{
    static int a = 10;
}
Run Code Online (Sandbox Code Playgroud)

我甚至认为它没有明确定义会发生什么.

当然,对于您在实例字段初始化程序中使用委托的特定示例:

Action<int> progress = OnProgress; // ILLEGAL (non-static method OnProgress)
Run Code Online (Sandbox Code Playgroud)

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