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

时间:2013-06-27 11:49:42

标签: c# java compiler-construction initialization

首先,介绍:

此代码:

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

无法使用以下错误进行编译:

  

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

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

这与C# spec says - 内容初始化程序无法访问当前正在创建的实例(this)或扩展名中的任何实例字段的内容一致:

  

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

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

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

还在我身边吗?好的,这是问题所在。呃,问题。

在一个假设的世界中,这在C#中是有效的,我想知道:它甚至是否可能?如果是这样,它会添加到表中的优缺点是什么? 此外,由于它确实受到Java的支持,为Java做了相同的优点/缺点 ?或者,类型初始值设定项在两种语言中的工作方式是否存在基本差异

5 个答案:

答案 0 :(得分:13)

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

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

答案 1 :(得分:5)

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

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

答案 2 :(得分:2)

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

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

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

在C#中:

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

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

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

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

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

一个简单的例子说明了这一点:

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

输出:

C.c
B.b
B.ctor
C.ctor

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

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

在Java中:

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

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

一切都在向下继承链。

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

  • 在继承链中重复上一步的上述步骤。

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

以下是证明:(or run it yourself online

class Main
{
    public static void main (String[] args) throws java.lang.Exception
    {
      new C();
    }
}

class C extends B {
    {
        WriteLine("init C");
    }
    int c = GetInt("C.c");

    public C(){
            WriteLine("C.ctor");
    }

}

class B {
    {
        WriteLine("init B");
    }
    int b = GetInt("B.b");

    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
B.b
B.ctor
init C
C.c
C.ctor

这意味着,当字段初始化程序运行时,所有继承的字段都已初始化(通过基类中的初始化程序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.
    }
}

答案 3 :(得分:-1)

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

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

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

答案 4 :(得分:-1)

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

无论如何,无论如何,你可以让我成为一个const(应该是)。