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 允许这样做?可能是因为他们可以方便地实现它。bytesbyte具有相同的二进制表示,因此您可以将 abyte[]视为 asbyte[] 而不会违反内存安全性

相同的技巧适用于具有相同内存布局的其他原始类型。

于 2012-08-15T20:49:31.423 回答
28

有趣的是,我在我的问题中被这个问题所困扰,为什么使用 ToList 时这个 Linq Cast 会失败?

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(假)直接加载到堆栈上,然后调用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 的一维数组”类型的表达式永远不能是“Byte 的一维数组”类型。

相当于第一个if语句。

并且第二个的等效项if在运行时按预期成功(即它抛出编码的异常),因为它是相同的 CLR。

于 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
}

出现不一致是因为 C# 编译器决定它(new byte[0]) is sbyte[]在编译时知道false. 也许它应该真正替代true,以与 CLR 行为更加一致。

据我所知,只有这个小小的优化是不一致的。仅当is表达式的两边都静态类型为数组,其元素类型为有符号或无符号整数或枚举,并且整数的大小相同时,才会发生这种情况。

好消息是,虽然这看起来不一致,但 C# 在替换false此类表达式时总是会发出警告——实际上,我认为这可能比安静地返回更有用true

于 2012-09-25T14:48:05.257 回答