Rng*_*bus 42 c# arrays types command-line-interface
调查一个bug,我发现这是由于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[]);
Run Code Online (Sandbox Code Playgroud)
输出为"True False True True",而我希望" bar is byte[]
"返回False.显然酒吧既是一个byte[]
又是一个sbyte[]
?同样的情况对于其他符号/无符号类型,如Int32[]
VS UInt32[]
,但不是说Int32[]
VS Int64[]
.
谁能解释这种行为?这是在.NET 3.5中.
Eri*_*ert 66
更新:我使用这个问题作为博客条目的基础,在这里:
有关此问题的详细讨论,请参阅博客评论.谢谢你这个好问题!
您偶然发现了CLI类型系统和C#类型系统之间有趣且不幸的不一致.
CLI具有"赋值兼容性"的概念.如果已知数据类型S的值x与已知数据类型T的特定存储位置y"分配兼容",则可以将x存储在y中.如果没有,那么这样做不是可验证的代码,验证者将不允许它.
例如,CLI类型系统表示引用类型的子类型与引用类型的超类型兼容.如果你有一个字符串,你可以将它存储在一个object类型的变量中,因为它们都是引用类型而string是object的子类型.但事实恰恰相反; 超类型与子类型不兼容.如果没有先将其作为字符串变量,就不能将只有对象的东西粘贴到字符串类型的变量中.
基本上"赋值兼容"意味着"将这些精确位置于此变量中是有意义的".从源值到目标变量的赋值必须是"表示保留".有关详细信息,请参阅我的文章:
http://ericlippert.com/2009/03/03/representation-and-identity/
CLI的一个规则是"如果X与Y兼容,则X []与Y []"分配兼容.
也就是说,数组在赋值兼容性方面是协变的.这实际上是一种破碎的协方差; 有关详细信息,请参阅我的文章.
这不是C#的规则.C#的数组协方差规则是"如果X是隐式可转换为引用类型Y的引用类型,则X []可隐式转换为Y []". 这是一个微妙的不同规则,因此你的情况令人困惑.
在CLI中,uint和int是赋值兼容的.但是在C#中,int和uint之间的转换是EXPLICIT,而不是IMPLICIT,这些是值类型,而不是引用类型.所以在C#中,将int []转换为uint []是不合法的.
但它在CLI中是合法的.所以现在我们面临着一个选择.
1)实现"is",这样当编译器无法静态确定答案时,它实际上会调用一个方法来检查所有C#规则以保持可转换性.这很慢,99.9%的时间与CLR规则相匹配.但我们将性能提升到100%符合C#规则.
2)实现"is",这样当编译器无法静态确定答案时,它会执行令人难以置信的快速CLR分配兼容性检查,并认为这表明uint []是一个int [],即使这会在C#中实际上并不合法.
我们选择后者.遗憾的是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[] });
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)