web-dev-qa-db-ja.com

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

.NETでは、値型(C#struct)にパラメーターのないコンストラクターを含めることはできません。 this post によると、これは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はdefault zeroingコンストラクターを使用する必要があります。こうすることで、使用した分だけ支払うことになります。次に、デフォルトではないRationalsの1000個のコンテナが必要な場合(および1000個の構造を最適化する場合)、配列ではなくList<Rational>を使用します。

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

234
Motti

注:以下の答えはC#6よりも前に書かれたもので、構造体でパラメータなしのコンストラクタを宣言する機能を導入することを計画していますが、すべての状況で呼び出されるわけではありません(配列の作成など) (最終的にこの機能 C#6に追加されませんでした )。


編集:GrauenwolfがCLRを洞察したため、以下の回答を編集しました。

CLRでは、値型にパラメーターなしのコンストラクターを含めることができますが、C#ではできません。これは、コンストラクターが呼び出されないときに呼び出されるという期待を導入するためだと思います。たとえば、これを考慮してください:

MyStruct[] foo = new MyStruct[1000];

CLRは、適切なメモリを割り当ててすべてゼロにするだけで、非常に効率的にこれを行うことができます。 MyStructコンストラクターを1000回実行する必要がある場合は、効率が大幅に低下します。 (実際、そうではありません-パラメーターなしのコンストラクターがdoある場合、配列を作成するとき、または初期化されていないインスタンス変数があるときは実行されません。 )

C#の基本的なルールは、「どのタイプのデフォルト値も初期化に依存できない」です。これで、couldはパラメーターなしのコンストラクターの定義を許可しましたが、すべての場合にコンストラクターを実行する必要はありませんでしたが、それは混乱を招くことになります。 (少なくとも、だから私は議論が続くと信じています。)

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

Rational[] fractions = new Rational[1000];

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

  • そうでない場合、1000個の無効な論理式になります
  • もしそうなら、実際の値で配列を埋めようとする場合、潜在的に多くの作業を無駄にしています。

編集:(もう少し質問に答えます)パラメーターなしのコンストラクターは、コンパイラーによって作成されません。 CLRに関する限り、値型にはコンストラクターは必要ありません。ただし、ILで記述した場合はcanであることがわかります。 C#で「new Guid()」と記述すると、通常のコンストラクターを呼び出した場合とは異なるILを放出します。 this SO question をご覧ください。

suspectは、パラメーターなしのコンストラクターを持つフレームワークに値型が存在しないことを示しています。間違いなく、NDependが十分に質問したかどうかを教えてくれました... C#がそれを禁止しているという事実は、おそらく悪い考えだと思うのに十分なヒントです。

184
Jon Skeet

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

MyClass m;
MyStruct m2;

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

42
user42467

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

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

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

var rat = Rational.One;
14
Skasel

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

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

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

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

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

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

14
Adiii

短い説明:

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

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

13
Joel Coehoorn

特別な場合だけ。分子が0で分母が0の場合、本当に望む値を持っているふりをします。

2
Jonathan Allen

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

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

1
Jonathan Allen

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

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

私がこれから提供しようとしている最新のソリューションに相当するものを見たことがないので、ここにあります。

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

このソリューションは、値型のみの単純な構造体で機能します(ref型も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;
    }
}

これは異なります than この答え、このアプローチは特別なケースではなく、すべての範囲で機能するオフセットの使用です。

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

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
M.kazem Akhgary
public struct Rational 
{
    private long numerator;
    private long denominator;

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