我刚刚在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???");
}
这将抛出“为什么???”,但不是“不可能!”。它适用于所有相同大小的整数类型的数组。谁可以给我解释一下这个?我糊涂了。我顺便使用.NET 4。
P.S。:我知道我可以使用o.GetType() == typeof(byte[])
获得预期的结果。
答案 0 :(得分:51)
CLR的强制规则指明这是可能的。 C#规则说这是不可能的。 The C# team consciously decided他们会因各种原因容忍这种偏离规范的行为。
为什么CLR允许这样做?可能是因为他们可以方便地实现它。 byte
和sbyte
具有相同的二进制表示形式,因此您可以将byte[]
“视为”sbyte[]
而不会违反内存安全。
同样的技巧适用于具有相同内存布局的其他基本类型。
答案 1 :(得分:28)
有趣的是,在我的问题Why does this Linq Cast Fail when using ToList?
中,我被它咬了一口Jon Skeet(当然)解释说我的问题是C#编译器,无论出于何种原因,认为它们永远不会是同一个东西,并且有助于将其优化为假。但是,CLR 确实让这种情况发生。强制转换为对象会抛出编译器优化,因此它会通过CLR。
his answer的相关部分:
即使在C#中你不能直接将字节[]转换为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)直接加载到堆栈上,然后在IL_0009上调用WriteLine
。所以不,优化标志没有区别。如果要咨询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)
我同意优化器的行为。优化标志不应改变程序的行为。
答案 2 :(得分:2)
VB.NET实际上“抛出”编译时间:
类型'SByte'的1维数组的表达式永远不能是'Byte'的1维数组类型。
相当于第一个if
语句。
并且第二个if
的等价物在运行时按预期成功(即它抛出编码异常),因为它是相同的CLR。
答案 3 :(得分: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
}
出现不一致是因为C#编译器在编译时决定它知道(new byte[0]) is sbyte[]
的结果,而只是替换false
。也许它应该真正替代true
,以便与CLR行为更加一致。
据我所知,只是这种小优化不一致。它仅在is
表达式的两边静态类型为元素类型为有符号或无符号整数或枚举的数组时才会出现,并且整数的大小相同。
好消息是,虽然这可能看起来不一致,但是当C#将false
替换为这样的表达式时,C#总会发出警告 - 实际上,我认为这可能比悄悄地返回true
更有用。