51

C#/.NET で奇妙な現象が発生しました。

実証するために、この最小限の例を作成しました。

if (new sbyte[5] is byte[])
{
 throw new ApplicationException("Impossible!");
}

object o = new sbyte[5];

if (o is byte[])
{
 throw new ApplicationException("Why???");
}

これは "Why???" をスローしますが、"Impossible!" はスローしません。同じサイズの整数型のすべての配列で機能します。誰かが私にこれを説明できますか?よくわかりません。ちなみに、私は.NET 4を使用しています。

PS: を使用して期待される結果を得ることができることを知っていo.GetType() == typeof(byte[])ます。

4

4 に答える 4

51

キャストの CLR 規則では、これが可能であると指定されています。C# の規則では、それは不可能であるとされています。C# チームは、さまざまな理由から、この仕様からの逸脱を許容することを意識的に決定しました。

CLR がこれを許可するのはなぜですか? おそらく、彼らはそれを便利に実装できるからです。byte同じバイナリ表現を持つため、メモリの安全性に違反することなくaを an としてsbyte「扱う」ことができます。byte[]sbyte[]

同じメモリ レイアウトを持つ他のプリミティブ型でも同じトリックが機能します。

于 2012-08-15T20:49:31.423 に答える
28

面白いことに、ToList を使用しているときにこの Linq キャストが失敗するのはなぜですか?

Jon Skeet (もちろん) は、私の問題は C# コンパイラであると説明しています。何らかの理由で、それらが同じになることはあり得ないと考え、それを false に最適化するのに役立ちます。ただし、CLRこれを可能にします。オブジェクトへのキャストはコンパイラの最適化を無効にするため、CLR を通過します。

彼の答えからの関連部分:

C# では byte[] を sbyte[] に直接キャストすることはできませんが、CLR ではそれが可能です。

var foo = new byte[] {246, 127};
// This produces a warning at compile-time, and the C# compiler "optimizes"
// to the constant "false"
Console.WriteLine(foo is sbyte[]);

object x = foo;
// Using object fools the C# compiler into really consulting the CLR... which
// allows the conversion, so this prints True
Console.WriteLine(x is sbyte[]);

Joel はコメントで興味深い質問をしました/o

このコードを考えると:

static void Main(string[] args)
{
    sbyte[] baz = new sbyte[0];
    Console.WriteLine(baz is byte[]);
}

csc /o- Code.cs(最適化しないで)でコンパイルすると、コンパイラはとにかくそれを最適化するようです。結果のIL:

IL_0000:  nop
IL_0001:  ldc.i4.0
IL_0002:  newarr     [mscorlib]System.SByte
IL_0007:  stloc.0
IL_0008:  ldc.i4.0
IL_0009:  call       void [mscorlib]System.Console::WriteLine(bool)
IL_000e:  nop
IL_000f:  ret

IL_0008 は 0 (false) をスタックに直接ロードし、次にWriteLineIL_0009 を呼び出します。いいえ、最適化フラグは違いはありません。CLR を参照すると、isinst命令が使用されます。IL_0008 から始まると、おそらく次のようになります。

IL_0008:  ldloc.0
IL_0009:  isinst     uint8[]
IL_000e:  ldnull
IL_000f:  cgt.un
IL_0011:  call       void [mscorlib]System.Console::WriteLine(bool)

オプティマイザの動作に同意します。最適化フラグは、プログラムの動作を変更するべきではありません。

于 2012-08-15T20:51:44.480 に答える
2

VB.NET はコンパイル時に実際に「スロー」します。

「SByte の 1 次元配列」型の式は、「Byte の 1 次元配列」型にはなりません。

if最初のステートメントに相当します。

そして、if同じ CLR であるため、2 番目に相当するものは実行時に期待どおりに成功します (つまり、コード化された例外をスローします)。

于 2012-08-22T06:47:44.337 に答える
1

同じ問題を示す簡単な例を次に示します。

static void Main(string[] args)
{
    bool a = ((object) new byte[0]) is sbyte[];
    bool b = (new byte[0]) is sbyte[];

    Console.WriteLine(a == b); // False
}

(new byte[0]) is sbyte[]C# コンパイラは、コンパイル時にの結果を知っていると判断し、単に を置き換えるため、矛盾が生じfalseます。trueおそらく、CLR の動作とより一貫性を持たせるために、実際には を置き換える必要があります。

私が知る限り、矛盾しているのはこの小さな最適化だけです。これは、要素の型が符号付きまたは符号なしの整数または列挙型である配列として式の両側isが静的に型指定され、整数のサイズが同じである場合にのみ発生します。

良いニュースは、これは矛盾しているように見えるかもしれませんが、C# はそのような式に代入するときに常に警告を発行するということです。false実際には、これは静かに返すよりも役立つと思いますtrue

于 2012-09-25T14:48:05.257 に答える