Covariance in C#

Covariance and contravariance real world example

Let's say you have a class Person and a class that derives from it, Teacher. You have some operations that take an IEnumerable<Person> as the argument. In your School class you have a method that returns an IEnumerable<Teacher>. Covariance allows you to directly use that result for the methods that take an IEnumerable<Person>, substituting a more derived type for a less derived (more generic) type. Contravariance, counter-intuitively, allows you to use a more generic type, where a more derived type is specified.

See also Covariance and Contravariance in Generics on MSDN.

Classes:

public class Person 
{
public string Name { get; set; }
}

public class Teacher : Person { }

public class MailingList
{
public void Add(IEnumerable<out Person> people) { ... }
}

public class School
{
public IEnumerable<Teacher> GetTeachers() { ... }
}

public class PersonNameComparer : IComparer<Person>
{
public int Compare(Person a, Person b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : Compare(a,b);
}

private int Compare(string a, string b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.CompareTo(b);
}
}

Usage:

var teachers = school.GetTeachers();
var mailingList = new MailingList();

// Add() is covariant, we can use a more derived type
mailingList.Add(teachers);

// the Set<T> constructor uses a contravariant interface, IComparer<in T>,
// we can use a more generic type than required.
// See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax
var teacherSet = new SortedSet<Teachers>(teachers, new PersonNameComparer());

C# dont understand covariance and contravariance of delegates

Following the comments under the question, the Variance in Delegates document will explain that

.NET Framework 3.5 introduced variance support for matching method
signatures with delegate types in all delegates in C#. This means that
you can assign to delegates not only methods that have matching
signatures, but also methods that return more derived types
(covariance) or that accept parameters that have less derived types
(contravariance) than that specified by the delegate type.

So, your assignment MyDelegate<Dog, Mammal> myDelegate = TestMethod; is perfectly fine, despite different signatures on delegate and TestMethod (reversed input parameter and return type).

But in and out parameters are still needed when you have an implicit conversion between delegates, according to Variance in Generic Type Parameters section

To enable implicit conversion, you must explicitly declare generic
parameters in a delegate as covariant or contravariant by using the in
or out keyword.

E.g. the following code will not compile

MyDelegate<Dog, Mammal> myDelegate = TestMethod;
MyDelegate<Dog, Dog> anotherDelegate = TestMethod;
myDelegate = anotherDelegate; //error CS0029: Cannot implicitly convert type...

Until you declare MyDelegate witn contravariant parameter and covariant return type

delegate TOut MyDelegate<in TIn, out TOut>(TIn input);

After that last line will compile

Why does C# use contravariance (not covariance) in input parameters with delegate?

Olivier's answer is correct; I thought I might try to explain this more intuitively.

Why does C# choose to use contravariance (not covariance) in input parameters in delegate?

Because contravariance is typesafe, covariance is not.

Instead of Base, let's say Mammal:

delegate void MammalDelegate(Mammal m);

This means "a function that takes a mammal and returns nothing".

So, suppose we have

void M(Giraffe x)

Can we use that as a mammal delegate? No. A mammal delegate must be able to accept any mammal, but M does not accept cats, it only accepts giraffes.

void N(Animal x)

Can we use that as a mammal delegate? Yes. A mammal delegate must be able to accept any mammal, and N does accept any mammal.

covariance/contravariance rule does not seem to work in this example.

There is no variance here to begin with. You are making the extremely common mistake of confusing assignment compatibility with covariance. Assignment compatibility is not covariance. Covariance is the property that a type system transformation preserves assignment compatibility.

Let me say that again.

You have a method that takes a Mammal. You can pass it a Giraffe. That is not covariance. That is assignment compatibility. The method has a formal parameter of type Mammal. That is a variable. You have a value of type Giraffe. That value can be assigned to that variable, so it is assignment compatible.

What then is variance, if it is not assignment compatibility? Let's look at an example or two:

A giraffe is assignment compatible with a variable of type mammal. Therefore a sequence of giraffes (IEnumerable<Giraffe>) is assignment compatible with a variable of type sequence of mammals (IEnumerable<Mammal>).

That is covariance. Covariance is the fact that we can deduce the assignment compatibility of two types from the assignment compatibility of two other types. We know that a giraffe may be assigned to a variable of type animal; that lets us deduce another assignment compatibility fact about two other types.

Your delegate example:

A mammal is assignment compatible with a variable of type animal. Therefore a method which takes an animal is assignment compatible with a variable of type delegate which takes a mammal.

That is contravariance. Contravariance is again, the fact that we can deduce the assignment compatibility of two things -- in this case a method can be assigned to a variable of a particular type -- from the assignment compatibility of two other types.

The difference between covariance and contravariance is simply that the "direction" is swapped. With covariance we know that A can be used as B implies that I<A> can be used as I<B>. With contravariance we know that I<B> can be used as I<A>.

Again: variance is a fact about the preservation of an assignment compatibility relationship across a transformation of types. It is not the fact that an instance of a subtype may be assigned to a variable of its supertype.

What other cases than delegate use covariance/contravariance and why?

  • Conversion of method groups to delegates uses covariance and contravariance on return and parameter types. This only works when the return / parameter types are reference types.

  • Generic delegates and interfaces may be marked as covariant or contravariant in their type parameters; the compiler will verify that the variance is always typesafe, and if not, will disallow the variance annotation. This only works when the type arguments are reference types.

  • Arrays where the element type is a reference type are covariant; this is not typesafe but it is legal. That is, you may use a Giraffe[] anywhere that an Animal[] is expected, even though you can put a turtle into an array of animals but not into an array of giraffes. Try to avoid doing that.

Note that C# does NOT support virtual function return type covariance. That is, you may not make a base class method virtual Animal M() and then in a derived class override Giraffe M(). C++ allows this, but C# does not.

UPDATE regarding the previous paragraph: This answer was written in 2016; in 2020, C# 9 does now support return type covariance.

C# covariance confusion

You definitely have some serious misunderstanding about how covariance works, but it's not 100% clear to me what it is. Let me first say what interfaces are, and then we can go through your question line by line and point out all the misunderstandings.

Think of an interface as a collection of "slots", where each slot has a contract, and contains a method that fulfils that contract. For example, if we have:

interface IFoo { Mammal X(Mammal y); }

then IFoo has a single slot, and that slot must contain a method that takes a mammal and returns a mammal.

When we implicitly or explicitly convert a reference to an interface type, we do not change the reference in any way. Rather, we verify that the referred-to type already has a valid slot table for that interface. So if we have:

class C : IFoo { 
public Mammal X(Mammal y)
{
Console.WriteLine(y.HairColor);
return new Giraffe();
}
}

And later

C c = new C();
IFoo f = c;

Think of C as having a little table that says "if a C is converted to IFoo, C.X goes in the IFoo.X slot."

When we convert c to f, c and f have exactly the same content. They are the same reference. We have just verified that c is of a type that has a slot table compatible with IFoo.

Now let's go through your post.

Invoking IExtract<object>.Extract() invokes IExtract<string>.Extract(), as evidenced by the output.

Let's crisp that up.

We have sampleClassOfString which implements IExtract<string>. Its type has a "slot table" that says "my Extract goes in the slot for IExtract<string>.Extract".

Now, when sampleClassOfString is converted to IExtract<object>, again, we have to make a check. Does sampleClassOfString's type contain an interface slot table that is suitable for IExtract<object>? Yes it does: we can use the existing table for IExtract<string> for that purpose.

Why can we use it, even though those are two different types? Because all the contracts are still met.

IExtract<object>.Extract has a contract: it is a method that takes nothing and returns object. Well, the method that is in the IExtract<string>.Extract slot meets that contract; it takes nothing, and it returns a string, which is an object.

Since all the contracts are met, we can use the IExtract<string> slot table we've already got. The assignment succeeds, and all invocations will go through the IExtract<string> slot table.

IExtract<object> is NOT in the inheritance hierarchy containing IExtract<string>

Correct.

except the fact that C# made IExtract<string> assignable to IExtract<object>.

Don't confuse those two things; they are not the same. Inheritance is the property that a member of a base type is also a member of a derived type. Assignment compatibility is the property that an instance of one type may be assigned to a variable of another type. Those are logically very different!

Yes, there is a connection, insofar that derivation implies both assignment compatibility and inheritance; if D is a derived type of base type B then an instance of D is assignable to a variable of type B, and all heritable members of B are members of D.

But don't confuse those two things; just because they are related does not mean they are the same. There are actually languages where they are different; that is, there are languages where inheritance is orthogonal to assignment compatibility. C# just is not one of them, and you're so used to a world where inheritance and assignment compatibility are so closely linked you've never learned to see them as separate. Start thinking of them as different things, because they are.

Covariance is about extending the assignment compatibility relation to types which are not in inheritance hierarchies. That's what covariance means; the assignment compatibility relation is covariant if the relation is preserved across a mapping to a generic. "An apple may be used where a fruit is needed; therefore a sequence of apples may be used where a sequence of fruits is needed" is covariance. The assignment compatibility relationship is preserved across the mapping to sequences.

But IExtract<string> simply does NOT have a method named Extract() that it inherits from IExtract<object>

That's correct. There is no inheritance whatsoever between IExtract<string> and IExtract<object>. However, there is a compatibility relationship between them, because any method Extract which meets the contract of IExtract<string>.Extract is also a method that meets the contract of IExtract<object>.Extract. Therefore, the slot table of the former may be used in a situation requiring the latter.

Would it be sensible to say that IExtract<string>'s OWN coincidentally (or by design) similarly named Extract() method hides IExtract<object>'s Extract() method?

Absolutely not. There is no hiding whatsoever. "Hiding" occurs when a derived type has a member of the same name as an inherited member of a base type, and the new member hides the old one for the purposes of looking up names at compile time. Hiding is solely a compile-time name lookup concept; it has nothing whatsoever to do with how interfaces work at runtime.

And that it is a kind of a hack?

ABSOLUTELY NOT.

I'm trying to not find the suggestion offensive, and mostly succeeding. :-)

This feature was carefully designed by experts; it is sound (modulo extending to existing unsoundnesses in C#, such as unsafe array covariance), and it was implemented with a great deal of caution and review. There is absolutely nothing whatsoever "hackish" about it.

so exactly what happens when I invoke IExtract<object>.Extract()?

Logically, this is what happens:

When you convert the class reference to IExtract<object>, we verify that there is a slot table in the reference that is compatible with IExtract<object>.

When you invoke Extract, we look up the contents of the Extract slot in the slot table we have identified as compatible with IExtract<object>. Since that is the same slot table as the one the object already has for IExtract<string>, the same thing happens: the class's Extract method is in that slot, so it gets invoked.

In practice, the situation is a little more complicated than that; there is a bunch of gear in the invocation logic that is there to ensure good performance in common cases. But logically, you should think of it as finding a method in a table, and then invoking that method.

Delegates can also be marked as covariant and contravariant. How does that work?

Logically, you can think of delegates as just interfaces that have a single method called "Invoke", and it follows from there. In practice, of course the mechanisms are somewhat different thanks to things like delegate composition, but perhaps now you can see how they might work.

Where can I learn more?

This is a bit of a firehose:

https://stackoverflow.com/search?q=user%3A88656+covariance

so I would start at the top:

Difference between Covariance & Contra-variance

If you want the history of the feature in C# 4.0, start here:

https://blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/

Note that this was written before we had settled on "in" and "out" as the keywords for contravariance and covariance.

A bunch more articles, in "newest first" chronological order, can be found here:

https://blogs.msdn.microsoft.com/ericlippert/tag/covariance-and-contravariance/

and a few here:

https://ericlippert.com/category/covariance-and-contravariance/


EXERCISE: Now that you know roughly how this works behind the scenes, what do you think this does?

interface IFrobber<out T> { T Frob(); }
class Animal { }
class Zebra: Animal { }
class Tiger: Animal { }
// Please never do this:
class Weird : IFrobber<Zebra>, IFrobber<Tiger>
{
Zebra IFrobber<Zebra>.Frob() => new Zebra();
Tiger IFrobber<Tiger>.Frob() => new Tiger();
}

IFrobber<Animal> weird = new Weird();
Console.WriteLine(weird.Frob());

? Give it some thought, and see if you can work out what happens.

Covariance with C# Generics

The fact that AMQuestion implements the IQuestion interface does not translate into List<AMQuestion> deriving from List<IQuestion>.

Because this cast is illegal, your as operator returns null.

You must cast each item individually as such:

IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();

Regarding your comment, consider the following code, with the usual cliché animal examples:

//Lizard and Donkey inherit from Animal
List<Lizard> lizards = new List<Lizard> { new Lizard() };
List<Donkey> donkeys = new List<Donkey> { new Donkey() };

List<Animal> animals = lizards as List<Animal>; //let's pretend this doesn't return null
animals.Add(new Donkey()); //Reality unravels!

if we were allowed to cast List<Lizard> to a List<Animal>, then we could theoretically add a new Donkey to that list, which would break inheritance.

still confused about covariance and contravariance & in/out

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.

Covariance with C#

Recently I've come across such limitation and I implemented a variation of Visitor Pattern (may be abuse of it). But that helped me to solve the problem.

I'm no architect, just a developer. So pardon me if am abusing the design pattern, but sure this will help.

With provided information I created mocks of your classes and applied visitor pattern as follows.

public class Matrix<T>
{
public T Obj { get; set; }
}

public interface INonGenericWrapper
{
void Wrap();
void Accept(IVisitor visitor);
}

public class Wrapper<T> : INonGenericWrapper
{
public Matrix<T> Matrix { get; private set; }

public void Wrap()
{
//Your domain specific method
}

public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}

public interface IVisitor
{
void Visit<T>(T element);
}

public class SerializationVisitor : IVisitor
{
public void Visit<T>(T element)
{
new Serializer<T>().Serialize(element);
}
}

public class Serializer<T>
{
public Stream Serialize(T objectToSerialize)
{
Console.WriteLine("Serializing {0}", objectToSerialize);
//Your serialization logic here
return null;
}
}

How to use:

List<INonGenericWrapper> wrappers = new List<INonGenericWrapper>();
wrappers.Add(new Wrapper<object>());
wrappers.Add(new Wrapper<string>());
wrappers.Add(new Wrapper<int>());
var visitor = new SerializationVisitor();//Create the operation you need to apply
foreach (var wrapper in wrappers)
{
wrapper.Accept(visitor);
}

All you've to do is create a new visitor for each operation you need to perform.

Here is the Demo which outputs

Serializing Wrapper`1[System.Object]
Serializing Wrapper`1[System.String]
Serializing Wrapper`1[System.Int32]

covariance in c#

You can't do this, because it wouldn't be safe. Consider:

List<Joe> joes = GetJoes();    
List<Human> humanJoes = joes;
humanJoes.Clear();
humanJoes.Add(new Fred());
Joe joe = joes[0];

Clearly the last line (if not an earlier one) has to fail - as a Fred isn't a Joe. The invariance of List<T> prevents this mistake at compile time instead of execution time.

Covariance and Contravariance with Func in generics

I need more information about variance in generics and delegates.

I wrote an extensive series of blog articles on this feature. Though some of it is out of date -- since it was written before the design was finalized -- there's lots of good information there. In particular if you need a formal definition of what variance validity is, you should carefully read this:

https://blogs.msdn.microsoft.com/ericlippert/2009/12/03/exact-rules-for-variance-validity/

See my other articles on my MSDN and WordPress blogs for related topics.


Why the compiler complains about TIn being contravariant and TOut - covariant while the Func expects exactly the same variance?

Let's slightly rewrite your code and see:

public delegate R F<in T, out R> (T arg);
public interface I<in A, out B>{
B M(F<A, B> f);
}

The compiler must prove that this is safe, but it is not.

We can illustrate that it is not safe by supposing that it is, and then discovering how it can be abused.

Let's suppose we have an Animal hierarchy with the obvious relationships, eg, Mammal is an Animal, Giraffe is a Mammal, and so on. And let's suppose that your variance annotations are legal. We should be able to say:

class C : I<Mammal, Mammal>
{
public Mammal M(F<Mammal, Mammal> f) {
return f(new Giraffe());
}
}

I hope you agree this is a perfectly valid implementation. Now we can do this:

I<Tiger, Animal> i = new C();

C implements I<Mammal, Mammal>, and we've said that the first one can get more specific, and the second can get more general, so we've done that.

Now we can do this:

Func<Tiger, Animal> f = (Tiger t) => new Lizard();

That's a perfectly legal lambda for this delegate, and it matches the signature of:

i.M(f);

And what happens? C.M is expecting a function that takes a giraffe and returns a mammal, but it's been given a function that takes a tiger and returns a lizard, so someone is going to have a very bad day.

Plainly this must not be allowed to happen, but every step along the way was legal. We must conclude that the variance itself was not provably safe, and indeed, it was not. The compiler is right to reject this.

Getting variance right takes more than simply matching the in and out annotations. You've got to do so in a manner that does not allow this sort of defect to exist.

That explains why this is illegal. To explain how it is illegal, the compiler must check that the following is true of B M(F<A, B> f);:

  • B is valid covariantly. Since it is declared "out", it is.
  • F<A, B> is valid contravariantly. It is not. The relevant portion of the definition of "valid contravariantly" for a generic delegate is: If the ith type parameter was declared as contravariant, then Ti must be valid covariantly. OK. The first type parameter, T, was declared as contravariant. Therefore the first type argument A must be valid covariantly. But it is not valid covariantly, because it was declared contravariant. And that's the error you're getting. Similarly, B is also bad because it must be valid contravariantly, but B is covariant. The compiler does not go on to find additional errors after it finds the first problem here; I considered it but rejected it as being a too-complex error message.

I note also that you would still have this problem even if the delegate were not variant; nowhere in my counterexample did we use the fact that F is variant in its type parameters. A similar error would be reported if we tried

public delegate R F<T, R> (T arg);

instead.



Related Topics



Leave a reply



Submit