Still Confused About Covariance and Contravariance & In/Out

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.

I've read all about Covariance, Contravariance and Invariance, but I still don't get how to design my code

You can only declare variance modifiers (in, out) on generic interfaces, not types. So one way to solve this problem would be to declare interface for your GenericClass, like this:

interface IGenericClass<out TemplateClass> where TemplateClass : TestInterface {
TemplateClass goo { get; }
}
class GenericClass<TemplateClass> : IGenericClass<TemplateClass> where TemplateClass : TestInterface
{
public TemplateClass goo { get; }
}

And then

class Program {
protected static ISet<IGenericClass<TestInterface>> set = new HashSet<IGenericClass<TestInterface>>();

static void Main(string[] args) {
set.Add(new GenericClass<A>());
set.Add(new GenericClass<B>());
}
}

Covariance and Contravariance on the same type argument

As others have said, it is logically inconsistent for a generic type to be both covariant and contravariant. There are some excellent answers here so far, but let me add two more.

First off, read my article on the subject of variance "validity":

http://blogs.msdn.com/b/ericlippert/archive/2009/12/03/exact-rules-for-variance-validity.aspx

By definition, if a type is "covariantly valid" then it is not usable in a contravariant way. If it is "contravariantly valid" then it is not usable in a covariant way. Something that is both covariantly valid and contravariantly valid is not usable in either a covariant or contravariant way. That is, it is invariant. So, there is the union of covariant and contravariant: their union is invariant.

Second, let's suppose for a moment that you got your wish and that there was a type annotation that worked the way I think you want:

interface IBurger<in and out T> {}

Suppose you have an IBurger<string>. Because it is covariant, that is convertible to IBurger<object>. Because it is contravariant, that is in turn convertible to IBurger<Exception>, even though "string" and "Exception" have nothing whatsoever in common. Basically "in and out" means that IBurger<T1> is convertible to any type IBurger<T2> for any two reference types T1 and T2. How is that useful? What would you do with such a feature? Suppose you have an IBurger<Exception>, but the object is actually an IBurger<string>. What could you do with that, that both takes advantage of the fact that the type argument is Exception, and allows that type argument to be a complete lie, because the "real" type argument is an utterly unrelated type?

To answer your follow-up question: implicit reference type conversions involving arrays are covariant; they are not contravariant. Can you explain why you incorrectly believe them to be contravariant?

Difference between Covariance & Contra-variance

The question is "what is the difference between covariance and contravariance?"

Covariance and contravariance are properties of a mapping function that associates one member of a set with another. More specifically, a mapping can be covariant or contravariant with respect to a relation on that set.

Consider the following two subsets of the set of all C# types. First:

{ Animal, 
Tiger,
Fruit,
Banana }.

And second, this clearly related set:

{ IEnumerable<Animal>, 
IEnumerable<Tiger>,
IEnumerable<Fruit>,
IEnumerable<Banana> }

There is a mapping operation from the first set to the second set. That is, for each T in the first set, the corresponding type in the second set is IEnumerable<T>. Or, in short form, the mapping is T → IE<T>. Notice that this is a "thin arrow".

With me so far?

Now let's consider a relation. There is an assignment compatibility relationship between pairs of types in the first set. A value of type Tiger can be assigned to a variable of type Animal, so these types are said to be "assignment compatible". Let's write "a value of type X can be assigned to a variable of type Y" in a shorter form: X ⇒ Y. Notice that this is a "fat arrow".

So in our first subset, here are all the assignment compatibility relationships:

Tiger  ⇒ Tiger
Tiger ⇒ Animal
Animal ⇒ Animal
Banana ⇒ Banana
Banana ⇒ Fruit
Fruit ⇒ Fruit

In C# 4, which supports covariant assignment compatibility of certain interfaces, there is an assignment compatibility relationship between pairs of types in the second set:

IE<Tiger>  ⇒ IE<Tiger>
IE<Tiger> ⇒ IE<Animal>
IE<Animal> ⇒ IE<Animal>
IE<Banana> ⇒ IE<Banana>
IE<Banana> ⇒ IE<Fruit>
IE<Fruit> ⇒ IE<Fruit>

Notice that the mapping T → IE<T> preserves the existence and direction of assignment compatibility. That is, if X ⇒ Y, then it is also true that IE<X> ⇒ IE<Y>.

If we have two things on either side of a fat arrow, then we can replace both sides with something on the right hand side of a corresponding thin arrow.

A mapping which has this property with respect to a particular relation is called a "covariant mapping". This should make sense: a sequence of Tigers can be used where a sequence of Animals is needed, but the opposite is not true. A sequence of animals cannot necessarily be used where a sequence of Tigers is needed.

That's covariance. Now consider this subset of the set of all types:

{ IComparable<Tiger>, 
IComparable<Animal>,
IComparable<Fruit>,
IComparable<Banana> }

now we have the mapping from the first set to the third set T → IC<T>.

In C# 4:

IC<Tiger>  ⇒ IC<Tiger>
IC<Animal> ⇒ IC<Tiger> Backwards!
IC<Animal> ⇒ IC<Animal>
IC<Banana> ⇒ IC<Banana>
IC<Fruit> ⇒ IC<Banana> Backwards!
IC<Fruit> ⇒ IC<Fruit>

That is, the mapping T → IC<T> has preserved the existence but reversed the direction of assignment compatibility. That is, if X ⇒ Y, then IC<X> ⇐ IC<Y>.

A mapping which preserves but reverses a relation is called a contravariant mapping.

Again, this should be clearly correct. A device which can compare two Animals can also compare two Tigers, but a device which can compare two Tigers cannot necessarily compare any two Animals.

So that's the difference between covariance and contravariance in C# 4. Covariance preserves the direction of assignability. Contravariance reverses it.

Covariance and Contravariance

If B inherits from A, ie is a subtype of A, then C function is covariant if C(B) is a subtype of C(A), contravariant if C(A) is a subtype of C(B).

The question is wether or not you want consumers to deal with supertypes or subtypes.

A common example is a class library dealing in fruit classes.

It is OK for me to give you an apple where you expect a fruit.
It is not OK for me to give you just any fruit where you expect an apple.

If you were making a fruit library. You should be expecting instances fruit from me and I should be expecting instances of fruit from you. Neither of us should have expectations about a subtype that could be false.

Therefore, the answer to the question above depends on wether you're getting or sending. Wether the parameter is read or write.
A fruit list should be accepting bananas or apples, but only send "fruits"

Why is using a variable of a generic interface with a concrete class instance inheriting the interface fail in C#?

That's because of co- and contra-variance.

When something is a IFooWrapper<Foo>, that doesn't mean it's could be compile time converted to IFooWrapper<IFoo>. I think it's better explained here: Covariance and Contravariance (C#) | Microsoft Docs and still confused about covariance and contravariance & in/out.

Please note that you could mark interfaces in C# as co- or contra-variant. For example, if you have the below interface (notice the out I - it's now covariant for type parameter I), you could use IFooWrapper<IFoo> foo = new FooWrapper();

public interface IFooWrapper<out I> where I : IFoo { }

See demo in Fiddle

Instead of changing the interface, you could fix the method signature with generics:

void Register<T>(IFooWrapper<T> foo) where T: IFoo
{
}

That one could be called with IFooWrapper<Foo> and IFooWrapper<IFoo> - so this works: Register(new FooWrapper());

See also Fiddle

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.

Type safety in CoVariance and ContraVariance


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.



Related Topics



Leave a reply



Submit