调查一个错误,我发现这是由于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[]
?其他签名/无符号类型(如Int32[]
vs UInt32[]
)也是如此,但Int32[]
与Int64[]
无关。
任何人都可以解释这种行为吗?这是在.NET 3.5中。
答案 0 :(得分: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规范在这个小问题上存在分歧,但我们愿意忍受不一致。
答案 1 :(得分:9)
通过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
。
答案 2 :(得分:5)
同样有趣:
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
答案 3 :(得分:0)
当然输出是正确的。 bar“is”既是sbyte []又是byte [],因为它与两者兼容,因为bar只是一个对象,所以它“可以”有符号或无符号。
“is”被定义为“表达式可以转换为类型”。