Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't this C# instance constructor being called, unless there is a reference to a non-static member? [duplicate]

Tags:

This code seems to not call the Mixed constructor and prints y = 0

public class Mixed
{
    public int x;
    public static int y;

    public Mixed()
    {
        x = 1;
        y = 1;
    }
}    

public class Program
{
    static Mixed mixed = new Mixed();

    static void Main(string[] args)
    {
        Console.WriteLine("y = " + Mixed.y);

        Console.ReadLine();
    }
}

However, simply modifying the Main function to look like this results in the constructor being called.

static void Main(string[] args)
{   
    Console.WriteLine("x = " + mixed.x);
    Console.WriteLine("y = " + Mixed.y);

    Console.ReadLine();
}

This prints:

x = 1
y = 1

Why does simply adding this reference to a non-static field result in the constructor being called correctly? Shouldn't creating the object always result in a constructor being called, regardless of how that object is used later in the program?

Oddly enough, making the Mixed object non-static like this also results in the constructor being called:

public class Program
{
    static void Main(string[] args)
    {
        Mixed mixed = new Mixed();

        Console.WriteLine("y = " + Mixed.y);

        Console.ReadLine();
    }
}

However, this doesn't seem to make sense to me either. Declaring the Mixed object as static should only imply that there is only one copy of the object in memory, regardless of how many times Program is instantiated. Is this some sort of compiler optimization that for static fields the compiler waits for a reference to a non-static field of that type before actually instantiating it?

like image 903
William Avatar asked Sep 11 '18 14:09

William


1 Answers

What you're experiencing is that the static field of the Program type are not being initialized.

You will find that all static fields of a type are initialized when any of them are accessed for the first time. This is agreed between the different runtimes (.NET Framework, .NET Core, Mono).

As seen in the following IL code, your example will generate an .cctor (static constructor) which initializes the fields.

.class public auto ansi beforefieldinit ConsoleApp1.Program
    extends [System.Runtime]System.Object
{
    .field private static class ConsoleApp1.Mixed mixed

    // (...) omitted irrelevant methods

    .method private hidebysig specialname rtspecialname static 
        void .cctor () cil managed 
    {
        // Code size 11 (0xb)
        .maxstack 8

        IL_0000: newobj instance void ConsoleApp1.Mixed::.ctor()
        IL_0005: stsfld class ConsoleApp1.Mixed ConsoleApp1.Program::mixed
        IL_000a: ret
    }

}

It is however not agreed at what point the fields will be initialized. .NET Core for instance will lazy load the fields, just before any of them are being accessed. .NET Framework will eagerly load the fields when any member of the type (including methods) are being accessed.

The following code will have different results in .NET Core and .NET Framework.

public class Program
{
    static Mixed mixed = new Mixed();

    static string text = "Hello, World!";

    static void Main(string[] args)
    {
        Console.WriteLine("y = " + Mixed.y);
        Console.WriteLine(text);
        Console.WriteLine("y = " + Mixed.y);

        Console.ReadLine();
    }
}

In .NET Core:

y = 0
Hello, World!
y = 1

In .NET Framework:

y = 1
Hello, World!
y = 1


If you add a static constructor to the class, the fields will always be initialized when any field or method is accessed:

// ... adding to previous Program class
static Program()
{
    // empty body
}

In .NET Core:

y = 1
Hello, World!
y = 1

This happens because the beforefieldinit flag will not be added to a type with a custom static constructor. This flag indicates that the fields of the type can be loaded as late/lazy as possible.

When this flag is omitted, the fields will be initialized eagerly. When the flag is present, the fields are at least loaded lazily but could also be loaded eagerly, this is up to the runtime.

As seen previously, .NET Core will load the fields lazily when the flag is present. .NET Framework will will load the fields eagerly, no matter if the flag is present.

like image 193
ikkentim Avatar answered Sep 28 '22 18:09

ikkentim