在C#/ .NET中,为什么sbyte 与byte 相同,除非它不是?

我只是在C#/。NET中观察到一个奇怪的现象。

我创build了这个最小的例子来演示:

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。

PS:我知道我可以通过使用o.GetType() == typeof(byte[])来获得预期的结果。

CLR的铸造规则指定这是可能的。 C#规则说这是不可能的。 C#团队有意识地决定 ,由于种种原因,他们会容忍这种偏离规范的行为。

为什么CLR允许这样做? 可能是因为他们可以方便地实施它。 bytesbyte具有相同的二进制表示forms,因此您可以在不影响内存安全的情况下byte[]视为sbyte[]

相同的技巧适用于具有相同内存布局的其他基本types。

有趣的是,我在我的问题中被咬了, 为什么Linq在使用ToList时失败?

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)加载到堆栈上,然后在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) 

我会同意优化器的行为。 优化标志不应该改变你的程序的行为。

VB.NET实际上在编译时“抛出”:

types“SByte的一维数组”的expression不能是types“字节的一维数组”。

相当于第一个if语句。

if成功(如它抛出编码的exception)的第二个等价物如预期的那样在运行时,因为它是相同的CLR。

这是一个简单的例子,显示了同样的问题:

 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的行为。

据我所知,只是这个小小的优化是不一致的。 只有当isexpression式的两边静态types为一个数组,其元素types是一个有符号或无符号整数或一个枚举,整数的大小是相同的。

好消息是虽然这看起来可能不一致,但是C#在将false换成这样的expression式时总会发出警告 – 实际上,我认为这可能比悄悄地返回true更有用。