C# 为什么我不能为 .NET 中的结构定义默认构造函数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/333829/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 23:36:01  来源:igfitidea点击:

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

c#.netstruct

提问by Motti

In .NET, a value type (C# struct) can't have a constructor with no parameters. According to this postthis is mandated by the CLI specification. What happens is that for every value-type a default constructor is created (by the compiler?) which initialized all members to zero (or null).

在 .NET 中,值类型 (C# struct) 不能有没有参数的构造函数。根据这篇文章,这是 CLI 规范规定的。发生的情况是,对于每个值类型,都会创建一个默认构造函数(由编译器?),它将所有成员初始化为零(或null)。

Why is it disallowed to define such a default constructor?

为什么不允许定义这样的默认构造函数?

One trivial use is for rational numbers:

一种微不足道的用途是用于有理数:

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;
    }
}

Using current version of C#, a default Rational is 0/0which is not so cool.

使用当前版本的 C#,默认的 Rational0/0并不是那么酷。

PS: Will default parameters help solve this for C# 4.0 or will the CLR-defined default constructor be called?

PS:默认参数会帮助解决 C# 4.0 的这个问题还是会调用 CLR 定义的默认构造函数?



Jon Skeetanswered:

乔恩·斯基特回答:

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

 Rational[] fractions = new Rational[1000];

Should it run through your constructor 1000 times?

以您的示例为例,当有人这样做时,您希望发生什么:

 Rational[] fractions = new Rational[1000];

它应该通过你的构造函数运行 1000 次吗?

Sure it should, that's why I wrote the default constructor in the first place. The CLR should use the default zeroingconstructor when no explicit default constructor is defined; that way you only pay for what you use. Then if I want a container of 1000 non-default Rationals (and want to optimize away the 1000 constructions) I will use a List<Rational>rather than an array.

当然应该,这就是我首先编写默认构造函数的原因。当未定义显式默认构造函数时,CLR 应使用默认归零构造函数;这样你只需为你使用的东西付费。然后,如果我想要一个包含 1000 个非默认Rationals的容器(并且想要优化掉 1000 个构造),我将使用 aList<Rational>而不是数组。

This reason, in my mind, is not strong enough to prevent definition of a default constructor.

在我看来,这个原因不足以阻止定义默认构造函数。

采纳答案by Jon Skeet

Note:the answer below was written a long time prior to C# 6, which is planning to introduce the ability to declare parameterless constructors in structs - but they still won't be called in all situations (e.g. for array creation)(in the end this feature was not added to C# 6).

注意:下面的答案是在 C# 6 之前很长时间编写的,它计划引入在结构中声明无参数构造函数的能力 - 但它们仍然不会在所有情况下(例如创建数组)(最后此功能未添加到 C# 6 中)。



EDIT: I've edited the answer below due to Grauenwolf's insight into the CLR.

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

The CLR allows value types to have parameterless constructors, but C# doesn't. I believe this is because it would introduce an expectation that the constructor would be called when it wouldn't. For instance, consider this:

CLR 允许值类型具有无参数构造函数,但 C# 不允许。我相信这是因为它会引入一种期望,即在构造函数不会被调用时会被调用。例如,考虑这个:

MyStruct[] foo = new MyStruct[1000];

The CLR is able to do this very efficiently just by allocating the appropriate memory and zeroing it all out. If it had to run the MyStruct constructor 1000 times, that would be a lot less efficient. (In fact, it doesn't - if you dohave a parameterless constructor, it doesn't get run when you create an array, or when you have an uninitialized instance variable.)

CLR 只需分配适当的内存并将其全部清零即可非常有效地完成此操作。如果它必须运行 MyStruct 构造函数 1000 次,那效率会低很多。(事实上,它没有-如果你这样做有一个参数的构造函数,当你创建一个数组没有得到执行,或者当你有一个未初始化的实例变量。)

The basic rule in C# is "the default value for any type can't rely on any initialization". Now they couldhave allowed parameterless constructors to be defined, but then not required that constructor to be executed in all cases - but that would have led to more confusion. (Or at least, so I believe the argument goes.)

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

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

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

Rational[] fractions = new Rational[1000];

Should it run through your constructor 1000 times?

它应该通过你的构造函数运行 1000 次吗?

  • If not, we end up with 1000 invalid rationals
  • If it does, then we've potentially wasted a load of work if we're about to fill in the array with real values.
  • 如果不是,我们最终会得到 1000 个无效的有理数
  • 如果是这样,那么如果我们要用实际值填充数组,我们可能会浪费大量的工作。

EDIT: (Answering a bit more of the question) The parameterless constructor isn't created by the compiler. Value types don't have to have constructors as far as the CLR is concerned - although it turns out it canif you write it in IL. When you write "new Guid()" in C# that emits different IL to what you get if you call a normal constructor. See this SO questionfor a bit more on that aspect.

编辑:(回答更多的问题)无参数构造函数不是由编译器创建的。就 CLR 而言,值类型不必具有构造函数——尽管事实证明如果你用 IL 编写它就可以。当您new Guid()在 C# 中编写“ ”时,它会发出与调用普通构造函数时得到的 IL 不同的 IL。有关方面的更多信息,请参阅此 SO 问题

I suspectthat there aren't any value types in the framework with parameterless constructors. No doubt NDepend could tell me if I asked it nicely enough... The fact that C# prohibits it is a big enough hint for me to think it's probably a bad idea.

怀疑框架中没有任何具有无参数构造函数的值类型。毫无疑问,如果我问得足够好,NDepend 会告诉我...... C# 禁止它的事实足以让我认为这可能是一个坏主意。

回答by Joel Coehoorn

Shorter explanation:

更简短的解释:

In C++, struct and class were just two sides of the same coin. The only real difference is that one was public by default and the other was private.

在 C++ 中,struct 和 class 只是同一枚硬币的两个方面。唯一真正的区别是默认情况下一个是公开的,另一个是私有的。

In .NET, there is a much greater difference between a struct and a class. The main thing is that struct provides value-type semantics, while class provides reference-type semantics. When you start thinking about the implications of this change, other changes start to make more sense as well, including the constructor behavior you describe.

.NET 中,结构体和类之间的区别要大得多。主要是struct提供值类型语义,而class提供引用类型语义。当您开始考虑此更改的含义时,其他更改也开始变得更有意义,包括您描述的构造函数行为。

回答by user42467

A struct is a value type and a value type must have a default value as soon as it is declared.

结构体是值类型,值类型一经声明就必须具有默认值。

MyClass m;
MyStruct m2;

If you declare two fields as above without instantiating either, then break the debugger, mwill be null but m2will not. Given this, a parameterless constructor would make no sense, in fact all any constructor on a struct does is assign values, the thing itself already exists just by declaring it. Indeed m2 could quite happily be used in the above example and have its methods called, if any, and its fields and properties manipulated!

如果如上所述声明两个字段而没有实例化,则中断调试器,m将为空但m2不会。鉴于此,无参数构造函数将毫无意义,事实上,结构上的所有构造函数所做的都是赋值,事物本身已经存在,只是通过声明它。事实上,m2 可以很愉快地用于上面的例子,并调用它的方法(如果有的话),并操纵它的字段和属性!

回答by Jonathan Allen

Just special-case it. If you see a numerator of 0 and a denominator of 0, pretend like it has the values you really want.

只是特例而已。如果您看到分子为 0 分母为 0,请假装它具有您真正想要的值。

回答by Jonathan Allen

You can't define a default constructor because you are using C#.

您无法定义默认构造函数,因为您使用的是 C#。

Structs can have default constructors in .NET, though I don't know of any specific language that supports it.

结构可以在 .NET 中具有默认构造函数,但我不知道支持它的任何特定语言。

回答by Jonathan Allen

You can make a static property that initializes and returns a default "rational" number:

您可以创建一个静态属性来初始化并返回一个默认的“有理”数:

public static Rational One => new Rational(0, 1); 

And use it like:

并像这样使用它:

var rat = Rational.One;

回答by Adiii

Although the CLR allows it, C# does not allow structs to have a default parameter-less constructor. The reason is that, for a value type, compilers by default neither generate a default constructor, nor do they generate a call to the default constructor. So, even if you happened to define a default constructor, it will not be called, and that will only confuse you.

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

To avoid such problems, the C# compiler disallows definition of a default constructor by the user. And because it doesn't generate a default constructor, you can't initialize fields when defining them.

为避免此类问题,C# 编译器不允许用户定义默认构造函数。并且因为它不生成默认构造函数,所以在定义字段时不能初始化它们。

Or the big reason is that a structure is a value type and value types are initialized by a default value and the constructor is used for initialization.

或者很大的原因是结构体是值类型,值类型由默认值初始化,并使用构造函数进行初始化。

You don't have to instantiate your struct with the newkeyword. It instead works like an int; you can directly access it.

您不必使用new关键字实例化您的结构。相反,它像 int 一样工作;你可以直接访问它。

Structs cannot contain explicit parameterless constructors. Struct members are automatically initialized to their default values.

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

A default (parameter-less) constructor for a struct could set different values than the all-zeroed state which would be unexpected behavior. The .NET runtime therefore prohibits default constructors for a struct.

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

回答by G1xb17

Here's my solution to the no default constructor dilemma. I know this is a late solution, but I think it's worth noting this is a solution.

这是我对无默认构造函数困境的解决方案。我知道这是一个迟到的解决方案,但我认为值得注意的是这是一个解决方案。

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 );
    }
}

ignoring the fact I have a static struct called null, (Note: This is for all positive quadrant only), using get;set; in C#, you can have a try/catch/finally, for dealing with the errors where a particular data type is not initialized by the default constructor Point2D(). I guess this is elusive as a solution to some people on this answer. Thats mostly why i'm adding mine. Using the getter and setter functionality in C# will allow you to bypass this default constructor non-sense and put a try catch around what you dont have initialized. For me this works fine, for someone else you might want to add some if statements. So, In the case where you would want a Numerator/Denominator setup, this code might help. I'd just like to reiterate that this solution does not look nice, probably works even worse from an efficiency standpoint, but, for someone coming from an older version of C#, using array data types gives you this functionality. If you just want something that works, try this:

忽略我有一个名为 null 的静态结构的事实,(注意:这仅适用于所有正象限),使用 get;set; 在 C# 中,您可以使用 try/catch/finally 来处理特定数据类型未由默认构造函数 Point2D() 初始化的错误。我想这对于某些人来说是难以捉摸的。这就是为什么我要添加我的。使用 C# 中的 getter 和 setter 功能将允许您绕过这个无意义的默认构造函数,并在未初始化的内容周围放置一个 try catch。对我来说这很好用,对于其他人,你可能想添加一些 if 语句。因此,在您需要分子/分母设置的情况下,此代码可能会有所帮助。我只想重申,这个解决方案看起来不太好,从效率的角度来看可能更糟,但是,对于来自旧版本 C# 的人来说,使用数组数据类型可以提供此功能。如果你只是想要一些有用的东西,试试这个:

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;
    }
}

回答by M.kazem Akhgary

I haven't seen equivalent to late solution I'm going to give, so here it is.

我还没有看到与我要提供的迟到解决方案等效的解决方案,所以就在这里。

use offsets to move values from default 0 into any value you like. here properties must be used instead of directly accessing fields. (maybe with possible c#7 feature you better define property scoped fields so they remain protected from being directly accessed in code.)

使用偏移量将值从默认 0 移动到您喜欢的任何值。这里必须使用属性而不是直接访问字段。(也许使用可能的 c#7 功能,您可以更好地定义属性范围的字段,这样它们就不会在代码中被直接访问。)

This solution works for simple structs with only value types (no ref type or nullable struct).

此解决方案适用于仅具有值类型(无 ref 类型或可为空结构)的简单结构。

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;
    }
}

This is different thanthis answer, this approach is not especial casing but its using offset which will work for all ranges.

这个答案不同,这种方法不是特殊的外壳,而是它使用的偏移量适用于所有范围。

example with enums as field.

以枚举作为字段的示例。

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;
    }
}

As I said this trick may not work in all cases, even if struct has only value fields, only you know that if it works in your case or not. just examine. but you get the general idea.

正如我所说,这个技巧可能不适用于所有情况,即使 struct 只有值字段,也只有您知道它是否适用于您的情况。只是检查。但你明白了一般的想法。

回答by eMeL

public struct Rational 
{
    private long numerator;
    private long denominator;

    public Rational(long num = 0, long denom = 1)   // This is allowed!!!
    {
        numerator   = num;
        denominator = denom;
    }
}