问题
ok i read a bit on this topic on stackoverflow, watched this & this, but still a bit confused about co/contra-variance.
from here
Covariance allows a \"bigger\" (less specific) type to be substituted in an API where the original type is only used in an \"output\" position (e.g. as a return value). Contravariance allows a \"smaller\" (more specific) type to be substituted in an API where the original type is only used in an \"input\" position.
i know it has to do with type safety.
about the in/out thing. can i say i use in when i need to write to it, and out when its read only. and in means contra-variance, out co-variance. but from the explanation above...
and here
For example, a
List<Banana>can\'t be treated as aList<Fruit>becauselist.Add(new Apple())is valid for List but not forList<Banana>.
so shouldn\'t it be, if i were to use in/ am going to write to the object, it must be bigger more generic.
i know this question has been asked but still very confused.
回答1:
Both covariance and contravariance in C# 4.0 refer to the ability of using a derived class instead of base class. The in/out keywords are compiler hints to indicate whether or not the type parameters will be used for input and output.
Covariance
Covariance in C# 4.0 is aided by out keyword and it means that a generic type using a derived class of the out type parameter is OK. Hence
IEnumerable<Fruit> fruit = new List<Apple>();
Since Apple is a Fruit, List<Apple> can be safely used as IEnumerable<Fruit>
Contravariance
Contravariance is the in keyword and it denotes input types, usually in delegates. The principle is the same, it means that the delegate can accept more derived class.
public delegate void Func<in T>(T param);
This means that if we have a Func<Fruit>, it can be converted to Func<Apple>.
Func<Fruit> fruitFunc = (fruit)=>{};
Func<Apple> appleFunc = fruitFunc;
Why are they called co/contravariance if they are basically the same thing?
Because even though the principle is the same, safe casting from derived to base, when used on the input types, we can safely cast a less derived type (Func<Fruit>) to a more derived type (Func<Apple>), which makes sense, since any function that takes Fruit, can also take Apple.
回答2:
I had to think long and hard on how to explain this well. Explaining is seems to be just as hard as understanding it.
Imagine you have a base class Fruit. And you have two subclasses Apple and Banana.
Fruit
/ \
Banana Apple
You create two objects:
Apple a = new Apple();
Banana b = new Banana();
For both of these objects you can typecast them into the Fruit object.
Fruit f = (Fruit)a;
Fruit g = (Fruit)b;
You can treat derived classes as if they were their base class.
However you cannot treat a base class like it was a derived class
a = (Apple)f; //This is incorrect
Lets apply this to the List example.
Suppose you created two Lists:
List<Fruit> fruitList = new List<Fruit>();
List<Banana> bananaList = new List<Banana>();
You can do something like this...
fruitList.Add(new Apple());
and
fruitList.Add(new Banana());
because it is essentially typecasting them as you add them into the list. You can think of it like this...
fruitList.Add((Fruit)new Apple());
fruitList.Add((Fruit)new Banana());
However, applying the same logic to the reverse case raises some red flags.
bananaList.Add(new Fruit());
is the same as
bannanaList.Add((Banana)new Fruit());
Because you cannot treat a base class like a derived class this produces errors.
Just in case your question was why this causes errors I'll explain that too.
Here's the Fruit class
public class Fruit
{
public Fruit()
{
a = 0;
}
public int A { get { return a; } set { a = value } }
private int a;
}
and here's the Banana class
public class Banana: Fruit
{
public Banana(): Fruit() // This calls the Fruit constructor
{
// By calling ^^^ Fruit() the inherited variable a is also = 0;
b = 0;
}
public int B { get { return b; } set { b = value; } }
private int b;
}
So imagine that you again created two objects
Fruit f = new Fruit();
Banana ba = new Banana();
remember that Banana has two variables "a" and "b", while Fruit only has one, "a". So when you do this...
f = (Fruit)b;
f.A = 5;
You create a complete Fruit object. But if you were to do this...
ba = (Banana)f;
ba.A = 5;
ba.B = 3; //Error!!!: Was "b" ever initialized? Does it exist?
The problem is that you don't create a complete Banana class.Not all the data members are declared / initialized.
Now that I'm back from the shower and got my self a snack heres where it gets a little complicated.
In hindsight I should have dropped the metaphor when getting into the complicated stuff
lets make two new classes:
public class Base
public class Derived : Base
They can do whatever you like
Now lets define two functions
public Base DoSomething(int variable)
{
return (Base)DoSomethingElse(variable);
}
public Derived DoSomethingElse(int variable)
{
// Do stuff
}
This is kind of like how "out" works you should always be able to use a derived class as if it were a base class, lets apply this to an interface
interface MyInterface<T>
{
T MyFunction(int variable);
}
The key difference between out/in is when the Generic is used as a return type or a method parameter, this the the former case.
lets define a class that implements this interface:
public class Thing<T>: MyInterface<T> { }
then we create two objects:
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
If you were do this:
base = derived;
You would get an error like "cannot implicitly convert from..."
You have two choices, 1) explicitly convert them or, 2) tell the complier to implicitly convert them.
base = (MyInterface<Base>)derived; // #1
or
interface MyInterface<out T> // #2
{
T MyFunction(int variable);
}
The second case comes in to play if your interface looks like this:
interface MyInterface<T>
{
int MyFunction(T variable); // T is now a parameter
}
relating it to the two functions again
public int DoSomething(Base variable)
{
// Do stuff
}
public int DoSomethingElse(Derived variable)
{
return DoSomething((Base)variable);
}
hopefully you see how the situation has reversed but is essentially the same type of conversion.
Using the same classes again
public class Base
public class Derived : Base
public class Thing<T>: MyInterface<T> { }
and the same objects
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
if you try to set them equal
base = derived;
your complier will yell at you again, you have the same options as before
base = (MyInterface<Base>)derived;
or
interface MyInterface<in T> //changed
{
int MyFunction(T variable); // T is still a parameter
}
Basically use out when the generic is only going to be used as a return type of the interface methods. Use in when it is going to be used as a Method parameter. The same rules apply when using delegates too.
There are strange exceptions but I'm not going to worry about them here.
Sorry for any careless mistakes in advance =)
回答3:
Let me share my take on this topic.
Disclaimer: ignore null assignments, I'm using them to keep the code relatively short and they are just enough to see what compiler wants to tell us.
Let's start with a hierarchy of classes:
class Animal { }
class Mammal : Animal { }
class Dog : Mammal { }
Now define some interfaces, to illustrate what in and out generic modifiers actually do:
interface IInvariant<T>
{
T Get(); // ok, an invariant type can be both put into and returned
void Set(T t); // ok, an invariant type can be both put into and returned
}
interface IContravariant<in T>
{
//T Get(); // compilation error, cannot return a contravariant type
void Set(T t); // ok, a contravariant type can only be **put into** our class (hence "in")
}
interface ICovariant<out T>
{
T Get(); // ok, a covariant type can only be **returned** from our class (hence "out")
//void Set(T t); // compilation error, cannot put a covariant type into our class
}
Ok, so why bother using interfaces with in and out modifiers if they restrict us? Let's see:
Invariance
Lets start with invariance (no in, no out modifiers)
Invariance experiment
Consider IInvariant<Mammal>
IInvariant<Mammal>.Get()- returns a MammalIInvariant<Mammal>.Set(Mammal)- accepts a Mammal
What if we try: IInvariant<Mammal> invariantMammal = (IInvariant<Animal>)null?
- Whoever calls
IInvariant<Mammal>.Get()expects a Mammal, butIInvariant<Animal>.Get()- returns an Animal. Not every Animal is a Mammal so it's incompatible. - Whoever calls
IInvariant<Mammal>.Set(Mammal)expects that a Mammal can be passed. SinceIInvariant<Animal>.Set(Animal)accepts any Animal (including Mammal), it's compatible - CONCLUSION: such assignment is incompatible
And what if we try: IInvariant<Mammal> invariantMammal = (IInvariant<Dog>)null?
- Whoever calls
IInvariant<Mammal>.Get()expects a Mammal,IInvariant<Dog>.Get()- returns a Dog, every Dog is a Mammal, so it's compatible. - Whoever calls
IInvariant<Mammal>.Set(Mammal)expects that a Mammal can be passed. SinceIInvariant<Dog>.Set(Dog)accepts only Dogs (and not every Mammal as a Dog), it's incompatible. - CONCLUSION: such assignment is incompatible
Let's check if we're right
IInvariant<Animal> invariantAnimal1 = (IInvariant<Animal>)null; // ok
IInvariant<Animal> invariantAnimal2 = (IInvariant<Mammal>)null; // compilation error
IInvariant<Animal> invariantAnimal3 = (IInvariant<Dog>)null; // compilation error
IInvariant<Mammal> invariantMammal1 = (IInvariant<Animal>)null; // compilation error
IInvariant<Mammal> invariantMammal2 = (IInvariant<Mammal>)null; // ok
IInvariant<Mammal> invariantMammal3 = (IInvariant<Dog>)null; // compilation error
IInvariant<Dog> invariantDog1 = (IInvariant<Animal>)null; // compilation error
IInvariant<Dog> invariantDog2 = (IInvariant<Mammal>)null; // compilation error
IInvariant<Dog> invariantDog3 = (IInvariant<Dog>)null; // ok
THIS ONE IS IMPORTANT: It's worth noticing that depending on whether the generic type parameter is higher or lower in class hierarchy, the generic types themselves are incompatible for different reasons.
Ok, so let's find out how could we exploit it.
Covariance (out)
You have covariance when you use out generic modifier (see above)
If our type looks like: ICovariant<Mammal>, it declares 2 things:
- Some of my methods return a Mammal (hence
outgeneric modifier) - this is boring - None of my methods accept a Mammal - this is interesting though, because this is the actual restriction imposed by the
outgeneric modifier
How can we benefit from out modifier restrictions? Look back at the results of the "Invariance experiment" above. Now try to see what happens when make the same experiment for covariance?
Covariance experiment
What if we try: ICovariant<Mammal> covariantMammal = (ICovariant<Animal>)null?
- Whoever calls
ICovariant<Mammal>.Get()expects a Mammal, butICovariant<Animal>.Get()- returns an Animal. Not every Animal is a Mammal so it's incompatible. ICovariant.Set(Mammal)- this is no longer an issue thanks to theoutmodifier restrictions!- CONCLUSION such assignment is incompatible
And what if we try: ICovariant<Mammal> covariantMammal = (ICovariant<Dog>)null?
- Whoever calls
ICovariant<Mammal>.Get()expects a Mammal,ICovariant<Dog>.Get()- returns a Dog, every Dog is a Mammal, so it's compatible. ICovariant.Set(Mammal)- this is no longer an issue thanks to theoutmodifier restrictions!- CONCLUSION such assignment is COMPATIBLE
Let's confirm it with the code:
ICovariant<Animal> covariantAnimal1 = (ICovariant<Animal>)null; // ok
ICovariant<Animal> covariantAnimal2 = (ICovariant<Mammal>)null; // ok!!!
ICovariant<Animal> covariantAnimal3 = (ICovariant<Dog>)null; // ok!!!
ICovariant<Mammal> covariantMammal1 = (ICovariant<Animal>)null; // compilation error
ICovariant<Mammal> covariantMammal2 = (ICovariant<Mammal>)null; // ok
ICovariant<Mammal> covariantMammal3 = (ICovariant<Dog>)null; // ok!!!
ICovariant<Dog> covariantDog1 = (ICovariant<Animal>)null; // compilation error
ICovariant<Dog> covariantDog2 = (ICovariant<Mammal>)null; // compilation error
ICovariant<Dog> covariantDog3 = (ICovariant<Dog>)null; // ok
Contravariance (in)
You have contravariance when you use in generic modifier (see above)
If our type looks like: IContravariant<Mammal>, it declares 2 things:
- Some of my methods accept a Mammal (hence
ingeneric modifier) - this is boring - None of my methods return a Mammal - this is interesting though, because this is the actual restriction imposed by the
ingeneric modifier
Contravariance experiment
What if we try: IContravariant<Mammal> contravariantMammal = (IContravariant<Animal>)null?
- this is no longer an issue thanks to theIContravariant<Mammal>.Get()inmodifier restrictions!- Whoever calls
IContravariant<Mammal>.Set(Mammal)expects that a Mammal can be passed. SinceIContravariant<Animal>.Set(Animal)accepts any Animal (including Mammal), it's compatible - CONCLUSION: such assignment is COMPATIBLE
And what if we try: IContravariant<Mammal> contravariantMammal = (IContravariant<Dog>)null?
- this is no longer an issue thanks to theIContravariant<Mammal>.Get()inmodifier restrictions!- Whoever calls
IContravariant<Mammal>.Set(Mammal)expects that a Mammal can be passed. SinceIContravariant<Dog>.Set(Dog)accepts only Dogs (and not every Mammal as a Dog), it's incompatible. - CONCLUSION: such assignment is incompatible
Let's confirm it with the code:
IContravariant<Animal> contravariantAnimal1 = (IContravariant<Animal>)null; // ok
IContravariant<Animal> contravariantAnimal2 = (IContravariant<Mammal>)null; // compilation error
IContravariant<Animal> contravariantAnimal3 = (IContravariant<Dog>)null; // compilation error
IContravariant<Mammal> contravariantMammal1 = (IContravariant<Animal>)null; // ok!!!
IContravariant<Mammal> contravariantMammal2 = (IContravariant<Mammal>)null; // ok
IContravariant<Mammal> contravariantMammal3 = (IContravariant<Dog>)null; // compilation error
IContravariant<Dog> contravariantDog1 = (IContravariant<Animal>)null; // ok!!!
IContravariant<Dog> contravariantDog2 = (IContravariant<Mammal>)null; // ok!!!
IContravariant<Dog> contravariantDog3 = (IContravariant<Dog>)null; // ok
BTW, this feels a bit counterintuitive, doesn't it?
// obvious
Animal animal = (Dog)null; // ok
Dog dog = (Animal)null; // compilation error, not every Animal is a Dog
// but this looks like the other way around
IContravariant<Animal> contravariantAnimal = (IContravariant<Dog>) null; // compilation error
IContravariant<Dog> contravariantDog = (IContravariant<Animal>) null; // ok
Why not both?
So can we use both in and out generic modifiers? - obviously not.
Why? Look back at what restrictions do in and out modifiers impose. If we wanted to make our generic type parameter both covariant and contravariant, we would basically say:
- None of the methods of our interface returns
T - None of the methods of our interface accepts
T
Which would essentially make our generic interface non-generic.
How to remember it?
You can use my tricks :)
- "covariant" is shorter than "contravaraint" and this opposite to the lengths of their modifiers ("out" and "in" respectively)
- contravaraint is a little counterintuitive (see the example above)
回答4:
Covariance is pretty easy to understand. It's natural. Contravariance is more confusing.
Take a close look at this example from MSDN. See how SortedList expects an IComparer, but they are passing in a ShapeAreaComparer : IComparer. The Shape is the "bigger" type (it's in the signature of the callee, not the caller), but contravariance allows the "smaller" type - the Circle - to be substituted for everywhere in the ShapeAreaComparer that would normally take a Shape.
Hope that helps.
回答5:
In Jons words:
Covariance allows a "bigger" (less specific) type to be substituted in an API where the original type is only used in an "output" position (e.g. as a return value). Contravariance allows a "smaller" (more specific) type to be substituted in an API where the original type is only used in an "input" position.
I found his explanation confusing at first - but it made sense to me once to be substitued is emphasised, combined with the example from the C# programming guide:
// Covariance.
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;
// Contravariance.
// Assume that the following method is in the class:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument
// is assigned to an object instantiated with a more derived type argument.
// Assignment compatibility is reversed.
Action<string> actString = actObject;
The converter delegate helps me to understand it:
delegate TOutput Converter<in TInput, out TOutput>(TInput input);
TOutput represents covariance where a method returns a more specific type.
TInput represents contravariance where a method is passed a less specific type.
public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }
public static Poodle ConvertDogToPoodle(Dog dog)
{
return new Poodle() { Name = dog.Name };
}
List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();
回答6:
Before coming to topic, lets have a quick refresher:
Base class reference can hold a derived class object BUT not vice-versa.
Covariance: Covariance lets you to pass a derived type object where a base type object is expected Covariance can be applied on delegate, generic, array, interface, etc.
Contravariance: Contravariance is applied to parameters. It allows a method with the parameter of a base class to be assigned to a delegate that expects the parameter of a derived class
Have a look at simple example below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CovarianceContravarianceDemo
{
//base class
class A
{
}
//derived class
class B : A
{
}
class Program
{
static A Method1(A a)
{
Console.WriteLine("Method1");
return new A();
}
static A Method2(B b)
{
Console.WriteLine("Method2");
return new A();
}
static B Method3(B b)
{
Console.WriteLine("Method3");
return new B();
}
public delegate A MyDelegate(B b);
static void Main(string[] args)
{
MyDelegate myDel = null;
myDel = Method2;// normal assignment as per parameter and return type
//Covariance, delegate expects a return type of base class
//but we can still assign Method3 that returns derived type and
//Thus, covariance allows you to assign a method to the delegate that has a less derived return type.
myDel = Method3;
A a = myDel(new B());//this will return a more derived type object which can be assigned to base class reference
//Contravariane is applied to parameters.
//Contravariance allows a method with the parameter of a base class to be assigned to a delegate that expects the parameter of a derived class.
myDel = Method1;
myDel(new B()); //Contravariance,
}
}
}
来源:https://stackoverflow.com/questions/3445631/still-confused-about-covariance-and-contravariance-in-out