为什么我的c#数组在抛出对象时丢失类型符号信息?

时间:2022-09-11 19:52:35

Investigating a bug, I discovered it was due to this weirdness in c#:

在调查一个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[]);

The output is "True False True True", while I would have expected "bar is byte[]" to return False. Apparently bar is both a byte[] and an sbyte[]? The same happens for other signed/unsigned types like Int32[] vs UInt32[], but not for say Int32[] vs Int64[].

输出是“True False True True”,而我希望“bar is byte[]”返回False。显然bar是一个字节[]和一个sbyte[]?对于其他有符号/无符号类型,如Int32[]和UInt32[],也会发生同样的情况,但对于say Int32[]和Int64[]则不是这样。

Can anyone explain this behavior? This is in .NET 3.5.

有人能解释这种行为吗?这是。net 3.5。

4 个解决方案

#1


66  

UPDATE: I've used this question as the basis for a blog entry, here:

更新:我把这个问题作为博客条目的基础。

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

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

See the blog comments for an extended discussion of this issue. Thanks for the great question!

有关这个问题的扩展讨论,请参阅博客评论。谢谢你的好问题!


You have stumbled across an interesting and unfortunate inconsistency between the CLI type system and the C# type system.

您无意中发现了CLI类型系统与c#类型系统之间有趣而不幸的不一致。

The CLI has the concept of "assignment compatibility". If a value x of known data type S is "assignment compatible" with a particular storage location y of known data type T, then you can store x in y. If not, then doing so is not verifiable code and the verifier will disallow it.

CLI有“分配兼容性”的概念。如果已知数据类型S的值x与已知数据类型T的特定存储位置y“赋值兼容”,那么可以将x存储在y中。

The CLI type system says, for instance, that subtypes of reference type are assignment compatible with supertypes of reference type. If you have a string, you can store it in a variable of type object, because both are reference types and string is a subtype of object. But the opposite is not true; supertypes are not assignment compatible with subtypes. You can't stick something only known to be object into a variable of type string without first casting it.

例如,CLI类型系统说,引用类型的子类型是与引用类型的超类型兼容的赋值。如果有一个字符串,可以将它存储在object类型的变量中,因为它们都是引用类型,而string是对象的子类型。但事实并非如此;超类型不能与子类型兼容。如果不先对类型字符串进行强制转换,就不能将已知的对象插入到类型字符串的变量中。

Basically "assignment compatible" means "it makes sense to stick these exact bits into this variable". The assignment from source value to target variable has to be "representation preserving". See my article on that for details:

基本上,“赋值兼容”意味着“将这些精确的位插入到这个变量中是有意义的”。从源值到目标变量的赋值必须是“表示保留”。有关细节,请参阅我的文章:

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

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

One of the rules of the CLI is "if X is assignment compatible with Y, then X[] is assignment compatible with Y[]".

CLI的规则之一是“如果X是与Y兼容的赋值,那么X[]就是与Y[]兼容的赋值”。

That is, arrays are covariant with respect to assignment compatibility. This is actually a broken kind of covariance; see my article on that for details.

也就是说,数组在分配兼容性方面是协变的。这实际上是一种破碎的协方差;详情请参阅我在这方面的文章。

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

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

That is NOT a rule of C#. C#'s array covariance rule is "if X is a reference type implicitly convertible to reference type Y, then X[] is implicitly convertible to Y[]". That is a subtly different rule, and hence your confusing situation.

这不是c#的规则。c#的数组协方差规则是“如果X是一个可以隐式转换为引用类型Y的引用类型,那么X[]可以隐式转换为Y[]”。这是一个微妙不同的规则,因此你会陷入混乱。

In the CLI, uint and int are assignment compatible. But in C#, the conversion between int and uint is EXPLICIT, not IMPLICIT, and these are value types, not reference types. So in C#, it's not legal to convert an int[] to a uint[].

在CLI中,uint和int是赋值兼容的。但是在c#中,int和uint之间的转换是显式的,而不是隐式的,这些是值类型,而不是引用类型。所以在c#中,将int[]转换为uint[]是不合法的。

But it IS legal in the CLI. So now we are faced with a choice.

但这在CLI中是合法的。所以现在我们面临一个选择。

1) Implement "is" so that when the compiler cannot determine the answer statically, it actually calls a method which checks all the C# rules for identity-preserving convertibility. This is slow, and 99.9% of the time matches what the CLR rules are. But we take the performance hit so as to be 100% compliant with the rules of C#.

1)实现“is”,以便当编译器不能静态地确定答案时,它实际上调用了一个方法,该方法检查所有c#规则的可兑换性。这是缓慢的,并且99.9%的时间与CLR规则匹配。但是我们的性能会受到影响,以便100%符合c#的规则。

2) Implement "is" so that when the compiler cannot determine the answer statically, it does the incredibly fast CLR assignment compatibility check, and live with the fact that this says that a uint[] is an int[], even though that would not actually be legal in C#.

2)实现“is”时,当编译器不能静态地确定答案时,它就会执行令人难以置信的快速CLR分配兼容性检查,并接受这一事实,即uint[]是int[],尽管在c#中这实际上是不合法的。

We chose the latter. It is unfortunate that C# and the CLI specifications disagree on this minor point but we are willing to live with the inconsistency.

我们选择了后者。不幸的是,c#和CLI规范在这一次要问题上存在分歧,但我们愿意接受这种不一致。

#2


9  

Ran the snippet through Reflector:

通过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[] });

The C# compiler is optimizing the first two comparisons (foo is sbyte[] and foo is byte[]). As you can see they have been optimized to foo != null and simply always false.

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

#3


5  

Also interesting:

也很有趣:

    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

#4


0  

Surely the output is correct. bar "is" both sbyte[] and byte[], because it is compatible with both, since bar is merely an object then it "could be" either signed or unsigned.

产出肯定是正确的。bar "是" sbyte[]和byte[],因为它与两者都兼容,因为bar只是一个对象,所以它可以是"有符号的或无符号的"。

"is" is defined as "expression can be cast to type".

“is”定义为“表达式可以转换为类型”。

#1


66  

UPDATE: I've used this question as the basis for a blog entry, here:

更新:我把这个问题作为博客条目的基础。

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

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

See the blog comments for an extended discussion of this issue. Thanks for the great question!

有关这个问题的扩展讨论,请参阅博客评论。谢谢你的好问题!


You have stumbled across an interesting and unfortunate inconsistency between the CLI type system and the C# type system.

您无意中发现了CLI类型系统与c#类型系统之间有趣而不幸的不一致。

The CLI has the concept of "assignment compatibility". If a value x of known data type S is "assignment compatible" with a particular storage location y of known data type T, then you can store x in y. If not, then doing so is not verifiable code and the verifier will disallow it.

CLI有“分配兼容性”的概念。如果已知数据类型S的值x与已知数据类型T的特定存储位置y“赋值兼容”,那么可以将x存储在y中。

The CLI type system says, for instance, that subtypes of reference type are assignment compatible with supertypes of reference type. If you have a string, you can store it in a variable of type object, because both are reference types and string is a subtype of object. But the opposite is not true; supertypes are not assignment compatible with subtypes. You can't stick something only known to be object into a variable of type string without first casting it.

例如,CLI类型系统说,引用类型的子类型是与引用类型的超类型兼容的赋值。如果有一个字符串,可以将它存储在object类型的变量中,因为它们都是引用类型,而string是对象的子类型。但事实并非如此;超类型不能与子类型兼容。如果不先对类型字符串进行强制转换,就不能将已知的对象插入到类型字符串的变量中。

Basically "assignment compatible" means "it makes sense to stick these exact bits into this variable". The assignment from source value to target variable has to be "representation preserving". See my article on that for details:

基本上,“赋值兼容”意味着“将这些精确的位插入到这个变量中是有意义的”。从源值到目标变量的赋值必须是“表示保留”。有关细节,请参阅我的文章:

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

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

One of the rules of the CLI is "if X is assignment compatible with Y, then X[] is assignment compatible with Y[]".

CLI的规则之一是“如果X是与Y兼容的赋值,那么X[]就是与Y[]兼容的赋值”。

That is, arrays are covariant with respect to assignment compatibility. This is actually a broken kind of covariance; see my article on that for details.

也就是说,数组在分配兼容性方面是协变的。这实际上是一种破碎的协方差;详情请参阅我在这方面的文章。

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

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

That is NOT a rule of C#. C#'s array covariance rule is "if X is a reference type implicitly convertible to reference type Y, then X[] is implicitly convertible to Y[]". That is a subtly different rule, and hence your confusing situation.

这不是c#的规则。c#的数组协方差规则是“如果X是一个可以隐式转换为引用类型Y的引用类型,那么X[]可以隐式转换为Y[]”。这是一个微妙不同的规则,因此你会陷入混乱。

In the CLI, uint and int are assignment compatible. But in C#, the conversion between int and uint is EXPLICIT, not IMPLICIT, and these are value types, not reference types. So in C#, it's not legal to convert an int[] to a uint[].

在CLI中,uint和int是赋值兼容的。但是在c#中,int和uint之间的转换是显式的,而不是隐式的,这些是值类型,而不是引用类型。所以在c#中,将int[]转换为uint[]是不合法的。

But it IS legal in the CLI. So now we are faced with a choice.

但这在CLI中是合法的。所以现在我们面临一个选择。

1) Implement "is" so that when the compiler cannot determine the answer statically, it actually calls a method which checks all the C# rules for identity-preserving convertibility. This is slow, and 99.9% of the time matches what the CLR rules are. But we take the performance hit so as to be 100% compliant with the rules of C#.

1)实现“is”,以便当编译器不能静态地确定答案时,它实际上调用了一个方法,该方法检查所有c#规则的可兑换性。这是缓慢的,并且99.9%的时间与CLR规则匹配。但是我们的性能会受到影响,以便100%符合c#的规则。

2) Implement "is" so that when the compiler cannot determine the answer statically, it does the incredibly fast CLR assignment compatibility check, and live with the fact that this says that a uint[] is an int[], even though that would not actually be legal in C#.

2)实现“is”时,当编译器不能静态地确定答案时,它就会执行令人难以置信的快速CLR分配兼容性检查,并接受这一事实,即uint[]是int[],尽管在c#中这实际上是不合法的。

We chose the latter. It is unfortunate that C# and the CLI specifications disagree on this minor point but we are willing to live with the inconsistency.

我们选择了后者。不幸的是,c#和CLI规范在这一次要问题上存在分歧,但我们愿意接受这种不一致。

#2


9  

Ran the snippet through Reflector:

通过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[] });

The C# compiler is optimizing the first two comparisons (foo is sbyte[] and foo is byte[]). As you can see they have been optimized to foo != null and simply always false.

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

#3


5  

Also interesting:

也很有趣:

    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

#4


0  

Surely the output is correct. bar "is" both sbyte[] and byte[], because it is compatible with both, since bar is merely an object then it "could be" either signed or unsigned.

产出肯定是正确的。bar "是" sbyte[]和byte[],因为它与两者都兼容,因为bar只是一个对象,所以它可以是"有符号的或无符号的"。

"is" is defined as "expression can be cast to type".

“is”定义为“表达式可以转换为类型”。