9

这个问题是Cristi Diaconescuthis关于在 C# 中访问字段初始值设定项的非法性的扩展。

这在 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# 规范非常简单(在一定程度上):

实例字段的变量初始值设定项不能引用正在创建的实例。因此,在变量初始化器中引用 this 是编译时错误,因为变量初始化器通过 simple-name引用任何实例成员是编译时错误。

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

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

编辑:我的问题措辞不够好。我不是在询问“简单名称”的定义,而是询问将非法性限制在特定场景的原因。如果以任何方式引用任何实例成员总是非法的,那么为什么要如此狭隘地指定它呢?如果不是,那么什么机制是合法的?

4

4 回答 4

6

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

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);
  }
}

您只能获取编译时可检测到的编译器错误。

于 2013-06-28T17:11:51.167 回答
1

根据文档

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

我想他们澄清了这一点,因为当没有命名变量在范围内时,这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];
}

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

于 2013-06-28T15:44:22.410 回答
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从中获取值。这会很难,但这是可能的。

于 2013-06-28T16:56:53.953 回答
-1

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

于 2013-06-28T18:23:12.747 回答