访问`this`的字段初始化程序:在C#中无效,在Java中有效吗?

一,介绍:

这段代码:

class C { int i = 5; byte[] s = new byte[i]; } 

无法编译时出现以下错误:

字段初始值设定项不能引用非静态字段,方法或属性`C.i’

Resharper说了类似的东西: 无法在静态上下文中访问非静态字段i

这与C#规范所说的一致 – 字段初始化程序无法访问当前正在创建的实例( this ),或者通过扩展,访问任何实例字段:

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

但是,这在Java中运行得很好:

 class C { int i = 5; byte s[] = new byte[i]; //no errors here } 

还在我这儿? 好的,这是问题所在。 呃,问题。

在一个假设的世界中,这在C#中是有效的,我想知道:它甚至可能吗? 如果是这样,它会增加到表中的利弊是什么? 另外,既然Java真的支持它, 那么为 Java 做同样的优点/缺点吗? 或者类型初始化程序在两种语言中的工作方式有根本区别吗?

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

至于为什么这个特性在Java中是合法的,你将不得不问一个Java设计师。

在C#中,字段初始化器仅仅是开发人员的便利语义。 编译器将所有字段初始值设定项移动到构造函数ABOVE的主体中,其中对基础构造函数进行调用。 因此,字段初始化为祖先链,并且该类从基础向下初始化。

静态引用是可以的,因为它们先于其他任何东西进行初始化

这绝不是权威的答案,但让我做出有根据的猜测。

存在根本区别,我认为其他问题的答案与这种差异有关。
它位于类型初始化的顺序中,尤其是在inheritance的上下文中。

那么,实例初始化是如何工作的呢?

在C#中:

  • 所有实例字段初始化程序首先运行,“向上”inheritance链,从大多数派生到基类。

  • 然后,ctors运行,“向下”链,从基地到派生。

ctors调用彼此或(明确地)调用基类的ctors的可能性并没有改变这种情况,所以我会把它留下来。

基本上发生的是,这运行链中的每个chass,从最派生的开始:

 Derived.initialize(){ derivedInstance.field1 = field1Initializer(); [...] Base.Initialize(); Derived.Ctor(); } 

一个简单的例子表明:

 void Main() { new C(); } class C: B { public int c = GetInt("Cc"); public C(){ WriteLine("C.ctor"); } } class B { public int b = GetInt("Bb"); public static int GetInt(string _var){ WriteLine(_var); return 6; } public B(){ WriteLine("B.ctor"); } public static void WriteLine(string s){ Console.WriteLine(s); } } 

输出:

 Cc Bb B.ctor C.ctor 

这意味着如果访问字段初始化程序中的字段有效,我可以做到这一点:

 class C: B { int c = b; //b is a field inherited from the base class, and NOT YET INITIALIZED! [...] } 

在Java中:

关于类型初始化的长篇有趣文章。 总结一下:

它有点复杂,因为除了实例字段初始化器的概念之外,还有(可选) 实例初始化器的概念,但这里是它的要点:

一切都在inheritance链中运行。

  • 类的实例初始化程序运行
  • 基类的字段初始值设定项运行
  • 基类的ctor(s)运行

  • 重复上面的步骤,inheritance链的下一个类。

  • 重复上一步,直到达到最派生的类。

这是证据:( 或在线自己运行 )

 class Main { public static void main (String[] args) throws java.lang.Exception { new C(); } } class C extends B { { WriteLine("init C"); } int c = GetInt("Cc"); public C(){ WriteLine("C.ctor"); } } class B { { WriteLine("init B"); } int b = GetInt("Bb"); public static int GetInt(String _var){ WriteLine(_var); return 6; } public B(){ WriteLine("B.ctor"); } public static void WriteLine(String s){ System.out.println(s); } } 

输出:

 init B Bb B.ctor init C Cc C.ctor 

这意味着,在字段初始化程序运行时,所有inheritance的字段都已初始化(通过基类中的初始化程序OR ctor),因此它足够安全以允许此行为:

 class C: B { int c = b; //b is inherited from the base class, and it's already initialized! [...] } 

在Java中,与C#一样,字段初始值设定项按声明顺序运行。
Java编译器甚至会检查字段初始化程序是否未被调用无序*:

 class C { int a = b; //compiler error: illegal forward reference int b = 5; } 

*另外,如果初始化程序调用实例方法,则可以无序访问字段:

 class C { public int a = useB(); //after initializer completes, a == 0 int b = 5; int useB(){ return b; //use b regardless if it was initialized or not. } } 

这是因为字段初始化程序由编译器移动到构造函数中(除非是静态的),因此您需要在构造函数中显式,如下所示:

 class C { int i = 5; byte[] s; public C() { s = new byte[i]; } } 

这是一个非答案,但我喜欢把类的主体中的任何东西都视为与序列无关。 它不应该是需要以特定方式进行评估的顺序代码 – 它只是类的默认状态。 如果你使用这样的代码,你期望我在s之前进行评估。

无论如何,无论如何,你可以制作ia const(应该是)。