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 a List<Fruit> because
list.Add(new Apple()) is valid for
List but not for List<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.
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 =)
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.
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 Mammal
IInvariant<Mammal>.Set(Mammal) - accepts a Mammal
What if we try: IInvariant<Mammal> invariantMammal = (IInvariant<Animal>)null?
Whoever calls IInvariant<Mammal>.Get() expects a Mammal, but IInvariant<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. Since IInvariant<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. Since IInvariant<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 out generic modifier) - this is boring
None of my methods accept a Mammal - this is interesting though, because this is the actual restriction imposed by the out generic 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, but ICovariant<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 the out modifier 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 the out modifier 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 in generic modifier) - this is boring
None of my methods return a Mammal - this is interesting though, because this is the actual restriction imposed by the in generic modifier
Contravariance experiment
What if we try: IContravariant<Mammal> contravariantMammal = (IContravariant<Animal>)null?
IContravariant<Mammal>.Get() - this is no longer an issue thanks to the in modifier restrictions!
Whoever calls IContravariant<Mammal>.Set(Mammal) expects that a Mammal can be passed. Since IContravariant<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?
IContravariant<Mammal>.Get() - this is no longer an issue thanks to the in modifier restrictions!
Whoever calls IContravariant<Mammal>.Set(Mammal) expects that a Mammal can be passed. Since IContravariant<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)
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.
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,
}
}
}
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();
Let’s start with the class hierarchy we are using in the covariance and contravariance examples:
public class Weapon { }
public class Sword : Weapon { }
public class TwoHandedSword : Sword { }
Covariance means you can return (output) the instance of a subtype as its supertype. Here is an
example:
[Fact]
public void Covariance_tests()
Assert.IsType<Sword>(Covariance());
Assert.Throws<InvalidCastException>(() => BreakCovariance());
}
// We can return a Sword into a Weapon
private Weapon Covariance()
=> new Sword();
// We cannot return a Sword into a TwoHandedSword
private TwoHandedSword BreakCovariance()
=> (TwoHandedSword)new Sword();
As shown in the preceding example, one way to break covariance is to return a supertype as a subtype.
On the other hand, contravariance means you can input the instance of a subtype as its supertype.
It is basically the same thing but for inputs, like this:
[Fact]
public void Contravariance_tests()
{
// We can pass a Sword as a Weapon
Contravariance(new Sword());
// We cannot pass a Weapon as a Sword
BreakContravariance(new Weapon()); // Compilation error
}
private void Contravariance(Weapon weapon) { }
private void BreakContravariance(Sword weapon) { }
The same polymorphic rule applies, as we can see from the preceding code. We can use a subtype as
a supertype.
Related
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 a List<Fruit> because
list.Add(new Apple()) is valid for
List but not for List<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.
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 =)
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.
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 Mammal
IInvariant<Mammal>.Set(Mammal) - accepts a Mammal
What if we try: IInvariant<Mammal> invariantMammal = (IInvariant<Animal>)null?
Whoever calls IInvariant<Mammal>.Get() expects a Mammal, but IInvariant<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. Since IInvariant<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. Since IInvariant<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 out generic modifier) - this is boring
None of my methods accept a Mammal - this is interesting though, because this is the actual restriction imposed by the out generic 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, but ICovariant<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 the out modifier 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 the out modifier 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 in generic modifier) - this is boring
None of my methods return a Mammal - this is interesting though, because this is the actual restriction imposed by the in generic modifier
Contravariance experiment
What if we try: IContravariant<Mammal> contravariantMammal = (IContravariant<Animal>)null?
IContravariant<Mammal>.Get() - this is no longer an issue thanks to the in modifier restrictions!
Whoever calls IContravariant<Mammal>.Set(Mammal) expects that a Mammal can be passed. Since IContravariant<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?
IContravariant<Mammal>.Get() - this is no longer an issue thanks to the in modifier restrictions!
Whoever calls IContravariant<Mammal>.Set(Mammal) expects that a Mammal can be passed. Since IContravariant<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)
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.
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,
}
}
}
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();
Let’s start with the class hierarchy we are using in the covariance and contravariance examples:
public class Weapon { }
public class Sword : Weapon { }
public class TwoHandedSword : Sword { }
Covariance means you can return (output) the instance of a subtype as its supertype. Here is an
example:
[Fact]
public void Covariance_tests()
Assert.IsType<Sword>(Covariance());
Assert.Throws<InvalidCastException>(() => BreakCovariance());
}
// We can return a Sword into a Weapon
private Weapon Covariance()
=> new Sword();
// We cannot return a Sword into a TwoHandedSword
private TwoHandedSword BreakCovariance()
=> (TwoHandedSword)new Sword();
As shown in the preceding example, one way to break covariance is to return a supertype as a subtype.
On the other hand, contravariance means you can input the instance of a subtype as its supertype.
It is basically the same thing but for inputs, like this:
[Fact]
public void Contravariance_tests()
{
// We can pass a Sword as a Weapon
Contravariance(new Sword());
// We cannot pass a Weapon as a Sword
BreakContravariance(new Weapon()); // Compilation error
}
private void Contravariance(Weapon weapon) { }
private void BreakContravariance(Sword weapon) { }
The same polymorphic rule applies, as we can see from the preceding code. We can use a subtype as
a supertype.
Revisiting some old C#8 code after working in Swift for the past few years and am hitting a wall. Well, an annoying speed bump anyway.
Swift has a great language feature where the type Self always refers to the instantiated type, even if you are within a base class (although from within the base class, you still only have access to the base class members.) This means if you have a function or property of type Self it will always be of the instantiated type at the call site.
Here's an illustrative example. Not sure if this compiles as I'm typing off the top of my head, but you get the idea.
abstract class Foo {
public Foo ReturnMe(){ // Note the return type 'Foo'
return this;
}
}
class Laa : Foo {}
var laa = new Laa();
var x = laa.ReturnMe(); // x is of type 'Foo', not of type 'Laa'
However, by changing the return type of ReturnMe to Self, something magical happens...
(Of course this won't compile. This is illustrative only.)
abstract class Foo {
public Self ReturnMe(){ // Note the return type 'Self' here, not 'Foo' as before
return this;
}
}
class Laa : Foo {}
var laa = new Laa();
var y = laa.ReturnMe(); // 'y' here is type 'Laa' not 'Foo' thanks to 'Self'
class Hee : Foo {}
var hee = new Hee();
var z = hee.ReturnMe(); // Like above, 'z' here is type 'Hee'
It's such a helpful feature because it allows you to easily create chainable, fluent-style interfaces without losing type along the way. As an example, you could do this...
x.doSomething().thenSomethingMore().andEvenmore();
Even if those methods were all defined in base classes, whatever type x is is the type passed on down, meaning all base and subclass members would be available, not just those in the base class.
So, does C#8 have any such mechanisms?
I have the following piece of code where I am trying to write a generic validation rule for my domain objects. while doing so I have an issue to deal Func delegate supporting variance
public class Person { }
public class Employee : Person { }
internal interface IValidation<T> where T: Person
{
void AddValidationRule(Func<T,bool> predicateFunction);
}
internal class BaseValidation : IValidation<Person>
{
void IValidation<Person>.RegisterValidationRules(Person person)
{
}
}
internal class EmployeeValidation : BaseValidation
{
void RegisterValidation()
{
Func<Employee,bool> empPredicate = CheckJoiningDate;
base.AddValidationRule(empPredicate);
}
bool CheckJoiningDate(Employee employee)
{
return employee.JoiningDate > DateTime.Now.AddDays(-1) ;
}
}
With the above code in place, compiler gives an error message saying
Compiler Error on line : base.AddValidationRule(empPredicate);
Argument 1: cannot convert from 'System.Func<>Employee, bool>' to
'System.Func<>Person, bool>
I had referred to this https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/dd465122%28v%3dvs.100%29 but I still couldn't make the compiler to understand about the contravariance here,
Appreciate your help so I understand this better
You've mixed up covariance and contravariance.
With covariance, the generic type argument can be "smaller" than what is required. That is, if we have:
Func<Mammal, Mammal> f1 = whatever;
Func<Mammal, Animal> f2 = f1;
Why does that work? Func is covariant in its second parameter. f2 is expecting a delegate that returns Animal. It got a delegate that returns a smaller type; there are fewer Mammals than Animals, so Mammal is smaller.
Think about why this works. When someone calls f2, they are expecting to get an animal back. But if they are really calling f1, they still get an animal, because every mammal is an animal.
With covariance, the "size" of the generic type varies in the same direction as the size of the type argument. Mammal is smaller than Animal. Func<Mammal, Mammal> is smaller than Func<Mammal, Animal>. That's why it is "co" variance, co meaning "together".
Contravariance is the opposite, hence "contra", meaning "against". With contravariance, the generic type argument can be bigger than expected:
Func<Giraffe, Mammal> f3 = f1;
f3 expects a function that takes a giraffe; we have a function that takes a bigger type, mammal. It's bigger because there are more mammals than giraffes. Contravariance says this is good, and that should make sense. If someone calls f3 with a giraffe, it's OK if that is actually a call to f2, because f2 can take a giraffe; it can take any mammal.
You've mixed up covariance and contravariance; you're expecting that a contravariant parameter can be used in a covariant manner, which is wrong. A function that takes employees cannot be converted to a function that takes persons, because you could pass a non-employee person to it. A function that takes employees can be converted to a function that takes managers, because all managers are employees.
cannot convert from 'System.Func<>Employee, bool>' to 'System.Func<>Person, bool>
base.AddValidationRule requires a function that can operate on any Person. Your function can only operate on Exployee which is more restrictive. This is the wrong form of variance.
It is not shown here but likely BaseValidation implemented IValidation<Person>.
Likely, the best fix is to ensure that you inherit from IValidation<Employee>, possibly by making BaseValidation generic.
Would this work?
internal class BaseValidation<T> : IValidation<T>
{
void IValidation<T>.RegisterValidationRules(T entity)
{
}
}
internal class EmployeeValidation : BaseValidation<Employee>
{
//...
}
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).
I am reading C# in Depth from Jon Skeet. Although I have understood the concept of CoVariance and ContraVariance, but I am unable to understand this line:
Well, covariance is safe when SomeType only describes operations that
return the type parameter—and contravariance is safe when SomeType
only describes operations that accept the type parameter.
Can someone please explain with an example for both, why both are type safe in one direction and not in the other direction?
Updated Question:
I still didn't understand from the answers given. I will try to explain my concern using the same example from the book - C# In Depth.
It explains using the following class hierarchy:
COVARIANCE is: Trying to convert from IEnumerable<Circle> to IEnumerable<IShape>, but it is mentioned that this conversion is type safe only when we are performing while returning it from some method, and not type safe when we are passing it as an IN parameter.
IEnumerable<IShape> GetShapes()
{
IEnumerable<Circle> circles = GetEnumerableOfCircles();
return circles; // Conversion from IEnumerable<Circle> to IEnumerable<IShape> - COVARIANCE
}
void SomeMethod()
{
IEnumerable<Circle> circles = GetEnumerableOfCircles();
DoSomethingWithShapes(circles); // Conversion from IEnumerable<Circle> to IEnumerable<IShape> - COVARIANCE
}
void DoSomethingWithShapes(IEnumerable<IShape> shapes) // Why this COVARIANCE is type unsafe??
{
// do something with Shapes
}
CONTRA VARIANCE is: Trying to convert from IEnumerable<IShape> to IEnumerable<Circle>, which is mentioned to be type safe only while performing it when sending it as an IN parameter.
IEnumerable<Circle> GetShapes()
{
IEnumerable<IShape> shapes = GetEnumerableOfIShapes();
return shapes; // Conversion from IEnumerable<IShape> to IEnumerable<Circle> - Contra-Variance
// Why this Contra-Variance is type unsafe??
}
void SomeMethod()
{
IEnumerable<IShape> shapes = GetEnumerableOfIShapes();
DoSomethingWithCircles(shapes); // Conversion from IEnumerable<IShape> to IEnumerable<Circle> - Contra-Variance
}
void DoSomethingWithCircles(IEnumerable<Circle> circles)
{
// do something with Circles
}
Covariance
Covariance is safe when SomeType only describes operations that return the type parameter
The IEnumerable<out T> interface is probably the most common example of covariance. It is safe because it only returns values of type T (well, specifically an IEnumerator<out T> but does not accept any T objects as parameters.
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
This works because IEnumerator<T> is also covariant and only returns T:
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
T Current { get; }
}
If you have a base class called Base and a derived class called Derived, then you can do stuff like this:
IEnumerable<Derived> derivedItems = Something();
IEnumerable<Base> baseItems = derivedItems;
This works because each item in derivedItems is also an instance of Base, so it is perfectly acceptable to assign it the way we just did. However, we cannot assign the other way:
IEnumerable<Base> baseItems = Something();
IEnumerable<Derived> derivedItems = baseItems; // No good!
This isn't safe because there is no guarantee that each instance of Base is also an instance of Derived.
Contravariance
Contravariance is safe when SomeType only describes operations that accept the type parameter
The Action<in T> delegate is a good example of contravariance.
public delegate void Action<in T>(T obj);
It is safe because it only accepts T as a parameter, but doesn't return T.
Contravariance lets you do stuff like this:
Action<Base> baseAction = b => b.DoSomething()
Action<Derived> derivedAction = baseAction;
Derived d = new Derived();
// These 2 lines do the same thing:
baseAction(d);
derivedAction(d);
This works because it is perfectly acceptable to pass an instance of Derived to baseAction. However, it doesn't work the other way around:
Action<Derived> derivedAction = d => d.DoSomething()
Action<Base> baseAction = derivedAction; // No good!
Base b = new Base();
baseAction(b); // This is OK.
derivedAction(b); // This does not work because b may not be an instance of Derived!
This isn't safe because there is no guarantee that an instance of Base will also be an instance of Derived.
Imagine you make an ILogger<in T> interface that knows how to log the details of a T. And let's say you have a Request class and an ExpeditedRequest subclass. Surely an ILogger<Request> should be convertible to an ILogger<ExpeditedRequest>. After all, it can log any request.
Interface ILogger<in T> where T: Request {
void Log(T arg);
}
Now imagine another interface IRequestProducer<out T> that gets the next request in some queue. There are different sources of requests in your system, and of course, some of them can still have different subclasses. In this case, we can't rely on converting an IRequestProducer<Request> to an IRequestProducer<ExpeditedRequest> since it could produce a non-expedited request. But the reverse conversion would work.
Interface IRequestProducer<T> where T: Request {
T GetNextRequest();
}
I understood after reading from MSDN, the following two pages:
https://msdn.microsoft.com/en-us/library/dd469484.aspx
https://msdn.microsoft.com/en-us/library/dd469487.aspx
Actually, I think it will become clear from the book also when I will reach the C# 4 part of the book that will explain in and out keywords with Type Parameters of Generics. Right now, I was reading the Limitations of Generics in C# in the C# 1 part of book.
The statement that I wanted to understand was this:
Well, covariance is safe when SomeType only describes operations that
return the type parameter—and contravariance is safe when SomeType
only describes operations that accept the type parameter.
Besides being safe, it is also not possible to write the interface method in other direction as compiler will complain, as written in the above two pages on msdn:
A type can be declared contravariant in a generic interface or delegate if it is used only as a type of method arguments and not used as a method return type.
In a generic interface, a type parameter can be declared covariant if it satisfies the following conditions:
The type parameter is used only as a return type of interface methods and not used as a type of method arguments.
There are now two points, that made me confuse about the statement:
First , I was misunderstanding the statement itself - I was thinking that Covairance is safe only when an instance of Inteface<T> itself is returned from some method rather than passed as an input to some method. However, it is regarding the type parameter T and interface method. Same for ContraVariance.
Second, Now When I have understood what this statement means - that it is regarding passing/returning the generic type parameter T to/from interface methods. I wanted to know why it is Type Safe only while returning T from interface method in Covariance and why it is Type Safe only while passing T as input to interface method in ContraVariance.
Why it is Type Safe only while returning T in CoVariance:
interface IBase<out T>
{
T Return_T(); // Valid and Type Safe
void Accept_T(T input) // Invalid and Type UnSafe
}
class Sample<T> : IBase<T> { }
class BaseClass {}
class DerivedClass : BaseClass
{}
IBase<BaseClass> ibase = new Sample<BaseClass>();
IBase<DerivedClass> iderived = new Sample<DerivedClass>();
ibase = iderived; // Can be assinged because `T` is Covariant
BaseClass b = new BaseClass();
DerivedClass d = new DerivedClass();
ibase.Return_T(); // At runtime, this will return `DerivedClass` which can be assinged to variable of both base and derived class and is type safe
ibase.Accept_T(b); // The compiler will accept this statement, because at compile time, it accepts an instance of `BaseClass`, but at runtime, it actually needs an instance of `DerivedClass`. So, we are eventually assigning an instance of `BaseClass` to `DerivedClass` which is type unsafe.
Why it is Type Safe only while passing T as an input parameter in ContraVariance:
interface IBase<in T>
{
T Return_T(); // Invalid and Type UnSafe
void Accept_T(T input) // Valid and Type Safe
}
class Sample<T> : IBase<T> { }
class BaseClass {}
class DerivedClass : BaseClass
{}
IBase<BaseClass> ibase = new Sample<BaseClass>();
IBase<DerivedClass> iderived = new Sample<DerivedClass>();
iderived = ibase; // Can be assinged because `T` is Contravariant
BaseClass b = new BaseClass();
DerivedClass d = new DerivedClass();
iderived.Accept_T(d); // This is Type Safe, because both at compile time and runtime, either instance of `DerivedClass` can be assinged to `BaseClass` or instance of `BaseClass` can be assinged to `BaseClass`
DerivedClass d2 = iderived.Return_T(); // This is type unsafe, because this statement is valid at compile time, but at runtime, this will return an instance of `BaseClass` which is getting assinged to `DerivedClass`