在.NET中,值类型(C#struct
)不能包含没有参数的构造函数.根据这篇文章,这是CLI规范要求的.会发生什么,对于每个值类型,都会创建一个默认构造函数(由编译器?),它将所有成员初始化为零(或null
).
为什么不允许定义这样的默认构造函数?
一个微不足道的用途是有理数:
public struct Rational { private long numerator; private long denominator; public Rational(long num, long denom) { /* Todo: Find GCD etc. */ } public Rational(long num) { numerator = num; denominator = 1; } public Rational() // This is not allowed { numerator = 0; denominator = 1; } }
使用当前版本的C#,默认的Rational 0/0
并不是那么酷.
PS:默认参数是否有助于解决C#4.0或者是否会调用CLR定义的默认构造函数?
Jon Skeet回答道:
要使用您的示例,当有人执行时您希望发生什么:
Rational[] fractions = new Rational[1000];它应该通过你的构造函数1000次?
当然应该,这就是我首先编写默认构造函数的原因.当没有定义显式默认构造函数时,CLR应该使用默认的归零构造函数; 这样你只需支付你使用的费用.然后,如果我想要一个1000个非默认Rational
的容器(并希望优化1000个结构),我将使用一个List
而不是一个数组.
在我看来,这个原因并不足以阻止默认构造函数的定义.
注意:下面的答案是在C#6之前很长一段时间编写的,它计划引入在结构中声明无参数构造函数的能力 - 但它们仍然不会在所有情况下被调用(例如,用于数组创建) (最后)此功能未添加到C#6).
编辑:由于格劳恩沃尔夫对CLR的洞察力,我编辑了下面的答案.
CLR允许值类型具有无参数构造函数,但C#不具有参数构造函数.我相信这是因为它会引入一种期望,即构造函数不会被调用.例如,考虑一下:
MyStruct[] foo = new MyStruct[1000];
CLR能够通过分配适当的内存并将其归零来非常有效地完成此任务.如果它必须运行MyStruct构造函数1000次,那将效率低得多.(事实上,它没有-如果你这样做有一个参数的构造函数,当你创建一个数组它不会运行,或当你有一个未初始化的实例变量.)
C#中的基本规则是"任何类型的默认值都不能依赖于任何初始化".现在他们可以允许定义无参数构造函数,但是不需要在所有情况下执行构造函数 - 但这会导致更多的混淆.(或至少,所以我相信这个论点.)
编辑:要使用您的示例,当有人执行时您希望发生什么:
Rational[] fractions = new Rational[1000];
它应该通过你的构造函数1000次?
如果没有,我们最终会得到1000个无效的理由
如果确实如此,那么如果我们要用真实值填充数组,那么我们可能会浪费大量的工作.
编辑:(回答更多问题)无参数构造函数不是由编译器创建的.就CLR而言,值类型不必具有构造函数 - 尽管事实证明它可以在IL中编写它.当您new Guid()
在C#中写入" "时,如果您调用普通构造函数,则会发出不同的IL.有关该方面的更多信息,请参阅此SO问题.
我怀疑框架中没有无参数构造函数的任何值类型.毫无疑问NDepend可以告诉我,如果我问得好吗...... C#禁止它的事实是一个足够大的提示让我认为这可能是一个坏主意.
结构是值类型,值类型一旦声明就必须具有默认值.
MyClass m; MyStruct m2;
如果你声明上面的两个字段而没有实例化,那么打破调试器,m
将是null但m2
不会.鉴于此,无参数构造函数没有任何意义,实际上结构上的所有构造函数都是赋值,事物本身只是通过声明它就已存在.实际上m2可以很高兴地用在上面的例子中并调用它的方法,如果有的话,它的字段和属性被操纵!
虽然CLR允许它,但C#不允许结构具有默认的无参数构造函数.原因是,对于值类型,编译器默认情况下既不生成默认构造函数,也不生成对默认构造函数的调用.因此,即使您碰巧定义了默认构造函数,也不会调用它,这只会让您感到困惑.
为避免此类问题,C#编译器不允许用户定义默认构造函数.并且因为它不生成默认构造函数,所以在定义字段时无法初始化字段.
或者最大的原因是结构是值类型,值类型由默认值初始化,构造函数用于初始化.
您不必使用new
关键字实例化您的结构.它反过来像一个int; 你可以直接访问它.
结构不能包含显式无参数构造函数.Struct成员会自动初始化为其默认值.
结构的默认(无参数)构造函数可以设置与全零状态不同的值,这将是意外行为.因此,.NET运行时禁止结构的默认构造函数.
您可以创建一个初始化并返回默认"有理"数字的静态属性:
public static Rational One => new Rational(0, 1);
并使用它像:
var rat = Rational.One;
更简短的解释:
在C++中,struct和class只是同一枚硬币的两面.唯一真正的区别是,一个是默认公开,另一个是私人.
在.NET中,结构和类之间存在更大的差异.主要的是struct提供了值类型语义,而class提供了引用类型语义.当您开始考虑此更改的含义时,其他更改也会开始变得更有意义,包括您描述的构造函数行为.