Can you have default parameters on a readonly struct in c#?

Counter-intuitively you can't have all default parameters or an explicit parameterless constructor on a struct, this is not limited to readonly struct.

Unlike a class, a struct is value-type and is not required to have a constructor, so in your case you aren't providing any parameters at all, hence the constructor never gets called.

As noted in the documentation

Limitations with the design of a structure type

When you design a structure type, you have the same capabilities as with a class type, with the following exceptions:

You can't declare a parameterless constructor. Every structure type already provides an implicit parameterless constructor that produces the default value of the type.

When you don't supply parameters, the generated IL will call

OpCodes.Initobj Field

Initializes each field of the value type at a specified address to a null reference or a 0 of the appropriate primitive type.

Furthermore

Unlike Newobj, initobj does not call the constructor method. Initobj is intended for initializing value types, while newobj is used to allocate and initialize objects.

In contrast, in the following case it will. Defaults will be initalized the way you expect.

var asd = new ReadonlyStruct(2);

The following generated IL will be

newobj instance void ReadonlyStruct::.ctor(int32, uint8, bool)

OpCodes.Newobj Field

The newobj instruction allocates a new instance of the class associated with ctor and initializes all the fields in the new instance to 0 (of the proper type) or null references as appropriate. It then calls the constructor ctor with the given arguments along with the newly created instance. After the constructor has been called, the now initialized object reference (type O) is pushed on the stack.

In short, you may need to rethink your problem or use a static create method.


Optional parameters in C# always work this way. For any given call, if there are two overloads that are applicable, and one requires the compiler to use the default value as the argument for a parameter and the other doesn't, the one that doesn't require it "wins". Here's a simple example of that:

using System;

class Test
{
    static void Main()
    {
        // Prints "Parameterless"
        Foo();
    }

    static void Foo() =>
        Console.WriteLine("Parameterless");

    static void Foo(int x = 0) =>
        Console.WriteLine("Parameterized");
}

Next, remember that every struct implicitly has a parameterless constructor. From the C# 5 ECMA standard, section 16.4.9:

Unlike a class, a struct is not permitted to declare a parameterless instance constructor. Instead, every struct 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.

Put those two facts together, and the behaviour you're seeing makes perfect sense. The parameterless constructor is used in preference to the parameterized one, when you don't specify any argument. You can see exactly the same thing with a class, where the parameterless constructor is explicit:

using System;

class Test
{
    static void Main()
    {
        // Prints "Parameterless"
        Foo f = new Foo();
    }
}

class Foo
{
    public Foo()
    {
        Console.WriteLine("Parameterless");
    }

    public Foo(int x = 0, int y = 0)
    {
        Console.WriteLine("Parameterized");
    }
}

So what you're seeing is the C# language being entirely consistent. It may not be the behaviour you want, but I believe it's behaviour that makes perfect sense.

Note that if you specify any arguments, e.g. new Foo(x: 0), then the parameterized overload will be chosen, and the defaults will be used for any parameter without a corresponding argument.

As you've said elsewhere, the way to work around this is to declare a static method with optional parameters, that doesn't have a parameterless overload. That way, the same method will be called whichever arguments are provided - and that can then call the constructor.

Tags:

C#

.Net