为什么我的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中。
更新:我用这个问题作为博客条目的基础,在这里:
请参阅博客评论以进一步讨论此问题。 感谢您的好问题!
你偶然发现了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 []分配兼容”。
也就是说,数组对于赋值兼容性是协变的。 这实际上是一种破碎的协变性; 有关详细信息,请参阅我的文章。
这不是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”。