18
public struct Test 
{
    public double Val;
    public Test(double val = double.NaN) { Val = val; }
    public bool IsValid { get { return !double.IsNaN(Val); } }
}

Test myTest = new Test();
bool valid = myTest.IsValid;

上記はvalid==true、デフォルトの引数を持つコンストラクターが呼び出されず、オブジェクトが標準のデフォルトの val = 0.0 で作成されるためです。
構造体がクラスの場合、動作はvalid==false私が期待するものです。

この動作の違い、特に構造体の場合の動作は驚くべきものであり、直感的ではありません-何が起こっているのでしょうか? stuct コンストラクトのデフォルトの引数は何に役立ちますか? 役に立たない場合、なぜこれをコンパイルさせますか?

更新: ここでの焦点を明確にするために、動作が何であるかではなく、なぜこれが警告なしにコンパイルされ、直感的に動作しないのかを説明します。つまり、 new Test() の場合、コンストラクターが呼び出されないためにデフォルトの引数が適用されない場合、なぜそれをコンパイルさせるのでしょうか?

4

5 に答える 5

10

C# では (少なくとも C# 6 まで -ブログ投稿を参照)、呼び出しnew Test()は書き込みと同等default(Test)です。実際にはコンストラクターは呼び出されず、既定値が提供されます。

デフォルトの引数は何の役にも立ちません。オプションの引数は C# 4 でのみ追加されたため、コンパイラの実装における見落としの結果である可能性があります。

  • オプションの引数が既存のオーバーロードと競合しないことを確認するコードは、構造体の場合に初期化子と競合する可能性を認識していません。
  • 意味を変換するコードnew Test()は、オプションの引数の存在を認識していない可能性があります。

    • コメントを掘り下げた後、Mads Torgersenによる次の宝石に気付きました。

      T が構造体である場合、コンパイラの実装ではこれまでのところ、'new T()' が本質的に default(T) を意味するように「最適化」されていることは事実です。これは実際にはバグでした.ILで許可されているため、実際のパラメーターなしのコンストラクターがある場合は常に呼び出すことになっていました.

      あなたの例でnew Test()は、コンパイラによって効果的に置き換えられることを意味しますdefault(Test)-これはバグであり、Visual Studio の次のバージョンで修正される予定です。

言い換えれば、コーナーケースがあります。Visual Studio の次のバージョンでの動作が変更されているため、その動作を確認する良い機会になるでしょう。

于 2014-11-26T09:44:35.117 に答える
2

この動作の違い、特に構造体の場合の動作は驚くべきものであり、直感的ではありません-何が起こっているのでしょうか? stuct コンストラクトのデフォルトの引数は何に役立ちますか? 役に立たない場合、なぜこれをコンパイルさせますか?

それは何の役にも立ちません。発行された IL コードは、デフォルト パラメーターを使用してコンストラクターへの呼び出しを生成しませんが、default(Test). コンパイラーがコンストラクターが呼び出されないという警告を発することは完全に合理的と思われます (これは実装の詳細ですが)。http://connect.microsoft.comで問題を報告します

生成された IL コードを見ると、次のようになります。

Test myTest = new Test();
bool valid = myTest.IsValid;

見てみましょう:

IL_0000:  ldloca.s    00 // myTest
IL_0002:  initobj     UserQuery.Test // default(Test);
IL_0008:  ldloca.s    00 // myTest
IL_000A:  call        UserQuery+Test.get_IsValid

IL で行われた呼び出しは、コンストラクターへのメソッド呼び出しではなく (次のようになります: call Test..ctor)、次の呼び出しを生成したことに注意してくださいinitobj

指定されたアドレスにある値型の各フィールドを null 参照または適切なプリミティブ型の 0 に初期化します。Newobjとは異なり、initobjはコンストラクター メソッドを呼び出しません。initobj は値型の初期化を目的としており、newobj はオブジェクトの割り当てと初期化に使用されます。

これは、C#-6.0 まではそのようなコンストラクターを宣言することは禁止されていたため、コンパイラーがデフォルト パラメーターを持つコンストラクターを単に無視していることを意味します。

@JonSkeet は、構造体で "new" を使用するとヒープまたはスタックに割り当てられますか?への回答でこれを深く掘り下げています。

編集

私は実際にMads Torgersonに、関連していると思われる C#-6.0 でのパラメーターなしのコンストラクターの新しい使用法について質問したところ、彼は次のように述べました。

@Yuval など、構造体のパラメーターなしのコンストラクターに関して: 認識すべきことは、以前も現在も、コンストラクターは必ずしも構造体で実行されるとは限らないということです。私たちが行ったのは、実行が保証されないパラメーターなしのコンストラクターを持つ機能を追加したことだけです。初期化されていることが保証されている構造体を持つ合理的な方法はなく、パラメーターなしのコンストラクターはそれを助けません。

パラメーターなしのコンストラクターが役立つのは、パラメーターなしのコンストラクターを使用できるようにすることです。

混乱の主な原因は、 'new S()' が 'default(S)' を意味することが許されていることだと思います。それはこの言語の歴史的な間違いであり、私はそれを取り除けることを心から願っています. パラメーターなしのコンストラクターを持たない構造体で 'new S()' を使用することを強く思いとどまらせます。私が知る限り、C# 1.0 には default(S) 構文が存在しなかったため、これは含まれているため、これは構造体の既定値を取得するために使用される構文に過ぎませんでした。

T が構造体である場合、コンパイラの実装ではこれまでのところ、'new T()' が本質的に default(T) を意味するように「最適化」されていることは事実です。これは実際にはバグでした.ILで許可されているため、実際のパラメーターなしのコンストラクターがある場合は常に呼び出すことになっていました. これを修正して、一般的な場合でもコンストラクターを呼び出すようにします。

したがって、セマンティクスはクリーンです。 new S() は、構造体でパラメーターなしのコンストラクターを実行する唯一の方法であり、ジェネリックを介しても常にそのコンストラクターを実行します。

于 2014-11-26T09:53:07.507 に答える
1

これは、C# ではパラメーターのない既定のコンストラクターが許可されていないため、パラメーターなしでコンストラクター Test を呼び出すと、通常どおり初期化されるためだと思われます。詳細については、この投稿を確認してください (まったく重複していません): Why can't I define a default constructor for a struct in .NET?

于 2014-11-26T09:42:23.627 に答える
1

構造体はユーザー定義のパラメーターなしのコンストラクターを持つことができないためです。

Test(double val = double.NaN) のように見えますが、実際にTest(double val)はデフォルト値に関するいくつかのメタデータと同じようにコンパイルされています。

于 2014-11-26T09:46:24.593 に答える