简体   繁体   English

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

[英]In C#/.NET why is sbyte[] the same as byte[] except that it's not?

I just observed a weird phenomenon in C#/.NET. 我刚刚在C#/ .NET中发现了一个奇怪的现象。

I created this minimal example to demonstrate: 我创建了这个最小的例子来演示:

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

object o = new sbyte[5];

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

This will throw "Why???", but not "Impossible!". 这将抛出“为什么???”,但不是“不可能!”。 It works for all arrays of integral types of the same size. 它适用于所有相同大小的整数类型的数组。 Can someone explain this to me? 谁可以给我解释一下这个? I'm confused. 我糊涂了。 I'm using .NET 4 by the way. 我顺便使用.NET 4。

PS: I know that I can get the expected result by using o.GetType() == typeof(byte[]) . PS:我知道我可以通过使用o.GetType() == typeof(byte[])来获得预期的结果。

The CLR rules of casting specify that this is possible. CLR的强制规则指出这是可能的。 The C# rules say it is not possible. C#规则说这是不可能的。 The C# team consciously decided that they would tolerate this deviation from the spec for various reasons. C#团队有意识地决定 ,出于各种原因,他们会容忍这种偏离规范的行为。

Why does the CLR allow this? 为什么CLR允许这样做? Probably because they can conveniently implement it. 可能是因为他们可以方便地实现它。 byte and sbyte have the same binary representation so you can "treat" a byte[] as an sbyte[] without violating memory safety . bytesbyte具有相同的二进制表示,因此您可以将byte[]视为sbyte[] 而不会违反内存安全性

The same trick works for other primitive types with the same memory layout. 同样的技巧适用于具有相同内存布局的其他基本类型。

Funny, I got bitten by that in my question, Why does this Linq Cast Fail when using ToList? 有趣的是,在我的问题中,我被它咬了, 为什么使用ToList时这个Linq Cast会失败?

Jon Skeet (of course) explains that my problem is the C# compiler, for whatever reason, thinks they could never be the same thing, and helpfully optimizes it to false. Jon Skeet (当然)解释说我的问题是C#编译器,无论出于什么原因,认为它们永远不会是同一个东西,并且有助于将它优化为假。 However, the CLR does let this happen. 但是,CLR 确实让这种情况发生。 The cast to object throws off the compiler optimization, so it goes through the CLR. 强制转换为对象会抛出编译器优化,因此它会通过CLR。

The relevant part from his answer : 他回答的相关部分:

Even though in C# you can't cast a byte[] to an sbyte[] directly, the CLR allows it: 即使在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 asked an interesting question in the comments, "Is this behavior controlled by the Optimize Code flag ( /o to the compiler)?" Joel在评论中提出了一个有趣的问题,“这种行为是否由Optimize Code标志(编译器/o )控制?”

Given this code: 鉴于此代码:

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

And compiled with csc /o- Code.cs (don't optimize), it appears that the compiler optimizes it anyway. 并使用csc /o- Code.cs编译(不进行优化),看起来编译器无论如何都会优化它。 The resulting IL: 由此产生的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 loads 0 (false) directly onto the stack, then calls WriteLine on IL_0009. IL_0008将0(false)直接加载到堆栈上,然后在IL_0009上调用WriteLine So no, the optimization flag does not make a difference. 所以不,优化标志没有区别。 If the CLR were to be consulted, the isinst instruction would get used. 如果要咨询CLR,则会使用isinst指令。 It would probably look something like this starting from IL_0008: 从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)

I would agree with the optimizer's behavior. 我同意优化器的行为。 The optimization flag should not change the behavior of your program. 优化标志不应更改程序的行为。

VB.NET actually "throws" at compile time: VB.NET实际上在编译时“抛出”:

Expression of type '1-dimensional array of SByte' can never be of type '1-dimensional array of Byte'. 类型'SByte'的1维阵列的表达永远不能是'Byte'的1维阵列。

on the equivalent of the first if statement. 相当于第一个if语句。

And the equivalent of the second if succeeds (ie it throws the coded exception) at runtime as expected because it is the same CLR. 并且等效于第二个if成功(即它抛出编码异常)在运行时如预期的那样,因为它是相同的CLR。

Here's a simpler example that shows the same issue: 这是一个更简单的例子,显示了同样的问题:

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
}

The inconsistency arises because the C# compiler decides that it knows the result of (new byte[0]) is sbyte[] at compile time, and just substitutes false . 出现不一致是因为C#编译器在编译时确定它知道(new byte[0]) is sbyte[] ,并且只是替换false Perhaps it should really substitute true , to be more consistent with the CLR behaviour. 也许它应该真的替换为true ,以便与CLR行为更加一致。

As far as I can tell, it's only this little optimization that's inconsistent. 据我所知,只有这个小优化是不一致的。 It occurs only when both sides of the is expression are statically typed as an array whose element type is a signed or unsigned integer or an enum, and the sizes of the integers are the same. 仅当is表达式的两边静态类型为元素类型为有符号或无符号整数或枚举的数组时才会出现,并且整数的大小相同。

The good news is that while this might seem inconsistent, C# will always issue a warning when it substitutes false into such expressions – in practice, I think this might be more useful than quietly returning true . 好消息是,虽然这可能看起来不一致,但C#会在将false替换为这样的表达式时发出警告 - 实际上,我认为这可能比静默返回true更有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM