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?
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With