c# – Is it possible to do without a static constructor?

Question:

It is clear to me that a static constructor is used to assign values ​​to static variables, that it is called first of all when an object of a class is created.

But why is it needed if I can assign these values ​​when declaring variables?

Answer:

There are several ways to initialize fields in C #:

  1. At the place of the announcement

  2. In the constructor

Here's a naive example:

class Foo 
{
  private string s1 = "s1";
  private string s2;

  private static string s3 = "s3";
  private static string s4;

  public Foo()
  {
    s2 = "s2";
  }

  static Foo()
  {
    s4 = "s4";
  }
}

From a bird's eye view, in-place initialization is fairly straightforward: the expression used to initialize a field is transferred to the appropriate constructor — an instance constructor for instance fields, and a static constructor for static fields.

Here I must say why we have two constructors. An instance constructor is some kind of helper function designed to initialize an instance of the object being created. For example, we can say that any valid object must have some behavior (invariant) and the constructor is a special function that must provide it. An invariant can be anything: starting from the fact that some field is not zero, ending with more complex rules, for example, that the sum of the дебет and кредит fields is equal to 0.

In addition to the invariants of objects, there are also invariants of the type: i.e. some conditions that must be true not for specific objects, but for the whole type. The behavior of a type is expressed using static members, which means that a type's "invariant" is a valid state of static variables, the responsibility for the validity of which is provided by a static constructor.

Unlike the constructor of an object, the constructor of a type is not invoked by the user. Instead, it is called by the CLR the first time the type is accessed (skip the exact rules).

There are subtle differences in run-time behavior that distinguish the use of a field initializer at the declaration site from the initialization of the same fields in the constructor. Static and instance field initializers are syntactic sugar, but it's important to know which one!

Difference in case of instance fields and constructors

All instance field initializers are moved by the C # compiler into the instance constructor. But the main question here is where exactly.

In general terms, an instance constructor looks like this:

// ctor
// Код инициализаторов полей
// Вызов конструктора базового класса
// Код текущего конструктора

This algorithm has two important consequences. First, the code for the initializers of the instance fields is not just placed in the constructor, it is placed at the very beginning of it, even before the base class constructor is called , and secondly, it will be copied into all example constructors .

The first point is very important (yes, it can be asked about in an interview and it can come in handy in real applications). For example, if someone decides to call a virtual method in the constructor of the base class, then some of the fields will be initialized, and some will not. It is easy to guess that the fields initialized at the place of the declaration will already be valid, while other fields will contain default values.

Yes, yes, yes, calling virtual methods in the constructors of the base class is bad, but in reality this happens and you need to understand what will happen in runtime in this case.

Difference in case of static fields and constructors

With static constructors, things are a little more complicated and simpler at the same time. From the point of view of inheritance, the method of initializing static fields does not interfere in any way with calling the static constructor of the base class. No way. There, in general, the process of calling static constructors differs from instance ones. For example, when creating an instance of an inheritor, the static constructor of the inheritor is called first, and then the static constructor of the base class. And if the static method of the inheritor twitches, then the static constructor of the base class will not be called at all by the machine (it will be called only if the static method of the inheritor somehow twitches the base type).

But the difficulty arises with exactly when the static constructor will be called.

As @Qwertiy already wrote, the presence or absence of a static constructor in a class affects when this constructor will be called. The presence of a static constructor leads to the generation of a strange special flag, which will then tell the CLR that it is possible to be more relaxed about the time of calling the static constructor, and this can now be done not right before the first call, but, for example, before calling the method in which this call is going on.

Potentially, this can affect the efficiency of the application, since now the check will be done once, rather than thousands of times, provided that the first call is in the loop from 0 to 1000.

Conclusion

The field initializer (static and not) is sugar, but it can be bitter if you don't understand what its overuse leads to.

I usually use the following rule of thumb. For instance fields: you need to initialize the field with a constructor argument – (no options) use the constructor; otherwise, the field initiator. In the case of static fields: in the overwhelming majority of cases, I use an initializer. If there is a lot of code, then I highlight the method.

If I need to use a static constructor to set the initialization order or change the semantics of a type's initialization, then I add a huge comment that says why this piece of code needs to be treated very carefully.

If I need to use an initializer for the instance fields so that the initialization goes before the base class constructor is called, then I'll refactor the code so that it isn't necessary. For example, I highlight the factory method. If this behavior is really needed, then a two-page commentary is needed, which explains why this is necessary and why other options do not work.

Scroll to Top