33
votes

Considering the following code:

public class Progressor
{
    private IProgress<int> progress = new Progress<int>(OnProgress);

    private void OnProgress(int value)
    {
        //whatever
    }
}

This gives the following error on compilation:

A field initializer cannot reference the non-static field, method, or property 'Progressor.OnProgress(int)'

I understand the restriction it is complaining about, but I don't understand why it is an issue, but the field can be initialized in the constructor instead as follows:

public class Progressor
{
    private IProgress<int> progress;

    public Progressor()
    {
         progress =  new Progress<int>(OnProgress);
    }

    private void OnProgress(int value)
    {
        //whatever
    }
}

What is the difference in C# regarding the field initialization vs constructor initialization that requires this restriction?

4
The only thing that comes to my mind is the fact, that when particular field initialization runs all the other fields may or may not be yet initialized. Running an instance method at that point, which would give you access to all instance members, makes it possible to access other fields that were not yet initialized. That's potential source of problems. Why is it allowed in constructor? Because all the fields are initialized before constructor code runs. - MarcinJuraszek
@MarcinJuraszek This was my understanding, although you could make the argument that the runtime could simply initialize all the fields to their default values (i.e. 0, null, etc.), then run the field initializations, then run the applicable constructor. - sdzivanovich
@sdzivanovich That's exactly what happens - all the fields get initialized with default values before any initialization happens. But it still doesn't solve the issue of initialization code being unpredictable (see my answer). - MarcinJuraszek
The curious thing is that the field initializer doesn't need to run the instance method, only refer to it. Seems like it ought to be allowable. - yoyo

4 Answers

22
votes

Field initialization come before base class constructor call, so it is not a valid object. Any method call with this as argument at this point leads to unverifiable code and throws a VerificationException if unverifiable code is not allowed. For example: in security transparent code.

  • 10.11.2 Instance variable initializers
    When an instance constructor has no constructor initializer, or it has a constructor initializer of the form base(...), that constructor implicitly performs the initializations specified by the variable-initializers of the instance fields declared in its class. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor and before the implicit invocation of the direct base class constructor. The variable initializers are executed in the textual order in which they appear in the class declaration.
  • 10.11.3 Constructor execution
    Variable initializers are transformed into assignment statements, and these assignment statements are executed before the invocation of the base class instance constructor. This ordering ensures that all instance fields are initialized by their variable initializers before any statements that have access to that instance are executed.
18
votes

Everything in my answer is just my thoughts on 'why it would be dangerous to allow that kind of access'. I don't know if that's the real reason why it was restricted.

C# spec says, that field initialization happens in the order fields are declared in the class:

10.5.5.2. Instance field initialization

The variable initializers are executed in the textual order in which they appear in the class declaration.

Now, let's say the code you've mentioned is possible - you can call instance method from field initialization. It would make following code possible:

public class Progressor
{
    private string _first = "something";
    private string _second = GetMyString();

    private string GetMyString()
    {
        return "this is really important string";
    }
}

So far so good. But let's abuse that power a little bit:

public class Progressor
{
    private string _first = "something";
    private string _second = GetMyString();
    private string _third = "hey!";

    private string GetMyString()
    {
        _third = "not hey!";
        return "this is really important string";
    }
}

So, _second get's initialized before _third. GetMyString runs, _third get's "not hey!" value assigned, but later on it's own field initialization runs, and it's being set to `"hey!". Not really useful nor readable, right?

You could also use _third within GetMyString method:

public class Progressor
{
    private string _first = "something";
    private string _second = GetMyString();
    private string _third = "hey!";

    private string GetMyString()
    {
        return _third.Substring(0, 1);
    }
}

What would you expect to be value of _second? Well, before field initialization runs all the fields get default values. For string it would be null, so you'll get unexpected NullReferenceException.

So imo, designers decided it's just easier to prevent people from making that kind of mistakes at all.

You could say, OK let's disallow accessing properties and calling methods, but let's allow using fields that were declared above the one you want to access it from. Something like:

public class Progressor
{
    private string _first = "something";
    private string _second = _first.ToUpperInvariant();
}

but not

public class Progressor
{
    private string _first = "something";
    private string _second = _third.ToUpperInvariant();
    private string _third = "another";
}

That's seems useful and safe. But there is still a way to abuse it!

public class Progressor
{
    private Lazy<string> _first = new Lazy<string>(GetMyString);
    private string _second = _first.Value;

    private string GetMyString()
    {
        // pick one from above examples
    }
}

And all the problems with methods happen to come back again.

9
votes

Section 10.5.5.2: Instance field initialization describes this behavior:

A variable initializer for an instance field cannot reference the instance being created. Thus, it is a compile-time error to reference this in a variable initializer, as it is a compile-time error for a variable initializer to reference any instance member through a simple-name

This behavior applies to your code because OnProgress is an implicit reference to the instance being created.

7
votes

The answer is more or less, the designers of C# preferred it that way.

Since all field initializers are translated into instructions in the constructor(s) which go before any other statements in the constructor, there is no technical reason why this should not be possible. So it is a design choice.

The good thing about a constructor is that it makes it clear in what order the assignments are done.

Note that with static members, the C# designers chose differently. For example:

static int a = 10;
static int b = a;

is allowed, and different from this (also allowed):

static int b = a;
static int a = 10;

which can be confusing.

If you make:

partial class C
{
    static int b = a;
}

and elsewhere (in other file):

partial class C
{
    static int a = 10;
}

I do not even think it is well-defined what will happen.

Of course for your particular example with delegates in an instance field initializer:

Action<int> progress = OnProgress; // ILLEGAL (non-static method OnProgress)

there is really no problem since it is not a read or an invocation of the non-static member. Rather the method info is used, and it does not depend on any initialization. But according to the C# Language Specification it is still a compile-time error.