简体   繁体   中英

why covariant type parameters are used only for the return types of the members?

Why covariant type paramaters like IEnumerable<out T> type T used only for the return type (read-only) or inverse contravariant type parameter like Action<in T> type T used only as parameter type (write-only)? in other word i think there exist a relation between pure covariant concept and c# covariant type paramaters used only for the return types of the members.

Why do covariant type parameters like IEnumerable<out T> have type T used only for the return type?

First off: T does not have to be used only for the return type. For example:

interface I1<out T>{ T M1(); }
interface I2<out T>{ void M2(Action<I1<T>> a); }

In I1<T> , T is used only in return type positions. But in I2 , T is used in an input a , and an I1<T> is the input of the Action , so in a sense it is being used in two input positions here.

But let's consider just a simpler case. Why can we make I1 covariant in T but not contravariant in T ?

The reason is because covariance is safe and contravariance is not. We can see that covariance is safe:

class Animal {}
class Mammal : Animal {}
class Tiger : Mammal {}
class Giraffe : Mammal {}
class C : I1<Mammal> {
    public Mammal M1() { return new Tiger(); }
}
I1<Mammal> i1m = new C(); // Legal
I1<Animal> i1a = i1m; // Legal
Animal a = i1a.M1(); // Returns a tiger; assigned to animal, good!

No matter what C.M1 returns, it is always a Mammal and therefore always an Animal .

But this cannot be legal:

I1<Giraffe> i1g = i1m; // Not legal
Giraffe g = i1g.M1(); // Returns a tiger; assigned to giraffe, bad!

The first line has to be illegal so that the second line never executes.

Now you should have enough information to figure out why contravariance works the way it does. Remember, you can always come back to a simple example and ask yourself "if this was legal, what mistakes could I make later?" The type system is protecting you from making those mistakes!

Exercise: Do this same analysis of I2<T> . Do you see why it is legal to use T in two input positions even though it is out . (Hint: Action is contravariant , so it reverses the direction of assignment compatibility. What happens if you reverse directions twice ?)

So i see where is your problem. Answer should be this way. Let me again use example from MSDN :

static object GetObject() { return null; }  
static void SetObject(object obj) { }  

static string GetString() { return ""; }  
static void SetString(string str) { }  

static void Test()  
{  
    // Covariance. A delegate specifies a return type as object,  
    // but you can assign a method that returns a string.  
    Func<object> del = GetString;  

    // Contravariance. A delegate specifies a parameter type as string,  
    // but you can assign a method that takes an object.  
    Action<string> del2 = SetObject;
    //However you can't use del2 this way, after this assingment:
    //del2(new object);
} 

This is hard to understand and for me it is quiet high level of abstraction.

Covariance

Let's take a closer look to Func<object> del = GetString; You are allowed to do such thing because string derives from object, so as long as you will get a method with return type derives from object you don't have problem with it. Imagine you declare the same del so all you know you will get object so you declare a variable:

object returnedType = del2();

You don't have to care about whether del2 return int or string because they derives from object it will be similar with:

 object returnedType = "string"; //Here we know what is on the left side
 //If we assign to del2 method with return type string.

Contravariance

Now let's take a look to Action<string> del2 = SetObject; Now you assume that you will get a string to method so if somebody, someday will use your delegate with method like SetObject(object obj) so it will be the same as earlier:

object obj= "string"; //Here we know what is on the right

Assuming

it's all about pure polymorphism. In covariance, we except one general type but it changes nothing for us if we get more specific type. In contravariance we know what we will passing, but it doesn't matter if we will assign string to string or string to object. (but we can't assign object to string).

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