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 .