.NETで構造体のデフォルトコンストラクターを定義できないのはなぜですか?


261

.NETでは、値の型(C#struct)にパラメーターのないコンストラクターを含めることはできません。この投稿によるとこれはCLI仕様で義務付けられています。すべての値タイプに対して、デフォルトのコンストラクターが(コンパイラーによって)作成され、すべてのメンバーがゼロ(またはnull)に初期化されます。

なぜそのようなデフォルトのコンストラクタを定義することができないのですか?

簡単な使い方の1つは有理数です。

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で定義されたデフォルトのコンストラクターが呼び出されますか?


ジョン・スキートは答えました:

あなたの例を使用するには、誰かがしたときに何をしたいですか:

 Rational[] fractions = new Rational[1000];

コンストラクタを1000回実行する必要がありますか?

もちろん、そうすべきです。そもそも、デフォルトのコンストラクタを最初に作成した理由です。明示的なデフォルトコンストラクターが定義されていない場合、CLRはデフォルトのゼロ化コンストラクターを使用する必要があります。そうすれば、使用した分だけ支払うことができます。次に、デフォルト以外の1000個のコンテナーが必要な場合Rational(および1000の構造を最適化したいList<Rational>場合)、配列ではなくを使用します。

私の考えでは、この理由は、デフォルトのコンストラクターの定義を妨げるほど強力ではありません。


3
+1にも同様の問題がありましたが、最後に構造体をクラスに変換しました。
Dirk Vollmar、2008

4
C#4のデフォルトパラメータRational()は、でなくパラメータなしのctorを呼び出すため、役に立ちませんRational(long num=0, long denom=1)
LaTeX、

6
Visual Studio 2015に付属するC#6.0では、構造体のゼロパラメーターインスタンスコンストラクターを作成できることに注意してください。したがってnew Rational()、存在する場合はコンストラクタを呼び出しますが、存在しない場合new Rational()はと同等になりdefault(Rational)ます。いずれの場合でもdefault(Rational)、構造体の「ゼロ値」が必要なときに構文を使用することをお勧めします(これは、の設計で「悪い」数値ですRational)。値タイプのデフォルト値Tは常にdefault(T)です。したがって、new Rational[1000]structコンストラクターを呼び出すことはありません。
Jeppe Stig Nielsen

6
この特定の問題を解決するにdenominator - 1は、構造体内に保存して、デフォルト値が0/1になるようにします
miniBill

3
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.配列が構造体のリストに対して別のコンストラクターを呼び出すことを期待するのはなぜですか?
mjwills 2017年

回答:


197

注:以下の答えは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()通常のコンストラクターを呼び出した場合に得られるものとは異なるILを放出するC#で「」を書き込む場合。その側面についてもう少し詳しくは、このSOの質問を参照してください。

私は疑うパラメータなしのコンストラクタを持つ枠組みの中で任意の値の型が存在しないこと。間違いなくNDependが私に十分に尋ねれば私に教えてくれるでしょう... C#がそれを禁止しているという事実は、それがおそらく悪い考えだと私が考えるのに十分に大きなヒントです。


9
短い説明:C ++では、構造体とクラスは同じコインの両面にすぎませんでした。唯一の本当の違いは、1つはデフォルトでパブリックであり、もう1つはプライベートであったことです。.Netでは、構造体とクラスの間に大きな違いがあり、それを理解することが重要です。
Joel Coehoorn、2008

39
@ジョエル:それはこの特定の制限を実際に説明するものではありませんよね?
Jon Skeet、

6
CLRでは、値型にパラメーターのないコンストラクターを含めることができます。そして、はい、それは配列内のありとあらゆる要素に対してそれを実行します。C#は、これは悪い考えであり、それを許可していませんが、そうすることができる.NET言語を書くことができます。
ジョナサンアレン

2
申し訳ありませんが、次の点について少し混乱しています。ないRational[] fractions = new Rational[1000];場合は、作業の負荷を無駄Rational代わりに構造体のクラスですか?もしそうなら、なぜクラスにデフォルトのctorがあるのですか?
私の脇の下にキスをする

5
@ FifaEarthCup2014:「大量の作業を無駄にする」という意味について、より具体的にする必要があります。しかし、どちらにしても、コンストラクタを1000回呼び出すことはありません。Rationalがクラスの場合、1000のnull参照の配列になります。
Jon Skeet、2014年

48

構造体は値型であり、値型は宣言されるとすぐにデフォルト値を持つ必要があります。

MyClass m;
MyStruct m2;

上記のようにインスタンス化せずに2つのフィールドを宣言すると、デバッガーが中断され、mnullになりますが、そうm2なりません。これを考えると、パラメーターのないコンストラクターは意味をなさず、実際、構造体のコンストラクターはすべて値を割り当てるだけであり、宣言するだけで事物自体はすでに存在しています。実際、m2は上記の例で非常に楽しく使用でき、そのメソッドがあれば呼び出され、そのフィールドとプロパティが操作されます。


3
なぜ誰かがあなたに反対票を投じたのかわかりません。あなたはここで最も正しい答えのようです。
pipTheGeek 2008

12
C ++の動作は、型にデフォルトのコンストラクターがある場合、そのようなオブジェクトが明示的なコンストラクターなしで作成されるときに使用されます。これはC#でデフォルトコンストラクターを使用してm2を初期化するために使用できたため、この回答は役に立ちません。
Motti

3
onester:宣言されたときに構造体が独自のコンストラクターを呼び出すことを望まない場合は、そのようなデフォルトのコンストラクターを定義しないでください!:)それはMottiのことわざです
Stefan Monov

8
@Tarik。私は同意しない。逆に、パラメーターなしのコンストラクターは完全に理にかなっています。デフォルト値として恒等行列を常に持つ「Matrix」構造体を作成したい場合、他の方法でどのように実行できますか?
Elo

1
「確かにm2はとても楽しく使用できます。」に完全に同意するかどうかはわかりません。以前のC#では真実であった可能性がありますが、構造体ではなく構造体を宣言してから、そのメンバーを使用しようとすると、コンパイラエラーになりますnew
Caius Jard

18

CLRでは許可されていますが、C#では構造体にデフォルトのパラメーターなしのコンストラクターを含めることはできません。その理由は、値型の場合、コンパイラーはデフォルトでデフォルトのコンストラクターを生成せず、デフォルトのコンストラクターへの呼び出しも生成しないためです。したがって、たとえデフォルトのコンストラクタを定義したとしても、それは呼び出されず、混乱するだけです。

このような問題を回避するために、C#コンパイラはユーザーによるデフォルトコンストラクターの定義を許可しません。また、デフォルトのコンストラクタを生成しないため、フィールドを定義するときにフィールドを初期化できません。

または、大きな理由は、構造体が値型であり、値型がデフォルト値によって初期化され、コンストラクターが初期化に使用されることです。

newキーワードで構造体をインスタンス化する必要はありません。代わりにintのように機能します。直接アクセスできます。

構造体には、明示的なパラメーターなしのコンストラクターを含めることはできません。Structメンバーは自動的にデフォルト値に初期化されます。

構造体のデフォルト(パラメーターなし)コンストラクターは、すべてゼロの状態とは異なる値を設定する可能性があり、予期しない動作になります。したがって、.NETランタイムは、構造体のデフォルトコンストラクターを禁止します。


この答えは断然最高です。結局、制限の全体的なポイントは、MyStruct s;提供したデフォルトのコンストラクターを呼び出さないなどの驚きを避けることです。
talles

1
ご説明ありがとうございます。したがって、改善が必要なのはコンパイラの不足だけであり、パラメータなしのコンストラクタを禁止する理論的な正当な理由はありません(プロパティへのアクセスのみに制限されるとすぐに)。
Elo

16

デフォルトの「有理」番号を初期化して返す静的プロパティを作成できます。

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

そしてそれを次のように使用します:

var rat = Rational.One;

24
この場合、Rational.Zero混乱が少し少ないかもしれません。
Kevin

13

短い説明:

C ++では、構造体とクラスは同じコインの両面にすぎませんでした。唯一の真の違いは、1つはデフォルトでパブリックであり、もう1つはプライベートであったことです。

では.NET、構造体とクラスの間にはるかに大きな差があります。主なことは、構造体が値型のセマンティクスを提供するのに対し、クラスは参照型のセマンティクスを提供することです。この変更の影響について考え始めると、説明するコンストラクターの動作を含む他の変更もより意味のあるものになります。


7
これが値と参照型の分割によってどのように暗示されるかについては、もう少し明示する必要があります。わかりません...
Motti

値型にはデフォルト値があります。コンストラクタを定義しなくても、nullではありません。一見、これはデフォルトのコンストラクタを定義することも除外しませんが、フレームワークはこの機能を内部で使用して、構造体に関する特定の仮定を行います。
Joel Coehoorn、2008

@annakata:他のコンストラクターは、おそらくReflectionを含むいくつかのシナリオで役立ちます。また、ジェネリックがパラメーター化された「新しい」制約を許可するように拡張された場合、それらに準拠できる構造体があると便利です。
スーパーキャット2012

@annakataこれは、C#にはnewコンストラクターを呼び出すために本当に記述しなければならない特定の強力な要件があるためだと思います。C ++では、コンストラクターは、配列の宣言またはインスタンス化時に、隠れた方法で呼び出されます。C#では、すべてがポインタであるため、nullから開始するか、構造体であり、何かで開始する必要がありますが、new(配列initのように)を記述できない場合、強力なC#ルールに違反します。
v.oddou

3

これから行う解決策に相当するものは見たことがありません。ここにあります。

オフセットを使用して、値をデフォルトの0から任意の値に移動します。ここでは、フィールドに直接アクセスする代わりに、プロパティを使用する必要があります。(おそらくc#7機能を使用すると、プロパティスコープのフィールドをより適切に定義して、コード内で直接アクセスできないように保護することができます。)

このソリューションは、値型のみの単純な構造体(参照型またはnull許容構造体なし)で機能します。

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

これは異なっているよりも、この答えは、このアプローチは特別なケースではありませんが、そのオフセットは使用すると、すべての範囲のために働くであろう。

フィールドとして列挙型を使用した例。

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

私が言ったように、このトリックはすべてのケースで機能するわけではありません。構造体が値フィールドのみを持つ場合でも、それがあなたのケースで機能するかどうかはあなただけが知っています。ただ調べてください。しかし、あなたは一般的な考えを理解します。


これは私が挙げた例の良い解決策ですが、それは実際には例にすぎないと想定されていたので、質問は一般的です。
Motti、2017

2

ただそれを特別な場合。分子が0で分母が0の場合は、実際に必要な値があるように見せかけます。


5
個人的には、クラスや構造体にこのような動作をさせたくありません。静かに失敗する(または開発者が推測する方法で回復する)ことは、見過ごされがちな間違いへの道です。
Boris Callens

2
+1これは良い答えです。値タイプの場合、デフォルト値を考慮する必要があるためです。これで、デフォルト値とその動作を「設定」できます。
IllidanS4はモニカに2015

これはまさにNullable<T>(例えばint?)などのクラスを実装する方法です。
ジョナサンアレン

それは非常に悪い考えです。0/0は常に無効な分数(NaN)である必要があります。誰かnew Rational(x,y)がxとyが偶然0である場所を呼び出した場合はどうなりますか?
マイクロソフト

実際のコンストラクターがある場合は、例外をスローして、実際の0/0が発生しないようにすることができます。または、それを実行したい場合は、デフォルトと0/0を区別するために追加のブール値を追加する必要があります。
ジョナサンアレン

2

私が使用しているのは、次のようなバッキングフィールドと組み合わせたnull結合演算子(??)です。

public struct SomeStruct {
  private SomeRefType m_MyRefVariableBackingField;

  public SomeRefType MyRefVariable {
    get { return m_MyRefVariableBackingField ?? (m_MyRefVariableBackingField = new SomeRefType()); }
  }
}

お役に立てれば ;)

注:nullの合体割り当ては現在、C#8.0の機能提案です。


1

C#を使用しているため、デフォルトのコンストラクターを定義できません。

構造体は.NETでデフォルトのコンストラクターを持つことができますが、それをサポートする特定の言語は知りません。


C#では、クラスと構造体は意味的に異なります。構造体は値型であり、クラスは参照型です。
Tom Sarduy、2011年

-1

これは、デフォルトコンストラクターなしのジレンマに対する私の解決策です。私はこれが遅い解決策であることを知っていますが、これが解決策であることは注目に値します。

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

nullと呼ばれる静的な構造体があることを無視して(注:これはすべての正の象限のみ)、get; setを使用します。C#では、特定のデータ型がデフォルトのコンストラクターPoint2D()によって初期化されないエラーに対処するために、try / catch / finallyを使用できます。これは、この答えの一部の人々への解決策としてとらえどころのないものだと思います。それが主に私が私の物を追加する理由です。C#でゲッターおよびセッター機能を使用すると、このデフォルトのコンストラクターを無意味にバイパスし、初期化していないものを回避することができます。私にとってはこれでうまくいきますが、他の誰かにとっては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;
    }
}

2
これは非常に悪いコードです。構造体に配列参照があるのはなぜですか?単にX座標とY座標をフィールドとして持たないのですか?フロー制御に例外を使用することは悪い考えです。通常は、NullReferenceExceptionが発生しないようにコードを記述する必要があります。本当にこれが必要な場合-そのような構成は構造体よりクラスに適していますが、遅延初期化を使用する必要があります。(そして技術的には、座標の最初の設定を除いて、まったく不必要です-各座標を2回設定します。)
Mike Rosoft

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

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

5
許可されていますが、パラメータが指定されていない場合は使用されませんideone.com/xsLloQ
Motti
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.