简体   繁体   中英

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 .

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 :

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.

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).

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 . 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 ; 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 .

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> . 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.

Note that some languages try to pretend that given the declarations 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. 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. If one says 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. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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