2

如果在接口中用作协变类型参数的类型是 a ,则以下测试失败(在最后一个 Assert 上)struct,但如果它是 a 则成功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

4 回答 4

3

这种行为是设计使然,但非常令人困惑,引用官方常见问题解答

仅当类型参数是引用类型时才支持变体。

于 2013-11-01T15:30:39.240 回答
3

通俗地说,因为将引用类型视为其他类型(祖先或后代)只涉及编译器更新其内部簿记结构;在运行时根本不需要更改任何内容,因为所有引用类型的内存表示具有相同的结构(在标准中,这涉及隐式引用转换)。

另一方面,值类型(可能)具有不同的内存表示,因此将值类型 A 的实例视为值类型 B 的实例必然涉及运行时转换。

于 2013-11-01T15:38:14.700 回答
1

因为结构是按值的。没有装箱操作,您不能将结构“转换”到另一个事物(接口)。

“out”只是关于强制转换:它允许您强制转换IEnumerable<MyClass>IEnumerable<MyClassBase>(您将枚举相同的对象,具有不同的类型,没有成本)......但这对于结构完全没有意义(需要装箱)。

于 2013-11-01T15:36:30.387 回答
1

如果 classFooClass和 structFooStruct都实现IFoo,则类型变量FooClass是对 的实现的引用IFoo,但类型变量本身FooStruct是的实现。引用类型可能存在协变的原因是,如果派生自,则每个对 的引用都将是对 a 的引用;如果对 a 的引用传递给一个期望对 a 的引用的方法,则接收到的参数将是对的引用,并且该方法不需要关心它也是对 的引用。IFooTUTUTUUT

协方差不适用于结构类型的原因是类型的值不是对实现Int32的堆对象的引用IComparable<Int32>——它是IComparable<Int32>. 具有参数类型的方法IComparable<Int32>不会期望接收IComparable<Int32>-- 它会期望接收引用的实现。

请注意,某些语言试图假装给定声明Int32 v1; Object v2 = v1;的类型和持有引用v1的对象的类型是相同的。v2实际上,它们是居住在不同宇宙中的不同类型。每当运行时环境看到除System.Enum派生自之外的类时System.ValueType,它都会有效地定义第二种类型,即在与堆类型分开的存储位置类型的宇宙中。如果有人说IComprable<Int32> v3 = v1;,就是要求系统创建一个堆对象类型 Int32的实例,该实例的内容从中加载v1,并存储到v3对此的参考。尽管系统允许从结构类型到相应的堆对象类型的隐式转换,以及相反的显式转换,但这并不意味着变量和堆对象是相同的类型。事实上,需要转换的事实意味着它们不需要。

于 2013-11-01T16:23:49.623 回答