4
struct vvvv 
{
    public int j = 8;

    //public vvvv() { } error    
}

class cccc 
{
    public int f = 8;
}

In the struct if I comment out the constructor the compiler tells me that the field j will not be initialize until I specify an EXPLICIT constructor while in the case of a class, the initializer will perfectly run before the IMPLICIT constructor's body is run.

I mean the struct also has an implicit constructor. Why do I have to specify an explicit one for the initializer to run? Isn't an implicit constructor enough?

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Erik_JI
  • 287
  • 1
  • 7

1 Answers1

4

TL;DR

C# compiler does not generate default ctor for structs, so if there is non declared - the field initializers will not run, so they are essentially pointless and to prevent such unexpected behaviour compiler does not allow field initializers without declaring ctor (not necessarily default one).

The longer rant:

First of all as this answer explains (in the edit part):

The parameterless constructor isn't created by the compiler. Value types don't have to have constructors as far as the CLR is concerned - although it turns out it can if you write it in IL. When you write new Guid() in C# that emits different IL to what you get if you call a normal constructor.

I.e. the following:

Console.WriteLine(typeof(Struct).GetConstructors().Length); // prints 0
Console.WriteLine(typeof(Class).GetConstructors().Length); // prints 1

struct Struct
{
}

class Class
{
}

(note that for struct Struct{ public Struct() { } } the code above will print 1)

So one of the reasons can be performance and backward compatibility.

Also there are some notes on the topic in the LDM:

We feel that a simple rule here would be to mirror the observed behavior with reference types: if there is no explicit constructor for a struct type, we will synthesize that constructor. If that constructor happens to be empty (as it would be today in a constructorless struct type because field initializers aren't yet supported) we optimize that constructor away. If a constructor is explicitly declared, we will not synthesize any constructor for the type, and field initializers will not be run by new S() unless the parameterless constructor is also explicitly declared. This does have a potential pit of failure where users would expect the parameterless constructor to run the field initializers, but synthesizing a parameterless constructor would have bad knock-on effects for record structs with a primary constructor: what would the parameterless constructor do there? It doesn't have anything it can call the primary constructor with, and would result in confusing semantics.

Which resulted in the following added to the drat specification:

An error is reported if a struct has field initializers and no declared instance constructors since the field initializers will not be run.

And

If no parameterless instance constructor is declared, the struct (see §15.4.9) ...

implicitly has a parameterless instance constructor which always returns the value that results from setting all value type fields to their default value and all reference type fields to null.

Which results in this quite noteworthy behaviour:

Console.WriteLine(new V().j);  // prints 8
Console.WriteLine(new V1().j); // prints 0 - no custom parameterless ctor, no init

struct V
{
    public V() { }
    public V(int _) {}

    public int j = 8;
}

struct V1
{
    public V1(int _) { }

    public int j = 8;
}

P.S.

Note that it seems that at least some versions of .NET 6 SDK work incorrectly breaking the spec above, i.e. :

Console.WriteLine(new V().j);

struct V
{
    public int j = 8;
}

Not only compiles but prints 8:

enter image description here

Though it seems that this was fixed in the latest (at the moment of writing) 6.0.400 SDK.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • 1
    Thank you @Guru Stron . I feel kind of ashamed to ask this but. what is this? int _ in the constructor. What does it actually mean? – Erik_JI Aug 11 '22 at 20:54
  • 2
    @Erik_JI this is so called [discard](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards) which in current case denotes an unused parameter of `int` type. – Guru Stron Aug 11 '22 at 21:02
  • 1
    Okay @Guru Stron, after doing some testing I can say this. Please correct me if I am wrong. _If_ there is a parameterless constructor which is only _implicitly_ defined, it ONLY Defaults fields to their default values. BUT if I define the parameterless constructor _explicitly_ only then my field initializers will run. Am I correct? – Erik_JI Aug 11 '22 at 21:10
  • 2
    @Erik_JI in the nutshell yes, with small caveat - the implicit de facto does not even exists for structs from CLR point of view. – Guru Stron Aug 11 '22 at 21:15
  • 1
    Thank you so much @Guru Stron. Alright so if the implicit de facto does not even exists for structs from CLR point of view then how are the values defaulted? As far as I know the values are defaulted like right before the body of a constructor is run. And the field initializers are also run the same way, aren't they? – Erik_JI Aug 11 '22 at 21:18
  • 2
    @Erik_JI the answer in the first link mentions [another one](https://stackoverflow.com/a/204009/2501279) which explains it - basically default initialization of stuct is just wiping out the needed space and that's it. See the `initobj` part. But this is knowledge which is not needed like 99,(9)% of the time. – Guru Stron Aug 11 '22 at 21:22
  • 1
    Okay @Guru Stron , thank you, but I have to ask one more thing. Let's say I only define one Parametered constructor and I have like a filed in my struct `int f;` . In this case the same "wiping" is used to default my `f` field right? – Erik_JI Aug 11 '22 at 21:28
  • 1
    Overall, thanks to you @Guru, I got what I need. But I do not get why is that that in case of classes the implicitly defined parameterless constructor will run the field initializers but in case of structs not. – Erik_JI Aug 11 '22 at 21:32