字段初始化程序访问'this'重新加载

时间:2013-06-28 15:34:29

标签: c#

此问题是Cristi Diaconescu's关于在C#中访问this的字段初始化程序非法性的扩展。

这在C#中是非法的:

class C
{
    int i = 5;
    double[] dd = new double[i]; //Compiler error: A field initializer cannot reference the non-static field, method, or property.
}

好的,所以合理解释为什么这是非法的,其中包括Eric Lippert

  

简而言之,在构造函数体运行之前访问接收器的能力是边缘效益的一个特征,这使得编写错误程序变得更容易。因此,C#语言设计者完全禁用它。如果你需要使用接收器,那么将该逻辑放在构造函数体中。

此外,C#规范非常简单(直到某一点):

  

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

所以我的问题是:“通过简单名称”是什么意思?

是否存在合法的替代机制?我确信规范中的几乎每个单词都有一个非常具体的原因,那么将这个特定代码的非法性通过简单的名称限制为引用的原因是什么?

编辑:我没有把我的问题措辞得太好。我不是要求“简单名称”的定义,我问的是将非法性限制在特定情况的原因。如果以任何方式引用任何实例成员始终是非法的,那么为什么要如此狭窄地指定呢?如果不是,那么什么机制合法?

4 个答案:

答案 0 :(得分:5)

在一般情况下,确定表达式是否指向正在构造的对象是不可能的,因此禁止它并要求编译器诊断它将需要不可能的。考虑

partial class A {
  public static A Instance = CreateInstance();
  public int a = 3;
  public int b = Instance.a;
}

有可能,据我所知完全有效,即使是一个可怕的想法,用FormatterServices.GetUninitializedObject(typeof(A))创建一个对象,将A.Instance设置为该对象,然后调用构造函数。初始化b时,对象将读取其自己的a成员。

partial class A {
  public static A CreateInstance() {
    Instance = (A)FormatterServices.GetUninitializedObject(typeof(A));
    var constructor = typeof(A).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
    var helperMethod = new DynamicMethod(string.Empty, typeof(void), new[] { typeof(A) }, typeof(A).Module, true);
    var ilGenerator = helperMethod.GetILGenerator();
    ilGenerator.Emit(OpCodes.Ldarg_0);
    ilGenerator.Emit(OpCodes.Call, constructor);
    ilGenerator.Emit(OpCodes.Ret);
    var constructorInvoker = (Action<A>)helperMethod.CreateDelegate(typeof(Action<A>));
    constructorInvoker(Instance);
    return Instance;
  }
}

static class Program {
  static void Main() {
    Console.WriteLine("A.Instance = (a={0}, b={1})", A.Instance.a, A.Instance.b);
  }
}

您只能在编译时检测到编译器错误。

答案 1 :(得分:1)

根据the documentation

  

简单名称由单个标识符组成。

我想他们澄清了这一点,因为this.i等同于类方法中的i,而没有名为i的变量在范围内。他们已经禁止在实例方法之外使用this

class C
{
    int i = 5;
    double[] dd = new double[this.i]; 
    //Compiler error: Keyword 'this' is not available in the current context.
}

如果没有这种语言,有些人可能会认为这意味着您可以通过省略关键字this来引用实例变量。

最好的选择是使用构造函数:

class C
{
    int i = 5;
    double[] dd;
    C()
    {
        dd = new double[i];
    }
}

你也可以这样做:

class C
{
    public int i = 5;
}
class D
{
    double[] dd = new double[new C().i];
}

由于这两个成员属于不同的类,因此初始化它们的顺序是明确的。

答案 2 :(得分:1)

当非托管代码发挥作用时,你总是可以搞砸了。考虑一下:

public class A
{
    public int n = 42;
    public int k = B.Foo();

    public A()
    {

    }
}

public class B
{
    public static unsafe int Foo()
    {
        //get a pointer to the newly created instance of A 
        //through some trickery.  
        //Possibly put some distinctive field value in `A` to make it easier to find

        int i = 0;
        int* p = &i;
        //get p to point to n in the new instance of `A`

        return *p;
    }
}

我花了一些时间试图实际实现这个(踢),但稍稍放弃了。也就是说,您可以获得指向堆的指针,然后开始寻找可以识别为A实例的内容,然后从中获取n值。这很难,但有可能。

答案 3 :(得分:-1)

我认为你只是误读了最后一句话。该规范断然说明实例字段初始化程序无法引用正在创建的实例。然后它只是引用了一些例子。您无法使用this,出于同样的原因,您无法使用“简单名称”,因为简单的名称访问会隐式使用this。规范并未缩小案例范围。它只是调出一些非法的具体结构。另一个是使用base从基类访问受保护的字段。