关于c#:为什么我不能在.NET中为结构定义默认构造函数?

Why can't I define a default constructor for a struct in .NET?

在.net,value type(C # struct)不让的constructor与没有参数。根据这个后这mandated由cli规范。什么happens是为每一个价值型的违约constructor雅(也被compiler吗?)这initialized所有成员到零(或null)。 P / < >

为什么它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也0/0这是不是很酷。 P / < >

PS:将违约不能解决参数的帮助这对C #天窗;4,或将之定义为CLR -违约constructor叫进来吗? P / < >

乔恩:answered飞碟 P / < >

To use your example, what would you want to happen when someone did:

1
 Rational[] fractions = new Rational[1000];

Should it run through your constructor 1000 times?

我应该相信它,这就是为什么我写了"违约constructor在第一个地方。*为CLR应该使用的违约zeroing constructor违约constructor当没有显式定义的方式;你只支付给你用。然后,如果我想在集装箱的1000非违约Rationals(和想optimize走"建设1000),我会用List而不是一个阵列。 P / < >

这个原因,在我的心中,也没有强到足够的预防中定义的constructor违约。 P / < >


注:下面的答案在c 6之前写了很长一段时间,它计划引入在结构中声明无参数构造函数的能力-但是在所有情况下它们仍然不会被调用(例如,对于数组创建)(in the end this feature was not added to c 6)。

编辑:由于Grauenwolf对CLR的洞察,我已经编辑了下面的答案。

clr允许值类型具有无参数的构造函数,但c不允许。我认为这是因为它会引入一种期望,即当构造函数没有参数时,它将被调用。例如,请考虑:

1
MyStruct[] foo = new MyStruct[1000];

仅仅通过分配适当的内存并将其全部归零,CLR就能够非常有效地完成这项工作。如果它必须运行mystruct构造函数1000次,那么效率会低很多。(事实上,它没有——如果您确实有一个无参数的构造函数,那么在创建数组或有一个未初始化的实例变量时,它不会运行。)

C中的基本规则是"任何类型的默认值都不能依赖于任何初始化"。现在,它们可以允许定义无参数构造函数,但在所有情况下都不需要执行该构造函数——但这会导致更多的混乱。(或者至少,我相信这一论点是正确的。)

编辑:要使用您的示例,当有人这样做时,您希望发生什么:

1
Rational[] fractions = new Rational[1000];

它是否应该在构造函数中运行1000次?

  • 如果没有,我们最终会得到1000个无效的理性
  • 如果是这样,那么如果我们要用实际值填充数组,那么我们可能会浪费大量的工作。

edit:(回答更多问题)无参数构造函数不是由编译器创建的。就clr而言,值类型不必有构造函数——尽管如果您在il中编写它,结果是可以的。当你用C语言写"new Guid()时,如果你称之为普通的构造函数,它会发出不同的IL。在这方面看这个问题。

我怀疑框架中没有任何带有无参数构造函数的值类型。毫无疑问,恩德彭德可以告诉我,如果我问得够好的话…事实上,C禁止这是一个足够大的提示,我认为这可能是一个坏主意。


结构是值类型,并且值类型在声明后必须具有默认值。

1
2
MyClass m;
MyStruct m2;

如果您像上面一样声明两个字段而不实例化其中任何一个字段,那么中断调试器,m将为空,但m2将不为空。如果这样,一个无参数的构造函数是没有意义的,事实上,一个结构上的所有构造函数都是赋值的,仅仅通过声明它本身就已经存在了。事实上,在上面的例子中,M2可以非常高兴地被使用,并且可以调用它的方法(如果有的话),并且可以操纵它的字段和属性!


尽管clr允许,但c不允许结构具有无参数的默认构造函数。原因是,对于值类型,编译器在默认情况下既不生成默认构造函数,也不生成对默认构造函数的调用。所以,即使您碰巧定义了一个默认的构造函数,也不会调用它,这只会使您感到困惑。

为了避免这些问题,C编译器不允许用户定义默认的构造函数。因为它不生成默认的构造函数,所以在定义字段时不能初始化字段。

或者,主要原因是结构是值类型,值类型由默认值初始化,构造函数用于初始化。

不必用new关键字实例化结构。相反,它的工作方式类似于int;您可以直接访问它。

结构不能包含显式无参数构造函数。结构成员自动初始化为其默认值。

结构的默认(无参数)构造函数可以设置不同于全零状态的值,这将是意外行为。因此,.NET运行时禁止结构的默认构造函数。


可以创建一个静态属性,该属性初始化并返回默认的"Rational"数字:

1
public static Rational One => new Rational(0, 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;
    }
}