42

バグを調査したところ、c#のこの奇妙さが原因であることがわかりました。

sbyte[] foo = new sbyte[10];
object bar = foo;
Console.WriteLine("{0} {1} {2} {3}",
        foo is sbyte[], foo is byte[], bar is sbyte[], bar is byte[]);

出力は「TrueFalseTrueTrue」ですが、「bar is byte[]」がFalseを返すことを期待していました。どうやらバーはabyte[]sbyte[]?の両方です Int32[]vsのような他の符号付き/符号なしタイプでも同じことが起こりますUInt32[]が、たとえばInt32[]vsでは起こりませんInt64[]

誰かがこの振る舞いを説明できますか?これは.NET3.5にあります。

4

4 に答える 4

69

更新: この質問をブログ エントリの基礎として使用しました。

https://web.archive.org/web/20190203221115/https://blogs.msdn.microsoft.com/ericlippert/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent/

この問題に関する詳細な議論については、ブログのコメントを参照してください。素晴らしい質問をありがとう!


CLI の型システムと C# の型システムの間に、興味深い、そして残念な矛盾があることに気付きました。

CLI には「割り当ての互換性」という概念があります。既知のデータ型 S の値 x が、既知のデータ型 T の特定の格納場所 y と「割り当て互換」である場合、x を y に格納できます。そうでない場合、検証可能なコードではなく、検証者はそれを許可しません。

たとえば、CLI 型システムは、参照型のサブタイプは参照型のスーパータイプと互換性のある代入であると述べています。どちらも参照型であり、文字列はオブジェクトのサブタイプであるため、文字列がある場合は、オブジェクト型の変数に格納できます。しかし、その逆は正しくありません。スーパータイプは、サブタイプとの代入互換性がありません。最初にキャストせずに、オブジェクトとしてのみ知られているものを文字列型の変数に貼り付けることはできません。

基本的に「割り当て互換性」とは、「これらの正確なビットをこの変数に貼り付けることが理にかなっている」ことを意味します。ソース値からターゲット変数への割り当ては、「表現を保持」する必要があります。詳細については、それに関する私の記事を参照してください。

http://ericlippert.com/2009/03/03/representation-and-identity/

CLI のルールの 1 つに、「X が Y と互換性のある代入である場合、X[] は Y[] と互換性のある代入である」というものがあります。

つまり、配列は割り当ての互換性に関して共変です。これは実際には壊れた共分散です。詳細については、それに関する私の記事を参照してください。

https://web.archive.org/web/20190118054040/https://blogs.msdn.microsoft.com/ericlippert/2007/10/17/covariance-and-contravariance-in-c-part-two-array-共分散/

これは C# のルールではありません。C# の配列共分散規則は、「X が暗黙的に参照型 Y に変換可能な参照型である場合、X[] は暗黙的に Y[] に変換可能」です。 それは微妙に異なるルールであるため、混乱する状況です。

CLI では、uint と int は代入互換です。しかし C# では、int と uint の間の変換は IMPLICIT ではなく EXPLICIT であり、これらは参照型ではなく値型です。したがって、C# では、int[] を uint[] に変換することはできません。

ただし、CLI では合法です。だから今、私たちは選択を迫られています。

  1. "is" を実装して、コンパイラが答えを静的に判断できない場合に、すべての C# ルールをチェックして同一性を維持する変換可能性をチェックするメソッドを実際に呼び出すようにします。これは遅く、99.9% の確率で CLR ルールと一致します。ただし、C# のルールに 100% 準拠するように、パフォーマンスの低下を考慮しています。

  2. 「is」を実装して、コンパイラが答えを静的に決定できない場合に、信じられないほど高速な CLR 代入の互換性チェックを実行し、実際には uint[] が int[] であると言っているという事実を受け入れます。 C# で合法であること。

私たちは後者を選びました。残念なことに、C# と CLI の仕様がこのマイナーな点で一致していませんが、私たちはこの不一致を受け入れるつもりです。

于 2009-07-24T17:42:22.743 に答える
9

Reflector を介してスニペットを実行します。

sbyte[] foo = new sbyte[10];
object bar = foo;
Console.WriteLine("{0} {1} {2} {3}", new object[] { foo != null, false, bar is sbyte[], bar is byte[] });

C# コンパイラは、最初の 2 つの比較 (foo is sbyte[]foo is byte[]) を最適化しています。ご覧のとおり、それらはfoo != null常に常に最適化されていfalseます。

于 2009-07-24T17:34:39.637 に答える
5

また興味深い:

    sbyte[] foo = new sbyte[] { -1 };
    var x = foo as byte[];    // doesn't compile
    object bar = foo;
    var f = bar as byte[];    // succeeds
    var g = f[0];             // g = 255
于 2009-07-24T17:37:50.473 に答える
0

確かに出力は正しいです。bar は sbyte[] と byte[] の両方と互換性があるため、sbyte[] と byte[] の両方「です」。

「is」は「式を型にキャストできる」と定義されています。

于 2009-07-24T17:31:15.853 に答える