为什么递归构造函数调用使无效的C#代码编译?

在观看网络研讨会Jon Skeet Inspects ReSharper后,我开始玩一点
递归构造函数调用并发现,下面的代码是有效的C#代码(通过有效的我的意思是它编译)。

class Foo
{
    int a = null;
    int b = AppDomain.CurrentDomain;
    int c = "string to int";
    int d = NonExistingMethod();
    int e = Invalid<Method>Name<<Indeeed();

    Foo()       :this(0)  { }
    Foo(int v)  :this()   { }
}

正如我们大概知道的,字段初始化由编译器转移到构造函数中。所以如果你有一个字段int a = 42,你将有一个= 42在所有构造函数。但是如果你有构造函数调用另一个构造函数,你将只有在被调用的初始化代码。

例如,如果你有带有参数调用默认构造函数的构造函数,你将只在默认构造函数中赋值a = 42。

为了说明第二种情况,下面的代码:

class Foo
{
    int a = 42;

    Foo() :this(60)  { }
    Foo(int v)       { }
}

编译成:

internal class Foo
{
    private int a;

    private Foo()
    {
        this.ctor(60);
    }

    private Foo(int v)
    {
        this.a = 42;
        base.ctor();
    }
}

所以主要问题是,我的代码,在这个问题的开头给出,被编译成:

internal class Foo
{
    private int a;
    private int b;
    private int c;
    private int d;
    private int e;

    private Foo()
    {
        this.ctor(0);
    }

    private Foo(int v)
    {
        this.ctor();
    }
}

正如你所看到的,编译器不能决定在哪里放置字段初始化,因此,不会把它放在任何地方。还要注意,没有基础构造函数调用。当然,没有对象可以创建,如果你将尝试创建一个Foo的实例,你将总是最终与StackOverflowException。

我有两个问题:

为什么编译器允许递归构造函数调用?

为什么我们观察编译器对字段的这种行为,在这样的类中初始化?

一些注意事项:ReSharper警告您可能的循环构造函数调用。此外,在Java中,这种构造函数调用不会事件编译,所以Java编译器在这种情况下更有限制性(Jon在网络研讨会上提到了这个信息)。

这使得这些问题更有趣,因为在所有方面对Java社区,C#编译器至少更现代。

这使用C# 4.0C# 5.0编译器编译并使用dotPeek反编译。

有趣的发现。

看来,实际上只有两种类型的实例构造函数:

>一个实例构造函数,它使用:this(…)语法链接同一类型的另一个实例构造函数。
>链接基类的实例构造函数的实例构造函数。这包括没有指定chainig的实例构造函数,因为:base()是默认值。

(我忽略了System.Object的实例构造函数,这是一个特例,System.Object没有基类!但System.Object也没有字段。)

可能存在于类中的实例字段初始化器需要被复制到上面类型2的所有实例构造器的主体的开头,而没有类型1的实例构造器需要字段赋值代码。

所以显然,C#编译器不需要对类型1的构造函数进行分析,以查看是否存在循环。

现在你的例子给出一个情况,其中所有实例构造函数都是类型1 ..在这种情况下,字段initaializer代码不需要放在任何地方。所以它不是很深的分析,似乎。

事实证明,当所有实例构造函数都是类型1时,你甚至可以从一个没有可访问构造函数的基类派生。基类必须是非密封的。例如,如果你写一个类只有私人实例构造函数,人们仍然可以从你的类派生,如果他们使所有的实例构造函数在派生类中的类型1。然而,一个新的对象创建表达式永远不会完成,当然。要创建派生类的实例,必须“欺骗”并使用类似System.Runtime.Serialization.FormatterServices.GetUninitializedObject方法的东西。

另一个例子:System.Globalization.TextInfo类只有一个内部实例构造函数。但是你仍然可以从这个类中得到这个类,而不是mscorlib.dll这个技术。

最后,关于

Invalid<Method>Name<<Indeeed()

句法。根据C#规则,这是读为

(Invalid < Method) > (Name << Indeeed())

因为左移运算符<具有比小于运算符<和大于运算符&gt ;.后两个操作数具有相同的优先级,因此由左相关规则进行评估。如果类型是

MySpecialType Invalid;
int Method;
int Name;
int Indeed() { ... }

并且如果MySpecialType引入了操作符<,(MySpecialType,int)的重载,则表达式

Invalid < Method > Name << Indeeed()

将是合法和有意义的。

在我看来,如果编译器在这种情况下发出警告将更好。例如,它可以表示检测到不可达的代码,并指向字段初始值设定器的行和列号,该行和列号从未转换为IL。

http://stackoverflow.com/questions/16645267/why-does-a-recursive-constructor-call-make-invalid-c-sharp-code-compile

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:为什么递归构造函数调用使无效的C#代码编译?