简体   繁体   English

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

[英]There is a difference between classes and structs as covariant type parameters

The following test fails (on the last Assert) if the type used as covariant a type parameter in an interface is a struct , but succeeds if it's a class . 如果在接口中用作协变类型参数的类型是struct ,则以下测试失败(在最后一个Assert上),但如果它是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>); 
}

Why is there a difference between structs and classes ? 为什么结构和类之间有区别

This behavior is by design but extremely confusing , to quote the official FAQ : 这种行为是设计但非常混乱 ,引用官方FAQ

Variance is supported only if a type parameter is a reference type. 仅当类型参数是引用类型时才支持差异。

In layman's terms, because treating a reference type as some other type (either ancestor or descendant) involves nothing more than the compiler updating its internal bookkeeping structures; 通俗地说,因为将引用类型视为其他类型(祖先或后代)只涉及编译器更新其内部簿记结构; nothing at all needs to change at runtime because the in-memory representation of all reference types has the same structure (in standardese this involves an implicit reference conversion ). 什么都不需要在运行时更改,因为所有引用类型的内存中表示具有相同的结构(在标准中,这涉及隐式引用转换 )。

On the other hand, value types have (potentially) distinct in-memory representation, so treating an instance of value type A as an instance of value type B necessarily involves runtime conversions. 另一方面,值类型具有(可能)不同的内存中表示,因此将值类型A的实例视为值类型B的实例必然涉及运行时转换。

Because a struct is by-value. 因为结构是按值的。 You cannot "cast" a struct to another thing (interface) without a boxing operation. 没有装箱操作,你不能将结构“转换”为另一个东西(接口)。

"out" is just about casting: It allows you to cast IEnumerable<MyClass> to IEnumerable<MyClassBase> (you will enumerate the same objects, with different type, with no cost) ... but that doesnt makes sense at all for structures (boxing is required). “out”只是关于强制转换:它允许你将IEnumerable<MyClass>IEnumerable<MyClassBase> (你将枚举相同的对象,具有不同的类型,没有成本)......但这对于结构来说根本没有意义(拳击是必需的)。

If class FooClass and struct FooStruct both implement IFoo , a variable of type FooClass is a reference to an implementation of IFoo , but a variable of type FooStruct is, itself , an implementation of IFoo . 如果类FooClass和struct FooStruct都实现IFoo ,则类型为FooClass的变量是对IFoo实现的引用 ,但FooStruct类型的变量本身就是 IFoo的实现。 The reason that covariance is possible with reference types is that if T derives from U , every reference to T will be a reference to a U ; 可以使用引用类型进行协方差的原因是,如果T派生自U ,则对T每个引用都将是对U的引用; if a reference to a T is passed a method that expects a reference to a U , the received parameter will be a reference to U and the method need not care that it's also a reference to T . 如果对T的引用传递了一个期望引用U ,则接收的参数将是对U的引用,并且该方法不必关心它也是对T的引用。

The reason covariance doesn't work with structure types is that a value of type Int32 isn't a reference to a heap object that implements IComparable<Int32> --it is an implementation of IComparable<Int32> . 协方差原因不与结构类型的工作是该类型的值Int32不是对实现一个堆对象的引用IComparable<Int32> --IT 的一个实现IComparable<Int32> A method with a parameter type IComparable<Int32> won't be expecting to receive an implementation of IComparable<Int32> --it will be expecting to receive a reference. 具有参数类型IComparable<Int32>将不会期望接收IComparable<Int32> - 它将期望接收引用。

Note that some languages try to pretend that given the declarations Int32 v1; Object v2 = v1; 注意,有些语言试图假装给出Int32 v1; Object v2 = v1;的声明Int32 v1; Object v2 = v1; Int32 v1; Object v2 = v1; the type of v1 and the type of the object to which v2 holds a reference are one and the same. v1的类型和v2保存引用的对象的类型是同一个。 In reality, they're different types which inhabit different universes. 实际上,它们是居住在不同宇宙中的不同类型。 Any time the runtime environment sees a class other than System.Enum derived from System.ValueType , it effectively defines a second type, in a universe of storage-location types which are separate from the heap types. 只要运行时环境看到派生自System.ValueType System.Enum以外的类,它就会在与堆类型分开的存储位置类型的Universe中有效地定义第二种类型。 If one says IComprable<Int32> v3 = v1; 如果有人说IComprable<Int32> v3 = v1; , what one is doing is asking the system to create an instance of heap object type Int32 whose contents are loaded from v1 , and store into v3 a reference to that. ,正在做的是要求系统创建一个堆对象类型 Int32的实例,其内容从v1加载,并存储到v3的引用。 Although the system allows implicit conversions from structure types to the corresponding heap-object types, and explicit conversions the other way, that doesn't mean the variables and heap objects are the same type. 虽然系统允许从结构类型到相应的堆对象类型的隐式转换,以及另一种方式的显式转换,但这并不意味着变量和堆对象是相同的类型。 Indeed, the fact that conversion is needed implies that they are not. 实际上,需要转换的事实意味着它们不是。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM