访问“this”的字段初始值设定项已重新加载
本文关键字:新加载 加载 this 字段 访问 | 更新日期: 2023-09-27 18:20:14
这个问题是Cristi Diaconescu关于在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# 规范非常简单(在一定程度上(:
实例字段的变量初始值设定项不能引用正在创建的实例。因此,在变量初始值设定项中引用它是一个编译时错误,因为变量初始值设定项通过简单名称引用任何实例成员是一个编译时错误。
所以我的问题是:">通过一个简单的名字"是什么意思?
是否有其他合法的机制?我确信规范中的几乎每个单词都有非常具体的原因,那么将这个特定代码的非法性限制为通过简单名称引用的原因是什么?
编辑:我的问题措辞不太好。我不是在问">简单名称"的定义,而是在问将非法性限制在那个特定情况下的原因。如果以任何方式引用任何实例成员总是非法的,那么为什么要如此狭隘地指定它呢?如果不是,那么什么机制是合法的?
在一般情况下,不可能确定表达式是否引用正在构造的对象,因此禁止它并要求编译器诊断它需要不可能的事情。考虑
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);
}
}
对于编译时可检测到的内容,只能获取编译器错误。
根据文档:
简单名称由单个标识符组成。
我想他们澄清了这一点,因为当范围内没有名为 i
的变量时,this.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];
}
由于这两个成员位于不同的类中,因此它们的初始化顺序是明确的。
当非托管代码发挥作用时,你总是可以做一些非常混乱的事情。 考虑一下:
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
值。 这会很难,但这是可能的。
我认为你只是误读了最后一句话。规范明确指出实例字段初始值设定项不能引用正在创建的实例。然后,它只是简单地引用示例。您不能使用this
,出于同样的原因,您不能使用"简单名称",因为简单名称访问隐式使用 this
。规范并没有缩小情况范围。它只是指出一些非法的特定结构。另一个是使用 base
从基类访问受保护的字段。