为什么我的C#数组在input对象时会丢失types符号信息?

调查一个错误,我发现这是由于这种奇怪的C#中:

sbyte[] foo = new sbyte[10]; object bar = foo; Console.WriteLine("{0} {1} {2} {3}", foo is sbyte[], foo is byte[], bar is sbyte[], bar is byte[]); 

输出是“True False True True”,而我期望“ bar is byte[] ”返回False。 显然bar是byte[]sbyte[] ? 其他有符号/无符号types,如Int32[] vs UInt32[] ,但不是说Int32[] vs Int64[]

任何人都可以解释此行为? 这是在.NET 3.5中。

更新:我用这个问题作为博客条目的基础,在这里:

http://blogs.msdn.com/ericlippert/archive/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent.aspx

请参阅博客评论以进一步讨论此问题。 感谢您的好问题!


你偶然发现了CLItypes系统和C#types系统之间的一个有趣和不幸的不一致之处。

CLI具有“分配兼容性”的概念。 如果已知数据typesS的值x与已知数据typesT的特定存储位置y是“赋值兼容的”,则可以将x存储在y中。 如果不是,那么这样做不是可validation的代码,validation者将不允许它。

例如,CLItypes系统说,引用types的子types是与引用types的超types分配兼容的。 如果你有一个string,你可以将它存储在一个objecttypes的variables中,因为它们都是引用types,而string是对象的一个​​子types。 但事实恰恰相反, 超types不是与子types兼容的。 你不能把一些只知道是对象的东西粘到一个stringtypes的variables上,而不能先转换它。

基本上“赋值兼容”的意思是“将这些精确的位粘贴到这个variables中是有意义的”。 从源值到目标variables的分配必须是“表示保留”。 有关详细信息,请参阅我的文章:

http://ericlippert.com/2009/03/03/representation-and-identity/

CLI的一个规则是“如果X与Y分配兼容,那么X []与Y []分配兼容”。

也就是说,数组对于赋值兼容性是协变的。 这实际上是一种破碎的协变性; 有关详细信息,请参阅我的文章。

http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx

这不是C#的规则。 C#的数组协变规则是“如果X是隐式转换为引用typesY的引用types,则X []隐式转换为Y []”。 这是一个微妙的不同的规则,因此你的混乱的情况。

在CLI中,uint和int是赋值兼容的。 但在C#中,int和uint之间的转换是EXPLICIT,而不是IMPLICIT,这些是值types,而不是引用types。 所以在C#中,将int []转换为uint []是不合法的。

但是在CLI中它是合法的。 所以现在我们面临着一个select。

1)实现“is”,这样当编译器不能静态地确定答案时,它实际上会调用一个方法来检查所有C#规则的身份保持可转换性。 这很慢,99.9%的时间与CLR规则相匹配。 但是,我们把性能打到100%符合C#的规则。

2)实现“是”,这样当编译器不能静态地确定答案的时候,它做了令人难以置信的快速的CLR分配兼容性检查,并且认为uint []是一个int [],在C#中实际上并不合法。

我们select了后者。 不幸的是,C#和CLI规范在这个小问题上不同意,但我们愿意忍受这种不一致。

通过Reflector运行代码片段:

 sbyte[] foo = new sbyte[10]; object bar = foo; Console.WriteLine("{0} {1} {2} {3}", new object[] { foo != null, false, bar is sbyte[], bar is byte[] }); 

C#编译器正在优化前两个比较( foo is sbyte[]foo is byte[] )。 正如你所看到的,它们已经被优化为foo != null并且总是为false

也有趣的是:

  sbyte[] foo = new sbyte[] { -1 }; var x = foo as byte[]; // doesn't compile object bar = foo; var f = bar as byte[]; // succeeds var g = f[0]; // g = 255 

当然输出是正确的。 bar“是”sbyte []和byte [],因为它与两者兼容,因为bar只是一个对象,所以它可能是有符号或无符号的。

“is”被定义为“expression式可以转换为types”。