How Does Static Field Initialization Work in C#

How does static field initialization work in C#?

This is correct.

Your static initializers, then the static constructor is run before your standard constructor, but when it runs, it's using new A(), so passing through your non-static constructor path. This causes the messages you see.

Here is the full path of execution:

When you first call var a = new A(); in your program, this is the first time A is accessed.

This will fire off the static initialization of A._A

At this point, A._A constructs with _A = (new A()).I();

This hits


Console.WriteLine("new A()");
if (_A == null)
Console.WriteLine("_A == null");

since at this point, _A hasn't been set with the returned, constructed type (yet).

Next, the static constructor A { static A(); } is run. This prints the "static A()" message.

Finally, your original statement (var a = new A();) is executed, but at this point, the statics are constructed, so you get the final print.

C# force static fields initialization

A static constructor is only invoked when the class is first accessed.

While you did used DerivedEnum.AllKeys, but it is simply inherited from BaseEnum. Therefore DerivedEnum were never directly referenced.

A little hack you could do is actually create a new static property on DerivedEnum that returns the same property from the base class, so when it is called the static constructor of the derived class will be invoked.

public class DerivedEnum : BaseEnum
{
public new static ICollection<string> AllKeys
{
get
{
return BaseEnum.AllKeys;
}
}
}

UPDATE

You can also use System.Reflexion to dynamically invoke them :

public class BaseEnum
static BaseEnum()
{
// from the types defined in current assembly
Assembly.GetExecutingAssembly().DefinedTypes
// for those who are BaseEnum or its derived
.Where(x => typeof(BaseEnum).IsAssignableFrom(x))
// invoke their static ctor
.ToList().ForEach(x => System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(x.TypeHandle));
}
}

You can also use this code to initialize derived class defined in other assemblies :

AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.DefinedTypes)
.Where(x => typeof(BaseEnum).IsAssignableFrom(x))
.ToList().ForEach(x => System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(x.TypeHandle));

Static field is initialized later when the class has a static constructor

Can someone explain this behaviour? What can be the reason for this?

@JonSkeet has a paragraph in C# in Depth about static fields and static constructors. Here's a snippet:

The C# specification states:

  • The static constructor for a class executes at most once in a given
    application domain. The execution of a static constructor is triggered
    by the first of the following events to occur within an application
    domain:

    • An instance of the class is created.
    • Any of the static members of the
      class are referenced.

The CLI specification (ECMA 335) states in
section 8.9.5:

A type may have a type-initializer method, or not. A type may be
specified as having a relaxed semantic for its type-initializer method
(for convenience below, we call this relaxed semantic BeforeFieldInit):
.

  • If marked BeforeFieldInit then the type's initializer method is
    executed at, or sometime before, first access to any static field
    defined for that type
    .
  • If not marked BeforeFieldInit then that type's
    initializer method is executed ((at (i.e., is triggered by): first
    access to any static or instance field of that type, or first
    invocation of any static, instance or virtual method of that type))

This goes to show you that when a type doesn't have the beforefieldinit flag, the run-time may invoke it in an arbitrary time, given that it is before the first access to any static field defined, which is exactly what you're seeing.

Is there any other thing which can change when the static constructor
is called?

The only thing is creating a static type constructor on your type. Otherwises, you have no control over it's invocation.

So isn't the whole thing I described a little bit problematic?

Problematic in what regards? I see no problem as long as you know what you're in for. The CLI specification makes it absolutely clear as to what guarantees you have with a type initializer and without. Thus, if you follow those guidelines there should be no ambiguity.

Static Field Initializers

Static initialization of fields happens in non-deterministic order, try making the constructors of A and B static, and initialize the variables inside. That ensure it is initialized the first time your class is used and in the order you specified.

Static field initialization order (C#) - can someone explain this snippet?

In C#, primitive types such as int take on a default value. For int, the default value is 0. This is not like C++ where you have to initialize the value or it will be getting a random dirty value from memory. Since Y is known to be of type int, X can be assigned it's default value of 0.

As far as the keyword ctor, I don't see it used very much as a C# programmer, but I am familiar with the term. I believe it is used in a few places in Visual Studio, for instance in the object browser.

Static field initializer not being run before instance constructor

Static Fields and properties are initialised in the order they appear in the class.

C# Spec

To Quote:

The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration.

Also for completeness:

If a static constructor exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class

Someone has pointed out in the comments and correctly so that this doesn't mention properties, just fields. I would say that auto properties are syntactic sugar over a private field and a property with get and set accessors, which is just more sugar so having Get() and Set(value) methods. So the above does apply to properties, the only caveat being that I dont know where and how the compiler orders those backing fields.

You initialise your Pending, Rejected and Accepted fields using the ctor that relies on a field that is after that fields being intialised.

Either put your hashset field first, so it gets initialised first. Or I think a better way would be to use a static ctor to initialise all this so you can clearly see the order and the dependencies of each become clearer. Also referring to the previous note about auto properties and where the compiler stores the private backing fields, it would be even more pertinent to use the static ctor and be fully confident of the order that they get the appropriate values set on them

static field initialization explanation and its requirement

When a type has a static constructor, the runtime is constrained to execute all type initialization immediately before the first use of any member of the type.

When it doesn't have a static constructor, the runtime has much more freedom - it has to execute the type initializer at some point before the first use of a static field or before an instance is constructed, but that's all. You can even observe static methods which don't touch static fields being executed without the type initializer executing.

In your case, both A and B have static constructors, and the order of access to the members is B first, then A, hence your output. Without those static constructors, you'd still be guaranteed to get "1 1" as the last line, and you'd still get both "Init A" and "Init B", but the ordering of them wouldn't be guaranteed.

This is just the way the language and runtime are specified - and usually it has no impact, as usually type initializers should just be setting up the type, with no other side effects. Type initializers should do as little as possible, ideally - if they fail for some reason, the type will never be usable; type initializers aren't retried.

For more details, see my beforefieldinit article and .NET 4.0 type initializer changes blog post.



Related Topics



Leave a reply



Submit