Richter writes in his book that if you initialize the inline fields, each constructor generates the same IL initialization code for these fields, and therefore he advises not to do everything inline, but to take everything into the standard constructor, but to call it from other constructors.

Is this relevant now? Why did microsoft do it that way?

    2 answers 2

    For starters, the technical side of the issue.

    if you initialize the inline fields, then in each constructor the same IL initialization code of these fields is generated

    Yes, it is (for non-delegating constructors, as suggested by @PetSerAl, that is, constructors that do not indicate this(...) instead of base(...) ). The modern version of C # compiles this class

     public class C { int X = 1; public C() { Console.WriteLine("C()"); } public C(int y) { Console.WriteLine("C(int)"); } } 

    in such an IL :

     .class public auto ansi beforefieldinit C extends [mscorlib]System.Object { // Fields .field private int32 X // Methods .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x2050 // Code size 24 (0x18) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldc.i4.1 IL_0002: stfld int32 C::X IL_0007: ldarg.0 IL_0008: call instance void [mscorlib]System.Object::.ctor() IL_000d: ldstr "C()" IL_0012: call void [mscorlib]System.Console::WriteLine(string) IL_0017: ret } // end of method C::.ctor .method public hidebysig specialname rtspecialname instance void .ctor ( int32 y ) cil managed { // Method begins at RVA 0x2069 // Code size 24 (0x18) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldc.i4.1 IL_0002: stfld int32 C::X IL_0007: ldarg.0 IL_0008: call instance void [mscorlib]System.Object::.ctor() IL_000d: ldstr "C(int)" IL_0012: call void [mscorlib]System.Console::WriteLine(string) IL_0017: ret } // end of method C::.ctor } // end of class C 

    We see a sequence of commands.

     IL_0000: ldarg.0 IL_0001: ldc.i4.1 IL_0002: stfld int32 C::X 

    which initializes the X field in both constructors.

    Why do not we take it into a separate private constructor, and do not call it yourself from each public constructor? (The private method is not appropriate, since it cannot initialize the readonly fields.) Technically, this is possible, but it is not the same.

    The difference begins where we have a base class with a non-trivial constructor. The point is that the initializers of the derived class are executed before the execution of the base constructor . But the constructor of the derived class itself is executed after the execution of the base constructor .

    Consider this code:

     public class B { public B() { Console.WriteLine("B constructor"); } } public class C : B { public static int Get1() { Console.WriteLine("Getting 1"); return 1; } int X = Get1(); public C() { Console.WriteLine("C Constructor"); } } 

    Constructor C in terms of IL-code is as follows:

     X = Get1(); B::ctor(); Console.WriteLine("C Constructor"); 

    and output accordingly

     Getting 1 B constructor C Constructor 

    If you put an X initialization into the C constructor, or into another, auxiliary class C constructor, it will be executed only after the end of the class B constructor. That is, the meaning of the code will be different.

    Worse, such a transformation is not always possible! For example, consider the class System.Exception .

     [Serializable] public class CustomException : Exception { readonly int ErrorCode; public CustomException(string message) : base(message) { } protected CustomException(SerializationInfo info, StreamingContext context) : base(info, context) { } } 

    It is impossible to render the common part to the “common” constructor, since the common constructor will not be able to call the correct basic constructor.


    The declaration of constructors can be a loophole so that all of them except one call other designers of the same class, while initializing the fields should be left where it is. For example, if you add a constructor

     public C(int x) : this() { Console.WriteLine("C(int) Constructor"); } 

    when we call it we get

     Getting 1 B constructor C Constructor C(int) Constructor 

    In this case, the initialization of the fields is present only in the code of the last constructor. However, this trick has the same drawbacks: it is not always possible from the “universal” constructor to call the necessary basic constructor!


    With the technical side of things, we seem to have figured out. Now about the real use.

    I personally would not bother, and did not write "as economical," but as it is more understandable. The gain from combining three or four initializers into a common method for a penny, and the code becomes more complex, and besides, you have to rewrite it without the need for a reader. In addition, you can assume that the compiler independently applied optimization to your code, known as method inlining :)


    Another argument for inline-initialization of fields: the fact that inline-initialization occurs before calling the parent type constructor reduces the chances of accessing an uninitialized object. Example (borrowed from a neighboring question ):

     class Parent { public Parent() { DoSomething(); } protected virtual void DoSomething() {} } class Child1 : Parent { private string foo = "FOO"; protected override void DoSomething() => Console.WriteLine(foo.ToLower()); } class Child2 : Parent { private string foo; public Child2() { foo = "FOO"; } protected override void DoSomething() => Console.WriteLine(foo.ToLower()); } 

    Why initializers run up to the base constructor call, written by Eric Lippert: Why Do Initializers Run In The Opposite Order As Constructors? Part One , Part Two .

      I really respect Richter for the depth of presentation and technical details, but there is one point in his books that worries me a lot. This is his advice about what is good and what is bad in matters of style or design. This is one of those tips.

      Is this relevant now?

      There are a number of tips, especially in matters of efficiency, which are very difficult to answer correctly, because the "correctness" strongly depends on your application. For example, a person who has been working on a fairly high-loaded application can give such advice “Never use LINQ”. The advice is quite reasonable if we are talking about critical areas of a high-loaded application, but it is very bad in the general case, since it is applicable only for poorly half-percent applications.

      The advice of the series "select constructors and do not use field-like initializers" sounds even funnier, since it applies to even fewer use cases.

      For the last couple of years I have been working on a high-loaded application, in which I really have to be wary of using LINQ, but I have never thought about whether I should transfer the code to a common constructor.

      This advice is also not relevant today, as it was not relevant 10 years ago. More precisely, it is relevant to the top ten people on the planet who are involved in the .net framework, corefx and, perhaps, developers of reusable components for unity.

      The idea of ​​the board is quite valid: the duplication of IL-code in each designer leads to an increase in the size of the assembly (which will increase its loading time) and to an increase in the length of the JIT compilation.

      Theoretically, the problem exists. Practically, when deciding whether or not to use field-like initialization, you need to build on the readability of the code, and not on the size of the generated IL-code.

      Why did microsoft do it that way? Immediately it is very difficult to come up with alternative simple and workable approaches. One could bite through all initialization and put it into a private method, mark it with a MethodImpl(AggressiveInline) attribute, and pull it from each constructor (*).

      I do not exclude that the authors of C # could even check this solution in practice and come to the conclusion that there is no difference and it is not worth it. Well, even if now there will be similar evidence, then it’s too late to change, and hardly anyone will, because backward compatibility. Yes, compiler builders are not eager to break the fundamental things of code generation, because there are many bodies in the world that inspect IL and will be very surprised to see the challenge of the left method there.

      (*) I disagree with u. @VladD about the fact that the private method does not work here. I’m not sure that the CLR actually enforces the rule that readonly fields should be initialized only in the constructor, so the private method generated by the compiler could well initialize these fields.

      • one
        I did not mean the optimization that the compiler could do, but the manual optimization: ideone.com/YI7MHI - VladD
      • 3
        I'm not sure that the CLR actually enforce the rule that readonly fields should be initialized only in the peverify constructor of a slightly different opinion: [IL]: Ошибка: [.\Test.dll : C::M][смещение 0x00000002] Невозможно изменить поле InitOnly за пределами .ctor. - PetSerAl
      • one
        OK. But since this is PEVerify, and not runtime, it means that peverify could be made so that this problem does not exist if a special initialization method is used that only the compiler can generate. In other words, this question is solved: it is not the guts of the CLR, it is already tulling around it. - Sergey Teplyakov
      • 2
        I used PEVerify because the System.Security.VerificationException: Операция может вызвать нестабильность при выполнении. message System.Security.VerificationException: Операция может вызвать нестабильность при выполнении. which throws out runtime is significantly less informative. If PEVerify stops displaying the error message, the error will not disappear anywhere. - PetSerAl
      • one
        I agree. In this case, in order for the proposed solution to work, it would be necessary to also run runtime. - Sergey Teplyakov