Why can't I define a default constructor for a struct in .NET?
在.net,value type(C #
为什么它disallowed到这样一个定义的违约constructor吗? P / < >
一个平凡的使用也为rational数: P / < >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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也
PS:将违约不能解决参数的帮助这对C #天窗;4,或将之定义为CLR -违约constructor叫进来吗? P / < >
乔恩:answered飞碟 P / < >
To use your example, what would you want to happen when someone did:
1Should it run through your constructor 1000 times?
我应该相信它,这就是为什么我写了"违约constructor在第一个地方。*为CLR应该使用的违约zeroing constructor违约constructor当没有显式定义的方式;你只支付给你用。然后,如果我想在集装箱的1000非违约
这个原因,在我的心中,也没有强到足够的预防中定义的constructor违约。 P / < >
注:下面的答案在c 6之前写了很长一段时间,它计划引入在结构中声明无参数构造函数的能力-但是在所有情况下它们仍然不会被调用(例如,对于数组创建)(in the end this feature was not added to c 6)。
编辑:由于Grauenwolf对CLR的洞察,我已经编辑了下面的答案。
clr允许值类型具有无参数的构造函数,但c不允许。我认为这是因为它会引入一种期望,即当构造函数没有参数时,它将被调用。例如,请考虑:
1 |
仅仅通过分配适当的内存并将其全部归零,CLR就能够非常有效地完成这项工作。如果它必须运行mystruct构造函数1000次,那么效率会低很多。(事实上,它没有——如果您确实有一个无参数的构造函数,那么在创建数组或有一个未初始化的实例变量时,它不会运行。)
C中的基本规则是"任何类型的默认值都不能依赖于任何初始化"。现在,它们可以允许定义无参数构造函数,但在所有情况下都不需要执行该构造函数——但这会导致更多的混乱。(或者至少,我相信这一论点是正确的。)
编辑:要使用您的示例,当有人这样做时,您希望发生什么:
1 |
它是否应该在构造函数中运行1000次?
- 如果没有,我们最终会得到1000个无效的理性
- 如果是这样,那么如果我们要用实际值填充数组,那么我们可能会浪费大量的工作。
edit:(回答更多问题)无参数构造函数不是由编译器创建的。就clr而言,值类型不必有构造函数——尽管如果您在il中编写它,结果是可以的。当你用C语言写"
我怀疑框架中没有任何带有无参数构造函数的值类型。毫无疑问,恩德彭德可以告诉我,如果我问得够好的话…事实上,C禁止这是一个足够大的提示,我认为这可能是一个坏主意。
结构是值类型,并且值类型在声明后必须具有默认值。
1 2 | MyClass m; MyStruct m2; |
如果您像上面一样声明两个字段而不实例化其中任何一个字段,那么中断调试器,
尽管clr允许,但c不允许结构具有无参数的默认构造函数。原因是,对于值类型,编译器在默认情况下既不生成默认构造函数,也不生成对默认构造函数的调用。所以,即使您碰巧定义了一个默认的构造函数,也不会调用它,这只会使您感到困惑。
为了避免这些问题,C编译器不允许用户定义默认的构造函数。因为它不生成默认的构造函数,所以在定义字段时不能初始化字段。
或者,主要原因是结构是值类型,值类型由默认值初始化,构造函数用于初始化。
不必用
结构不能包含显式无参数构造函数。结构成员自动初始化为其默认值。
结构的默认(无参数)构造函数可以设置不同于全零状态的值,这将是意外行为。因此,.NET运行时禁止结构的默认构造函数。
可以创建一个静态属性,该属性初始化并返回默认的"Rational"数字:
1 |
使用方法如下:
1 | var rat = Rational.One; |
简短解释:
在C++中,结构和类只是同一个硬币的两面。唯一真正的区别是,一个是默认的公开的,另一个是私有的。
在.NET中,结构和类之间的差别要大得多。主要的是,结构提供了值类型语义,而类提供了引用类型语义。当您开始考虑这个变更的含义时,其他的变更也开始变得更有意义,包括您描述的构造函数行为。
只是特例而已。如果你看到分子0和分母0,假装它有你真正想要的值。
我还没有看到等同于我要给出的迟交解决方案,所以这里是。
使用偏移将值从默认值0移动到您喜欢的任何值。这里必须使用属性,而不是直接访问字段。(也许有了可能的C 7特性,您可以更好地定义属性范围的字段,这样就可以保护它们不被代码直接访问。)
此解决方案适用于仅具有值类型(无引用类型或可为空的结构)的简单结构。
1 2 3 4 5 6 7 8 9 10 11 | public struct Tempo { const double DefaultBpm = 120; private double _bpm; // this field must not be modified other than with its property. public double BeatsPerMinute { get => _bpm + DefaultBpm; set => _bpm = value - DefaultBpm; } } |
这与这个答案不同,这种方法不是特殊的套管,而是使用偏移量,适用于所有范围。
以枚举为字段的示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public struct Difficaulty { Easy, Medium, Hard } public struct Level { const Difficaulty DefaultLevel = Difficaulty.Medium; private Difficaulty _level; // this field must not be modified other than with its property. public Difficaulty Difficaulty { get => _level + DefaultLevel; set => _level = value - DefaultLevel; } } |
正如我所说,这种技巧可能不适用于所有情况,即使结构只有值字段,只有您知道它是否适用于您的情况。只是检查一下。但你明白了。
下面是解决无默认构造函数困境的方法。我知道这是一个迟来的解决方案,但我认为值得注意的是,这是一个解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | public struct Point2D { public static Point2D NULL = new Point2D(-1,-1); private int[] Data; public int X { get { return this.Data[ 0 ]; } set { try { this.Data[ 0 ] = value; } catch( Exception ) { this.Data = new int[ 2 ]; } finally { this.Data[ 0 ] = value; } } } public int Z { get { return this.Data[ 1 ]; } set { try { this.Data[ 1 ] = value; } catch( Exception ) { this.Data = new int[ 2 ]; } finally { this.Data[ 1 ] = value; } } } public Point2D( int x , int z ) { this.Data = new int[ 2 ] { x , z }; } public static Point2D operator +( Point2D A , Point2D B ) { return new Point2D( A.X + B.X , A.Z + B.Z ); } public static Point2D operator -( Point2D A , Point2D B ) { return new Point2D( A.X - B.X , A.Z - B.Z ); } public static Point2D operator *( Point2D A , int B ) { return new Point2D( B * A.X , B * A.Z ); } public static Point2D operator *( int A , Point2D B ) { return new Point2D( A * B.Z , A * B.Z ); } public override string ToString() { return string.Format("({0},{1})" , this.X , this.Z ); } } |
忽略这样一个事实:我有一个名为空的静态结构(注意:这只适用于所有正象限),使用get;set;在c中,可以使用try/catch/finally来处理特定数据类型未由默认构造函数point2d()初始化的错误。我想这对于一些人来说是一个难以捉摸的答案。这就是为什么我要添加我的。使用C中的getter和setter功能将允许您无意义地绕过这个默认的构造函数,并对尚未初始化的内容进行一次尝试捕获。对于我来说,这很好,对于其他人,您可能需要添加一些if语句。因此,在需要设置分子/分母的情况下,此代码可能会有所帮助。我想重申一下,从效率的角度来看,这个解决方案看起来不太好,可能工作得更差,但是对于来自旧版本C的人来说,使用数组数据类型提供了这个功能。如果你只是想要一些有用的东西,试试这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | public struct Rational { private long[] Data; public long Numerator { get { try { return this.Data[ 0 ]; } catch( Exception ) { this.Data = new long[ 2 ] { 0 , 1 }; return this.Data[ 0 ]; } } set { try { this.Data[ 0 ] = value; } catch( Exception ) { this.Data = new long[ 2 ] { 0 , 1 }; this.Data[ 0 ] = value; } } } public long Denominator { get { try { return this.Data[ 1 ]; } catch( Exception ) { this.Data = new long[ 2 ] { 0 , 1 }; return this.Data[ 1 ]; } } set { try { this.Data[ 1 ] = value; } catch( Exception ) { this.Data = new long[ 2 ] { 0 , 1 }; this.Data[ 1 ] = value; } } } public Rational( long num , long denom ) { this.Data = new long[ 2 ] { num , denom }; /* Todo: Find GCD etc. */ } public Rational( long num ) { this.Data = new long[ 2 ] { num , 1 }; this.Numerator = num; this.Denominator = 1; } } |
您不能定义默认的构造函数,因为您使用的是C。
结构在.NET中可以有默认的构造函数,尽管我不知道任何支持它的特定语言。
1 2 3 4 5 6 7 8 9 10 11 | public struct Rational { private long numerator; private long denominator; public Rational(long num = 0, long denom = 1) // This is allowed!!! { numerator = num; denominator = denom; } } |