在.NET中,值类型(C# struct
)不能包含没有参数的构造函数 . 根据this post,这是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<Rational>
而不是数组 .
在我看来,这个原因并不足以阻止默认构造函数的定义 .
10 回答
注意:下面的答案是在C#6之前编写的,它计划引入在结构中声明无参数构造函数的能力 - 但它们仍然不会在所有情况下被调用(例如,用于数组创建)(最后)这个功能was not added to C# 6) .
编辑:由于格劳恩沃尔夫对CLR的洞察力,我编辑了下面的答案 .
CLR允许值类型具有无参数构造函数,但C#不具有参数构造函数 . 我相信这是因为它会引入一种期望,即构造函数不会被调用 . 例如,考虑一下:
CLR能够通过分配适当的内存并将其归零来非常有效地完成此任务 . 如果它必须运行MyStruct构造函数1000次,那将效率低得多 . (事实上,它没有 - 如果你有一个无参数构造函数,它在创建数组时没有运行,或者你有一个未初始化的实例变量 . )
C#中的基本规则是"the default value for any type can't rely on any initialization" . 现在他们可以允许定义无参数构造函数,但是不需要在所有情况下执行构造函数 - 但这会导致更多的混淆 . (或至少,所以我相信这个论点 . )
编辑:要使用您的示例,当有人执行时您希望发生什么:
它应该通过你的构造函数1000次?
如果没有,我们最终会得到1000个无效的理由
如果确实如此,那么我们将重新填充具有实际值的数组 .
编辑:(回答更多问题)就CLR而言,无参数构造函数不必具有构造函数 - 尽管事实证明如果你在IL中编写它也可以 . 当您在C#中编写“
new Guid()
”时,如果调用普通构造函数,则会发出不同的IL . 有关该方面的更多信息,请参阅this SO question .我怀疑这不是一个坏主意 .
结构是值类型,值类型一旦声明就必须具有默认值 .
如果你声明上面的两个字段而没有实例化,那么打破调试器,
m
将为null,但m2
将不会 . 鉴于此,无参数构造函数没有任何意义,实际上结构上的所有构造函数都是赋值,事物本身只是通过声明它就已存在 . 实际上m2可以很高兴地用在上面的例子中并调用它的方法,如果有的话,它的字段和属性被操纵!虽然CLR允许它,但C#不允许结构具有默认的无参数构造函数 . 原因是,对于值类型,编译器默认情况下既不生成默认构造函数,也不生成对默认构造函数的调用 . 因此,即使您碰巧定义了默认构造函数,也不会调用它,这只会让您感到困惑 .
为避免此类问题,C#编译器不允许用户定义默认构造函数 . 并且因为它不生成默认构造函数,所以在定义字段时无法初始化字段 .
或者最重要的原因是结构是值类型和值类型由默认值初始化,构造函数用于初始化 .
您不必使用
new
关键字实例化您的结构 . 它反过来像一个int;你可以直接访问它 .结构不能包含显式无参数构造函数 . Struct成员会自动初始化为其默认值 .
结构的默认(无参数)构造函数可以设置与全零状态不同的值,这将是意外行为 . 因此,.NET运行时禁止结构的默认构造函数 .
您可以创建一个初始化并返回默认“有理”数字的静态属性:
并使用它像:
更简短的解释:
在C中,struct和class只是同一枚硬币的两面 . 唯一真正的区别是,一个是默认公开,另一个是私人 .
在.NET中,结构和类之间存在更大的差异 . 主要的是struct提供了值类型语义,而class提供了引用类型语义 . 当您开始考虑此更改的含义时,其他更改也会开始变得更有意义,包括您描述的构造函数行为 .
只是特殊情况吧 . 如果你看到0的分子和0的分母,假装它具有你真正想要的值 .
您无法定义默认构造函数,因为您正在使用C# .
结构可以在.NET中具有默认构造函数,但我不知道任何支持它的特定语言 .
这是我对无默认构造函数困境的解决方案 . 我知道这是一个迟到的解决方案,但我认为值得注意的是这是一个解决方案 .
忽略了我有一个名为null的静态结构的事实(注意:这仅适用于所有正象限),使用get; set;在C#中,您可以使用try / catch / finally来处理默认构造函数Point2D()未初始化特定数据类型的错误 . 我想这是一个难以捉摸的解决方案 . 这主要是为什么我加我的 . 使用C#中的getter和setter功能将允许您绕过此默认构造函数,并尝试捕获您未初始化的内容 . 对我来说这很好,对于其他人你可能想要添加一些if语句 . 因此,在您需要Numerator / Denominator设置的情况下,此代码可能会有所帮助 . 我只想重申,这个解决方案看起来不太好,从效率的角度来看可能效果更差,但是,对于来自旧版本C#的人来说,使用数组数据类型可以为您提供此功能 . 如果你只想要一些有用的东西,试试这个:
我没有看到相当于我要给的后期解决方案,所以在这里 .
使用偏移将值从默认值0移动到您喜欢的任何值 . 必须使用此属性而不是直接访问字段 . (也许有了可能的c#7功能,你最好定义属性范围的字段,这样它们就不会被代码直接访问了 . )
此解决方案适用于仅具有值类型的简单结构(无ref类型或可为空结构) .
这是不同的than这个答案,这种方法不是特别的套管,但它的使用偏移将适用于所有范围 .
以枚举为字段的示例 .
正如我所说,这个技巧可能在所有情况下都不起作用,即使struct只有值字段,只有你知道它是否适用于你的情况 . 试试看 . 但你得到了一般的想法 .