Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Struct parameterless constructor and field initializers in C# 10

C# 10 makes it possible to declare a parameterless constructor and field initializers for a struct. Here is what happens when you declare field initializers but no explicit parameterless constructor, according to the language reference:

If you don't declare a parameterless constructor explicitly, a structure type provides a parameterless constructor whose behavior is as follows:

  • If a structure type has explicit instance constructors or has no field initializers, an implicit parameterless constructor produces the default value of a structure type, regardless of field initializers (…).

  • If a structure type has no explicit instance constructors and has field initializers, the compiler synthesizes a public parameterless constructor that performs the specified field initializations (…).

The following code outputs 1:

struct S {
    public int X = 1;
}

class Program {
    static void Main() { System.Console.WriteLine(new S().X); }
}

The following code outputs 0:

struct S {
    public int X = 1;
    public S(int x) { X = x; }
}

class Program {
    static void Main() { System.Console.WriteLine(new S().X); }
}

This perfectly agrees with the spec cited above, but I find this illogical, so my question is: WHY? Why does the implicit parameterless constructor have different behavior depending on whether there are other instance constructors? Why cannot it always take field initializers into account, even if there are other instance constructors?

like image 496
Bartosz Avatar asked Jan 27 '26 20:01

Bartosz


1 Answers

The implicit parameterless constructor on struct will only use its base default value and won't honor your defined defaults. IMO, the .NET Analyzers should do a better job of letting you know when you've written something that won't work.

For example, in C# 12.0 (.NET 8.0.402) the following will compile without errors or warnings, but won't actually instantiate new() with the expected 10 and "my string" values.

// IMPLICIT parameterless constructor
public struct MyStruct
{
    public int Param1 { get; }
    public string Param2 { get; }

    // confusing: these user-defined defaults are
    // ignored when you try to instantiate with `new()`
    public MyStruct(int param1 = 10, string param2 = "my string")
    {
        Param1 = param1;
        Param2 = param2;
    }
}

In this case, MyStruct foo = new(42); and MyStruct foo = new(10,"my string"); will still work as expected, but your parameterless instantiation MyStruct foo = new(); will not.

...
[TestMethod]
public void MyStruct_ImplicitCtor()
{
    MyStruct myStruct = new();

    Console.WriteLine($"P1: {myStruct.Param1} | P2: {myStruct.Param2}");
    // outputs type default only:  P1: 0 | P2: ""

    // failing tests
    Assert.AreEqual(10, myStruct.Param1);
    Assert.AreEqual("my string", myStruct.Param2);
}

For those seeking to have new() work as you'd expect, you'll have to create an explicitly defined parameterless constructor.

// EXPLICIT parameterless constructor
public struct MyStruct
{
    public int Param1 { get; }
    public string Param2 { get; }

    public MyStruct(int param1, string param2 = "my string")
    {
        Param1 = param1;
        Param2 = param2;
    }

    // ADD THIS
    // explicit parameterless constructor
    public MyStruct() : this(param1: 10, param2: "my string") { }
}

With the explicitly defined parameterless constructor, this now works:

...
[TestMethod]
public void MyStruct_ExplicitCtor()
{
    MyStruct myStruct = new();

    Console.WriteLine($"P1: {myStruct.Param1} | P2: {myStruct.Param2}");
    // outputs your defaults:  P1: 10 | P2: "my string"

    // passing tests
    Assert.AreEqual(10, myStruct.Param1);
    Assert.AreEqual("my string", myStruct.Param2);
}
like image 66
Dave Skender Avatar answered Jan 29 '26 09:01

Dave Skender



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!