奇怪的结构构造器编译方法
本文关键字:编译 方法 构造器 结构 | 更新日期: 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开始,实际上支持无参数结构构造函数,因为结构记录带有字段初始化项。来自文档:
如果一个记录结构既不包含主构造函数,也不包含任何实例构造函数,并且该记录结构有字段初始化式,编译器将合成一个公共的无参数实例构造函数。
但现在似乎一切都是显而易见的。让我们回顾一下上面的例子:
-
new Person()
:即使这种情况也有点令人困惑。显然,如果您有一个无参数构造函数,它将调用该构造函数。但不像在类的情况下,如果没有无参数构造函数,它将使用initobj
指令,即使有一个构造函数重载只有可选参数(OP的第二个例子)。 -
default(Person)
:这是明确的,initobj
指令将被使用 -
CreateGeneric<Person>()
:事实证明,它还调用了无参数结构构造函数…好吧,至少第一次。但是当目标是。net框架时,它无法为后续调用调用构造函数。 -
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
都具有可选参数的默认值,但这是不正确的
可选参数的值被硬编码到调用代码中。
如果稍后您选择更改默认值,则所有编译后的代码将保留其编译时使用的值。