Search code examples
c#structconstructorinitializationclr

The 'this' object cannot be used before all of its fields are assigned to. Why do not class constructors have such restriction?


I deal with a problem in this case:

public struct RequestLog
{
    public RequestLog(string body) // :this() - solution to the problem
    {
        Body = body != null ? Limit(body) : null; // The 'this' object cannot be used before all of its fields are assigned to
    }

    public readonly string Body;

    private string Limit(string str, int limit = 100) =>
        str.Length > limit
            ? str.Substring(0, limit)
            : str;
}

I know if I call a parameterless struct constructor before calling my constructor I won't have this problem.

But if I change type RequestLog from a struct to class, code would be correct.

My question: Why will I get this error if I use structure and not if I use class. If I'm right, struct parameterless constructor initializes all the fields, maybe I can paraphrase question :

Why should I to explicitly initialize all fields when creating a structure and shouldn't do this when creating a class.


Solution

  • First of all, try not to make an analogy between Class and Struct in C#. They are quite different.

    Let's say I got the following code:

     class MyClass
        {
            private int a = 0;
        }
        struct MyStruct
        {
            //private int a = 0; => this is not allowed
        }
        class Program
        {
            static void Main(string[] args)
            {
                var aCls=new MyClass();
                var aStruct=new MyStruct();
            }
        }
    

    When you check the il code, class MyClass{...} generated the following code:

    .class private auto ansi beforefieldinit
      Ctors.MyClass
        extends [mscorlib]System.Object
    {
    
      .field private int32 a
    
      .method public hidebysig specialname rtspecialname instance void
        .ctor() cil managed
      {
        .maxstack 8
    
        // [11 9 - 11 27]
        IL_0000: ldarg.0      // this
        IL_0001: ldc.i4.0
        IL_0002: stfld        int32 Ctors.MyClass::a
        IL_0007: ldarg.0      // this
        IL_0008: call         instance void [mscorlib]System.Object::.ctor()
        IL_000d: nop
        IL_000e: ret
    
      } // end of method MyClass::.ctor
    } // end of class Ctors.MyClass
    

    struct MyStruct{} generated the following code:

    .class private sealed sequential ansi beforefieldinit
      Ctors.MyStruct
        extends [mscorlib]System.ValueType
    {
    } // end of class Ctors.MyStruct
    

    Based on observatons above:
    1. MyClass genenerated a parameterless constructor.
    2. The a=0 assignment will be put in the parameterless constructor.
    3. There is no auto generated parameterless constructor for MyStruct
    4. The a=0 assignment can not be directly put in MyStruct. It won't compile.

    Let's see what will happen when we new them:

        IL_0001: newobj       instance void Ctors.MyClass::.ctor()
        IL_0006: stloc.0      // aCls
    
        // [22 13 - 22 40]
        IL_0007: ldloca.s     aStruct
        IL_0009: initobj      Ctors.MyStruct
    

    The MyClass will be ctored by newobj while the MyStruct will be ctored by initobj.

    Why call this() solved? Actullay, it generated the following code:

        IL_0000: ldarg.0      // this
        IL_0001: initobj      Ctors.RequestLog
    

    I think we should not consider it parameterless constructor. It works very different from what you would expect a parameterless constructor do. It is just syntax sugar to clear state for value type.

    Why should I to explicitly initialize all fields for struct? Simply Safety. More details can be found in the following quote:

    Note Strictly speaking, value type fields are guaranteed to be 0/null when the value type is a field nested within a reference type. However, stack-based value type fields are not guaranteed to be 0/null . For verifiability, any stack-based value type field must be written to prior to being read. If code could read a value type’s field prior to writing to the field, a security breach is possible. C# and other compilers that produce verifiable code ensure that all stack-based value types have their fields zeroed out or at least written to before being read so that a verification exception won’t be thrown at run time. For the most part, this means that you can assume that your value types have their fields initialized to 0 , and you can completely ignore everything in this note.

    by Jeffrey Richter CLR via C#.