如果在接口中用作协变类型参数的类型是struct
,则以下测试失败(在最后一个断言上),但如果它是class
则成功。
interface IOuter { }
interface IOuter<out T> : IOuter { T Value { get; } }
interface IInner { }
struct Inner : IInner { }
class Outer : IOuter<Inner> { public Inner Value { get { return new Inner(); } } }
[TestMethod()]
public void ContravarianceTest()
{
var a = new Outer();
Assert.IsTrue(a is IOuter<Inner>);
// Fails here if Inner is a struct. Succeeds if Inner is a class.
Assert.IsTrue(a is IOuter<IInner>);
}
为什么结构和类之间存在差异?
答案 0 :(得分:3)
此行为为by design but extremely confusing,引用official FAQ:
仅当类型参数是引用类型时才支持方差。
答案 1 :(得分:3)
用外行人的话来说,因为将引用类型视为其他类型(祖先或后代)只涉及编译器更新其内部簿记结构;什么都不需要在运行时更改,因为所有引用类型的内存中表示具有相同的结构(在标准中这涉及隐式引用转换)。
另一方面,值类型具有(可能)不同的内存中表示,因此将值类型A的实例视为值类型B的实例必然涉及运行时转换。
答案 2 :(得分:1)
因为结构是按值的。 在没有装箱操作的情况下,你不能将结构“转换”为另一个东西(接口)。
“out”只是关于强制转换:它允许你将IEnumerable<MyClass>
强制转换为IEnumerable<MyClassBase>
(你将枚举相同的对象,不同的类型,没有任何成本)......但这并不能使结构的意义(拳击是必需的)。
答案 3 :(得分:1)
如果类FooClass
和结构FooStruct
都实现了IFoo
,则FooClass
类型的变量是{em>引用到{{{}的实现1}},但IFoo
类型的变量是本身,是FooStruct
的一种实现。可以使用引用类型进行协方差的原因是,如果IFoo
来自T
,则对U
的每个引用都将引用T
;如果对U
的引用传递的方法需要引用T
,则接收的参数将是对U
的引用,并且该方法无需关心它也是引用到U
。
协方差不适用于结构类型的原因是类型T
的值不是对实现Int32
的堆对象的引用 - 它是 IComparable<Int32>
的实施。参数类型IComparable<Int32>
的方法不会期望接收IComparable<Int32>
的实现 - 它将期望接收引用。
请注意,某些语言会假设假定IComparable<Int32>
声明Int32 v1; Object v2 = v1;
的类型以及v1
拥有引用的对象的类型是同一个。实际上,它们是居住在不同宇宙中的不同类型。只要运行时环境看到从v2
派生的System.Enum
以外的类,它就会在与堆类型分开的存储位置类型的Universe中有效地定义第二种类型。如果有人说System.ValueType
,那么正在做的是要求系统创建堆对象类型 IComprable<Int32> v3 = v1;
的实例,其内容从Int32
加载,并存储进入v1
对此的引用。虽然系统允许从结构类型到相应的堆对象类型的隐式转换,以及另一种方式的显式转换,但这并不意味着变量和堆对象是相同的类型。实际上,需要转换的事实意味着它们不是。