Why does a recursive constructor call make invalid C# code compile?


Answers

I think because the language specification only rules out directly invoking the same constructor that is being defined.

From 10.11.1:

All instance constructors (except those for class object) implicitly include an invocation of another instance constructor immediately before the constructor-body. The constructor to implicitly invoke is determined by the constructor-initializer

...

  • An instance constructor initializer of the form this(argument-listopt) causes an instance constructor from the class itself to be invoked ... If an instance constructor declaration includes a constructor initializer that invokes the constructor itself, a compile-time error occurs

That last sentence seems to only preclude direct calling itself as producing a compile time error, e.g.

Foo() : this() {}

is illegal.


I admit though - I can't see a specific reason for allowing it. Of course, at the IL level such constructs are allowed because different instance constructors could be selected at runtime, I believe - so you could have recursion provided it terminates.


I think the other reason it doesn't flag or warn on this is because it has no need to detect this situation. Imagine chasing through hundreds of different constructors, just to see if a cycle does exist - when any attempted usage will quickly (as we know) blow up at runtime, for a fairly edge case.

When it's doing code generation for each constructor, all it considers is constructor-initializer, the field initializers, and the body of the constructor - it doesn't consider any other code:

  • If constructor-initializer is an instance constructor for the class itself, it doesn't emit the field initializers - it emits the constructor-initializer call and then the body.

  • If constructor-initializer is an instance constructor for the direct base class, it emits the field initializers, then the constructor-initializer call, and then then body.

In neither case does it need to go looking elsewhere - so it's not a case of it being "unable" to decide where to place the field initializers - it's just following some simple rules that only consider the current constructor.

Question

After watching webinar Jon Skeet Inspects ReSharper, I've started to play a little with recursive constructor calls and found, that the following code is valid C# code (by valid I mean it compiles).

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()   { }
}

As we all probably know, field initialization is moved into constructor by the compiler. So if you have a field like int a = 42;, you will have a = 42 in all constructors. But if you have constructor calling another constructor, you will have initialization code only in called one.

For example if you have constructor with parameters calling default constructor, you will have assignment a = 42 only in the default constructor.

To illustrate second case, next code:

class Foo
{
    int a = 42;

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

Compiles into:

internal class Foo
{
    private int a;

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

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

So the main issue, is that my code, given at the start of this question, is compiled into:

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

As you can see, the compiler can't decide where to put field initialization and, as result, doesn't put it anywhere. Also note, there are no base constructor calls. Of course, no objects can be created, and you will always end up with Exception if you will try to create an instance of Foo.

I have two questions:

Why does compiler allow recursive constructor calls at all?

Why we observe such behavior of the compiler for fields, initialized within such class?


Some notes: ReSharper warns you with Possible cyclic constructor calls. Moreover, in Java such constructor calls won't event compile, so the Java compiler is more restrictive in this scenario (Jon mentioned this information at the webinar).

This makes these questions more interesting, because with all respect to Java community, the C# compiler is at least more modern.

This was compiled using C# 4.0 and C# 5.0 compilers and decompiled using dotPeek.




I think this is allowed because you can (could) still catch the Exception and do something meaningfull with it.

The initialisation will never be run, and it will almost certaintly throw a Exception. But this can still be wanted behaviour, and didn't always mean the process should crash.

As explained here https://.com/a/1599236/869482




Related



Tags

c# c#