奇怪的结构构造器编译方法

本文关键字:编译 方法 构造器 结构 | 更新日期: 2023-09-27 18:05:09

结构不能包含显式无参数构造函数。如:

public struct Person
{
    public string Name { get; }
    public int Age { get; }
    public Person() { Name = string.Empty; Age = 0; }
}

但是,这是允许的:

public struct Person
{
    public string Name { get; }
    public int Age { get; }
    public Person(string name = null, int age = 0) { Name = name; Age = age; }
}

知道为什么吗?有什么不好的理由吗?

奇怪的结构构造器编译方法

原始答案(见下文更新):

第二个是允许的,因为它不是无参数的。但是我不会在这里使用可选参数,因为它非常令人困惑——如果你调用new Person()你的构造函数将不会被执行(如果你替换了默认值而不是null和0,你可以检查它):

public struct Person
{
    public string Name { get; }
    public int Age { get; }
    public Person(string name = "Bob", int age = 42)
    {
        Name = name;
        Age = age;
    }
}

所以new Person()default(Person)是一样的,两者都将使用initobj MSIL指令而不是调用构造函数。

那么,如果可以为结构定义默认构造函数,为什么会有问题呢?考虑下面的例子:

private void Test()
{
    Person p1 = new Person(); // ok, this is clear, use ctor if possible, otherwise initobj
    Person p2 = default(Person); // use initobj
    Person p3 = CreateGeneric<Person>(); // and here?
    Person[] persons = new Person[100]; // do we have initialized persons?
}
public T CreateGeneric<T>() where T : new()
{
    return new T();
}

所以c#中的结构不允许使用真正的无参数构造函数(但在CLR中是支持的)。实际上,无参数构造函数是计划在c# 6.0中引入的;然而,它引起了许多兼容性问题,最后这个特性从最终版本中删除了。


更新2022:

从c# 10开始,实际上支持无参数结构构造函数,因为结构记录带有字段初始化项。来自文档:

如果一个记录结构既不包含主构造函数,也不包含任何实例构造函数,并且该记录结构有字段初始化式,编译器将合成一个公共的无参数实例构造函数。

但现在似乎一切都是显而易见的。让我们回顾一下上面的例子:

  1. new Person():即使这种情况也有点令人困惑。显然,如果您有一个无参数构造函数,它将调用该构造函数。但不像在类的情况下,如果没有无参数构造函数,它将使用initobj指令,即使有一个构造函数重载只有可选参数(OP的第二个例子)。
  2. default(Person):这是明确的,initobj指令将被使用
  3. CreateGeneric<Person>():事实证明,它还调用了无参数结构构造函数…好吧,至少第一次。但是当目标是。net框架时,它无法为后续调用调用构造函数。
  4. new Persons[100]:没有构造函数调用(这实际上是预期的)

这个特性还有一些意想不到的含义:

  • 如果一个字段有初始化式,并且只有参数化的构造函数,那么new MyStruct()不会初始化该字段。
  • 参数默认值初始化(如void Method(Person p = new Person()))也无法调用无参数构造函数。与此同时,如果存在无参数构造函数,则通过发出CS1736来"修复";否则,允许并且只表示default
  • 如果你的目标是。net框架,Activator.CreateInstance(Type)也工作不正确(行为方式与上面的CreateGeneric<Person>()相同):构造函数只在第一次调用。
  • Expression.New(Type)也工作不正确,不仅在。net框架,但在所有的。net/core平台之前的版本6.0

故事还没有结束。现在看来,自动合成无参数构造函数将被删除,这使得上面的第一个要点不合法,这将是c# 10的一个重大变化。但也有进一步开放的问题,如这个,这也需要对语言规范进行一些更改。

结构体的无参数构造函数将更容易创建被认为是邪恶的可变结构体。

var person = new Person();
person.Age = 35;
...

我相信还有其他原因,但一个主要的痛苦是,因为它们在传递时被复制,所以很容易更改错误的结构,因此更容易犯难以诊断的错误。

public void IncreaseAge(Person p)
{
    p += 1; // does not change the original value passed in, only the copy
}

知道为什么吗?

这个构造函数不是隐式无参数的——它有两个参数。

有什么不好的理由吗?

这是一个原因:代码可能很难推理。

可以期望如果你写:

var people = new Person[100];

数组中的所有People都具有可选参数的默认值,但这是不正确的

可选参数的值被硬编码到调用代码中。

如果稍后您选择更改默认值,则所有编译后的代码将保留其编译时使用的值。