简体   繁体   中英

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

It appears that default parameters do not work on a readonly struct in c#. Am I misunderstanding something?

   public readonly struct ReadonlyStruct
   {
      public ReadonlyStruct(int p1 = 1, byte p2 = 2, bool p3 = true)
      {
         P1 = p1;
         P2 = p2;
         P3 = p3;
      }

      public int P1 { get; }
      public byte P2 { get; }
      public bool P3 { get; }
   }

Instantiating an instance of this struct using var test = new ReadonlyStruct(); does not appear to honor the default values. What am I doing wrong?

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, eg 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2025 STACKOOM.COM