为什么我的C#数组在转换为对象时会丢失类型符号信息?

时间:2009-07-24 17:18:56

标签: c# arrays types command-line-interface

调查一个错误,我发现这是由于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中。

4 个答案:

答案 0 :(得分:66)

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

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

有关此问题的详细讨论,请参阅博客评论。谢谢你提出的好问题!


您偶然发现了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 []”分配兼容。

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

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

这不是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”被定义为“表达式可以转换为类型”。