1

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.

Evgeniy Terekhin
  • 596
  • 3
  • 10
  • 1
    Wild speculation: I think it's a difference in how these objects are constructed. In a class, default values will be assigned to each field before the constructor is called. If I recall correctly, this isn't the case in a struct: the constructor is expect to assign all of the values before it completes. Therefore, in the same way that a derived class must call its base constructor before its own constructor (done automatically if base constructor requires no parameters), the compiler is worried that `Limit` will access an uninitialized value. – ProgrammingLlama May 11 '20 at 07:07
  • 1
    Without looking at the specification, etc. I would guess the parameterless constructor assigns default values to the fields therefore `Limit` accessing an uninitialized value is no longer a concern. Please not that this is my guess as to whats happening. Hopefully someone more familiar with this can provide a concrete answer as opposed to my wild speculation! :) – ProgrammingLlama May 11 '20 at 07:09
  • https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/structs#constructors – JohanP May 11 '20 at 07:18
  • https://stackoverflow.com/questions/2534960/struct-constructor-fields-must-be-fully-assigned-before-control-is-returned-to – Steve May 11 '20 at 07:29
  • 1
    _"Why will I get this error if I use structure and not if I use class"_ -- because reference types implicitly initialize all fields, while value types explicitly _do not_. The C# specification requires that all fields of a value type be explicitly initialized before the new object can be used, which precludes _any_ use of the `this` object. As for why the specification says this, it mainly is a matter of practicality: reference types are allocated in a way that ensures the whole memory block for the object is zeroed out, effectively initializing the fields, while value types are not. – Peter Duniho May 14 '20 at 07:20

3 Answers3

1

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#.

AlexWei
  • 1,093
  • 2
  • 8
  • 32
  • Thanks for the detailed study, I did not know that the initobj instruction for creating the structure was not generated by default. Also, I did not know that the stack does not clear after the frame is destroyed. – Evgeniy Terekhin May 16 '20 at 04:44
1

Fundamentally, classes are always allocated on the heap, heap memory is always zero initialised by the runtime before the constructor is executed. Structs may be allocated on the stack, where memory is left uninitialised to improve performance. So the fields of a struct have semantics similar to local variables. The compiler enforces this rule to ensure that all struct fields must be assigned before they can be used.

Jeremy Lakeman
  • 9,515
  • 25
  • 29
-1

You can not use methods of object, that is not initialized. Every nonstatic method call in C# is a object using. So calling Limit(body) equals this.Limit(body). To make your code copiled you should change Limit method to static.

Nikolai
  • 656
  • 6
  • 19
  • 2
    Except that changing `struct` to `class` in OP's example would cause perfectly valid working code. I know what you're saying, but the answer seems to be lacking a lot of details. – ProgrammingLlama May 11 '20 at 07:20