类和结构之间存在差异作为协变类型参数

时间:2013-11-01 15:30:39

标签: c# generics covariance

如果在接口中用作协变类型参数的类型是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>); 
}

为什么结构和类之间存在差异

4 个答案:

答案 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对此的引用。虽然系统允许从结构类型到相应的堆对象类型的隐式转换,以及另一种方式的显式转换,但这并不意味着变量和堆对象是相同的类型。实际上,需要转换的事实意味着它们不是。