在C#中,只读结构体可以有默认参数吗?

8

在C#中,似乎默认参数不适用于只读结构。我有什么误解吗?

   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; }
   }

使用var test = new ReadonlyStruct();实例化此结构体的实例似乎并不遵循默认值。我做错了什么吗?

2个回答

5
逆直觉的是,你不能在一个struct上拥有所有默认参数或者显式无参构造函数,这不仅限于readonly struct
与类不同,struct是值类型,不需要有构造函数,所以在你的情况下,你根本没有提供任何参数,因此构造函数从未被调用。
正如文档中所述,
结构类型的设计限制
当你设计一个结构类型时,你有与类类型相同的功能,但以下功能除外:
你不能声明一个无参构造函数。每个结构类型已经提供了一个隐式的无参构造函数,它产生类型的默认值。
当你没有提供参数时,生成的IL代码将调用 OpCodes.Initobj Field 初始化指定地址处的值类型的每个字段到null引用或适当基元类型的0。
另外,
与Newobj不同,initobj不调用构造函数方法。Initobj用于初始化值类型,而newobj用于分配和初始化对象。
相反,在以下情况下会。默认值将按照您预期的方式初始化。
var asd = new ReadonlyStruct(2);

以下生成的 IL 将会:
newobj instance void ReadonlyStruct::.ctor(int32, uint8, bool)

OpCodes.Newobj Field

newobj指令用于分配与构造函数(ctor)关联的类的新实例,并将新实例中的所有字段初始化为0(适当类型的)或null引用。然后,它使用给定参数调用构造函数ctor并传递新创建的实例。在调用构造函数之后,初始化的对象引用(类型O)被推送到堆栈上。

简而言之,您可能需要重新考虑问题或使用静态create方法。


@AlexUT 我同意,这是一个棘手的情况,但编译器相信你想要一个默认类型。 - TheGeneral
我认为更准确的说法是,虽然你可以有一个所有参数都是可选的构造函数,但如果你不提供任何参数,仍将使用默认构造函数 - 就像在类中如果你有一个显式的无参构造函数和带参数的构造函数一样。 - Jon Skeet
@AlexUT:它确实有效,与其他地方的可选参数完全相同:如果有一个重载不使用给定调用的默认值,则优先使用该重载,而不是需要给出可选参数的默认值的重载。基本上,编译器会像结构体始终有一个无参构造函数一样处理。 - Jon Skeet
@JonSkeet 是的,你说得对,“cant”的使用有点误导和限制性。 - TheGeneral
决定添加一个答案,而不仅仅在评论中进行澄清... - Jon Skeet
显示剩余2条评论

3

C#中的可选参数总是以这种方式工作。对于任何给定的调用,如果有两个适用的重载,并且其中一个需要编译器将默认值用作参数,而另一个则不需要,则不需要使用默认值的"赢得"。以下是一个简单的例子:

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");
}

首先,请记住每个结构体都具有隐式的无参构造函数。来自C# 5 ECMA标准,第16.4.9节:
“与类不同,结构体不允许声明一个无参数实例构造函数。相反,每个结构体都隐式地具有一个无参数实例构造函数,该函数始终返回将所有值类型字段设置为其默认值并将所有引用类型字段设置为 null 的值。”
将这两个事实结合起来,你所看到的行为就很容易理解了。当没有指定任何参数时,将使用无参构造函数而不是带参数的构造函数。在类中也可以完全相同地看到这一点,其中无参构造函数是显式的:
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");
    }
}

所以你看到的是C#语言完全一致的行为。可能不是你想要的行为,但我相信这种行为是非常合理的。

请注意,如果您指定了任何参数,例如new Foo(x: 0),那么将选择参数化的重载,并且默认值将用于任何没有对应参数的参数。

正如你在其他地方所说的那样,解决这个问题的方法是声明一个具有可选参数的静态方法,它没有无参重载。这样,无论提供哪些参数,都会调用同一个方法 - 然后该方法可以调用构造函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接