18

まず、背景を少し。私の質問の特定のシナリオについては、ここに投稿された質問と受け入れられた回答を読んでください。他にも同様のケースがあるかどうかはわかりませんが、これは私が知っている唯一のケースです。

上記の「クセ」は、私が以前から気になっていたことです。私はつい最近まで原因の全容を理解していませんでした。

このクラスに関する Microsoft のドキュメントはSqlParameter、状況をもう少し明らかにしています。

Objectvalue パラメーターでを指定するとSqlDbType、 はオブジェクトの Microsoft .NET Framework 型から推測されます。

コンストラクターのこのオーバーロードを使用してSqlParameter整数パラメーター値を指定する場合は注意してください。このオーバーロードは type の値を取るため、次の C# の例に示すように、値が zero の場合はObject整数値をObject 型に変換する必要があります。

Parameter = new SqlParameter("@pname", Convert.ToInt32(0));

この変換を実行しない場合、コンパイラは、SqlParameter (文字列、SqlDbType) コンストラクターのオーバーロードを呼び出そうとしていると見なします。

(強調を追加)

私の質問は、ハードコードされた "0" (および値 "0" のみ) を指定すると、整数型ではなく列挙型を指定しようとしているとコンパイラが想定するのはなぜですか? SqlDbTypeこの場合、値 0 ではなく、値を宣言していると見なされます。

これは直感的ではなく、さらに悪いことに、エラーには一貫性がありません。私は、何年もの間ストアド プロシージャを呼び出してきた、私が作成した古いアプリケーションを持っています。アプリケーションに変更を加え (多くの場合、SQL Server クラスに関連付けられていません)、更新プログラムを公開すると、この問題によってアプリケーションが突然壊れます。

複数のメソッド シグネチャを含むオブジェクトに、1 つのパラメータがオブジェクト/整数で、もう 1 つのパラメータが列挙を受け入れる 2 つの同様のシグネチャが含まれている場合、コンパイラが値 0 によって混乱するのはなぜですか?

前述したように、これが他のコンストラクターや他のクラスのメソッドに問題があるとは考えていません。これはクラスに固有のものSqlParameterですか、それとも C#/.Net 内のバグ継承ですか?

4

5 に答える 5

19

これは、ゼロ整数が暗黙的に列挙型に変換できるためです。

enum SqlDbType
{
    Zero = 0,
    One = 1
}

class TestClass
{
    public TestClass(string s, object o)
    { System.Console.WriteLine("{0} => TestClass(object)", s); } 

    public TestClass(string s, SqlDbType e)
    { System.Console.WriteLine("{0} => TestClass(Enum SqlDbType)", s); }
}

// This is perfectly valid:
SqlDbType valid = 0;
// Whilst this is not:
SqlDbType ohNoYouDont = 1;

var a1 = new TestClass("0", 0);
// 0 => TestClass(Enum SqlDbType)
var a2 = new TestClass("1", 1); 
// => 1 => TestClass(object)

( Visual C# 2008 Breaking Changes - change 12から適応)

コンパイラがオーバーロードの解決を実行する場合、次の理由により、コンストラクターとコンストラクターの両方で0 が該当する関数メンバーになります。SqlDbTypeobject

引数の型から対応するパラメーターの型への暗黙的な変換 (セクション 6.1) が存在する

(SqlDbType x = 0との両方object x = 0が有効です)

変換規則が優れているため、パラメーターはパラメーターSqlDbTypeよりも優れています。object

  • T1とが同じ型の場合T2、どちらの変換も優れていません。
    • objectSqlDbType同じタイプではない
  • の場合ST1C1より良い変換です。
    • 0ではないobject
  • の場合ST2C2より良い変換です。
    • 0ではないSqlDbType
  • から への暗黙的な変換が存在し、 からT1への暗黙的な変換が存在しない場合は、より適切な変換です。 T2T2T1C1
    • objectからへの暗黙的な変換はSqlDbType存在しません
  • から への暗黙的な変換が存在し、 からT2への暗黙的な変換が存在しない場合は、より適切な変換です。 T1T1T2C2
    • SqlDbTypeからへの暗黙的な変換がobject存在するため、SqlDbType変換の方が適切です

@Ericが回答で説明しているように、定数0を正確に構成するものがVisual C#2008 (MicrosoftのC#仕様の実装)で(かなり微妙に)変更されたことに注意してください。

于 2013-01-08T22:08:46.560 に答える
16

RichardTowers の回答は素晴らしいですが、少し補足したいと思います。

他の回答が指摘しているように、動作の理由は、(1)ゼロは任意の列挙型、および明らかにオブジェクトに変換可能であり、(2)任意の列挙型はそのオブジェクトにより固有であるため、列挙型を取るメソッドはしたがって、より良い方法としてオーバーロードの解決によって選択されます。ポイント2は自明だといいのですが、ポイント1を説明するものは何ですか?

まず、仕様からの残念な逸脱があります。仕様では、リテラルゼロ、つまりソースコードに0実際に文字どおり表示される数値は、暗黙的に任意の列挙型に変換される可能性があると述べています。コンパイラは実際に、任意の定数ゼロがこのように変換される可能性があることを実装しています。その理由は、奇妙な一貫性のない方法で、コンパイラが定数ゼロを許可する場合と許可しない場合があるというバグによるものです。この問題を解決する最も簡単な方法は、一貫して定数ゼロを許可することでした。これについては、こちらで詳しく読むことができます。

https://web.archive.org/web/20110308161103/http://blogs.msdn.com/b/ericlippert/archive/2006/03/28/the-root-of-all-evil-part-one. aspx

次に、ゼロを任意の列挙型に変換できるようにする理由は、「フラグ」列挙型を常にゼロにできるようにするためです。適切なプログラミング方法として、すべての「フラグ」列挙型にゼロに等しい値「None」を指定しますが、これはガイドラインであり、要件ではありません。C# 1.0 の設計者は、あなたが言わなければならないことは奇妙に見えると考えました。

for (MyFlags f = (MyFlags)0; ...

ローカルを初期化します。私の個人的な意見では、この決定は、上記のバグに対する悲しみと、発見した過負荷の解決に導入される奇妙な点の両方の点で、価値があるよりも多くの問題を引き起こした.

最後に、コンストラクターの設計者は、これがそもそも問題になることを認識し、オーバーロードのシグネチャを作成して、開発者がキャストを挿入することなく呼び出す ctor を明確に決定できるようにすることができました。残念ながら、これはかなり曖昧な問題であるため、多くの設計者はそれに気づいていません。これを読んでいる人が同じ間違いをしないことを願っています。2 つのオーバーライドに異なるセマンティクスを持たせる場合は、オブジェクトと列挙の間にあいまいさを作成しないでください

于 2013-01-09T15:09:27.603 に答える
1

これは、整数リテラル0が任意の列挙型への暗黙の変換を持っているという事実が原因です。C#仕様には次のように記載されています。

6.1.3暗黙的な列挙変換

暗黙的な列挙型変換により、decimal-integer-literal 0を任意のenum-typeに変換し、基になる型がenum-typeであるnullable-typeに変換することができます。後者の場合、変換は、基になる列挙型に変換し、結果をラップすることによって評価されます。

結果として、この場合の最も具体的な過負荷はですSqlParameter(string, DbType)

これは他のint値には適用されないため、SqlParameter(string, object)コンストラクターが最も具体的です。

于 2013-01-08T21:56:40.177 に答える
1

これは明らかに既知の動作であり、列挙型とオブジェクト型の両方が存在する関数のオーバーロードに影響します。私はそれをすべて理解していませんが、EricLippertは彼のブログでそれを非常にうまくまとめています

于 2013-01-08T21:49:48.207 に答える
0

オーバーロードされたメソッドの型を解決するとき、C#は最も具体的なオプションを選択します。SqlParameterクラスには、正確に2つの引数をとる2つのコンストラクターとがSqlParameter(String, SqlDbType)ありSqlParameter(String, Object)ます。リテラルを0指定すると、オブジェクトまたはSqlDbTypeとして解釈できます。SqlDbTypeはObjectよりも具体的であるため、インテントであると見なされます。

過負荷の解決について詳しくは、この回答をご覧ください。

于 2013-01-08T21:50:02.110 に答える