Over a million developers have joined DZone.

C# 4.0 Feature Focus – Part 4 – Co- and Contra-Variance for Generic Delegate and Interface Types


Last time around in this series, I promised to talk about generic co- and contra-variance. So that’s why we’re here today. In this post I’ll explain the theoretical concepts behind these fancy-sounding terms, look at the runtime’s take on it, show how to use them in C# 4.0 and most important of all: tell you why you don’t have to worry about all of this :-).


What’s variance?

Language features with names like variance ought to come from a theoretical background, don’t you think so? Absolutely right, and that’s no different for variance with category theory being its source of inspiration. Although we often speak about co- and contra-variance (turns out C# – amongst lots of other languages – already use those concepts , as we’ll see later) there are three sorts of variance to be explained before moving on.

Type ordering

First we need to establish the notion of type ordering. All of you know about object-oriented programming where types are used to indicate the “kind” of data one is dealing with. On top of this, subtyping is used to specialize types. Or, the other way around, operations supported on a supertype can also be applied to its subtypes (possibly with a specialized implementation through overriding and virtual method dispatch).

For example, System.Object has a ToString method. System.DateTime is a subtype of System.Object and therefore also has a ToString method on it (either derived from the base type as-is or overridden, but that doesn’t matter for now).

Let’s use more concise notation to write down such relationships. If D(erived) is a subclass of B(ase), we’ll write D <: B. To say d is an instance of D, we’ll write d : D.

Based on this we can start formulating properties, such as subsumption, the formal notion of subtype-relationships:

If d : D and D <: B, then d : B

For instance, if we substitute d for name, D for System.String and B for System.Object, we end up with:

If name : System.String and System.String <: System.Object, then name : System.Object

We all know this: because name is declared to be a string and the string type is a subtype of object, we can say name ought to be an object. The notation <: is what we call type ordering. I’ll leave it to the math-savvy reader to map those concepts back to the properties of binary relations, such as reflexivity, (anti)-symmetry and transitivity.


What’s up with variance? In short, variance is a property of operators that act on types. This is very abstract, but we’ll make it very concrete in just a second. For now, think of it very generally: assume op(T1, T2) is an operator “that does something with/based on” T1 and T2 which are both types. For mathematicians (and programmers as it turns out) it makes perfect sense to attribute properties to that operator, capturing its behavior with respect to the types it acts on.

To make things a bit more concrete, let’s map the concept of operators onto generics. (Note for geeks only. Generics are a form of “parametric polymorphism”, which extends the lambda calculus to something known as System F.) As we know, generics allow for type parameterization. Below is a simple example introducing a Pair type:

class Pair<TFirst, TSecond>
public Pair(TFirst first, TSecond second)
First = first;
Second = second;

public TFirst First { get; private set; }
public TSecond Second { get; private set; }

You can think of this type declaration as an operator that, given type values for TFirst and TSecond, can give us a constructed type back (in other words, it’s a type constructor, in the Haskell sense, not the CLR sense). Now we want to be able to express type ordering relationships over such constructed generic types. For example, how does Pair<string, int> relate to Pair<string, object> or to Pair<DateTime, int> or to … whatever. More specifically, we want to be able to infer the relationship between any Pair<T1, T2> and Pair<T3, T4> from relationships between the parameters.

For instance, can we say (this is a question, not an answer just yet…) that:

If T1 <: T3 and T2 <: T4, then Pair<T1,T2> <: Pair<T3,T4>

These are the kind of questions asked by theorists, but obviously by developers today as well.


Time to dive into the first kind of variance: covariance. Co as a prefix means “together with”. It means an operator preserves the ordering of types, when compared to its operands. Let me give a concrete sample, based on our Pair class above.

If you know that Apple is a subtype of Fruit (Apple <: Fruit) and Tomato is a subtype of Vegetable (Tomato <: Vegetable), can you also say that Pair<Apple, Tomato> is a subtype of Pair<Fruit, Vegetable>? Looks like that ought to be valid on first sight, no? But there might be some hidden caveats… We’ll get to that soon.

The formalized notion of covariance can be stated as follows:

Operator op(T1, …, Tn) is covariant in Ti (1 <= i <= n) if Si <: Ti implies op(T1, …, Ti-1, Si, Ti+1, …, Tn) <: op(T1, …, Tn)

A very practical case where this comes up is with sequences. For instance, given a LINQ query that returns a sequence of Student objects, can we treat that sequence as one of Person objects, i.e.:

IEnumerable<Person> people = from Student s in db.Students
where s.Age <= 25
select s;

Today, in C# 3.0, you cannot do this because IEnumerable<T> is not covariant in T.


The opposite of “co” is “contra” which means “against”. In the context of variance it means an operator reverses the ordering of types, when compared to its operands. Again a concrete sample is more than welcome I guess.

Let’s stick with fruits. We known – just repeating this obvious fact – that Apple is a subtype of Fruit (Apple <: Fruit). Now say if we want to carry out comparison between apples (IComparer<Apple>), is it possible then to use a comparison between fruits instead (IComparer<Fruit>)? Looks like that should be possible, right? Everything that can handle two pieces of fruit for comparison ought to be able to handle two apples as each apple is a piece of fruit.

The formalized notion of contravariance can be stated as follows:

Operator op(T1, …, Tn) is contravariance in Ti (1 <= i <= n) if Si <: Ti implies op(T1, …, Ti-1, Si, Ti+1, …, Tn) :> op(T1, …, Tn)

A very practical case where this comes up is with action delegates. For instance, given an action that takes in a Student, can we treat that action as one that deals with a Person object instead, i.e.:

Action<Person> submitLetter = (Student s) => {
if (s.InDorm)
SendMailTo(s.University.Address, s.StudentId);

Today, in C# 3.0, you cannot do this because Action<T> is not contravariant in T.


Broken array variance

First a shock to the reader: arrays in the CLR are covariant. “So what?”, you might wonder. Well, and here comes the shock, it turns out covariant use of arrays is not type-safe. (Let you heartbeat go down before reading on.) We’ll get to why covariant treatment of arrays is broken, but first some history. You might assume this “broken array variance” was put in the CLI (Common Language Infrastructure, the standardized specification of the CLR, ECMA-335) intentionally. The mission of the CLI was – and still is, bigger than ever with the whole DLR on top of it – to accommodate executing different languages on the same unified runtime with a unified type system, instruction set, runtime services, etc. One such language was Java, which has broken array covariance, and being able to accommodate that language won over fixing this behavior.

But what makes arrays unsafe for covariance? Another sample with fruits… The story of the fruit farmer.

A fruit farmer produces apples and has a contract with a local grocery store to sell the apples. To do so, the farmer hands over an array of apples to the store. The contract states the store can return the apples that haven’t been sold in the next week, and only the apples that were sold (indicated by empty spots in the array from which the pieces of fruit have been taken) will be billed. The remainder apples – possible rotten by now – are sent to a juice factory nearby that creates sweet apple juice. This scheme is illustrated below:

But because arrays are covariant, the grocery story (on the right) can treat an array of Apples (Apple[]) as if it were an array of Fruits (Fruit[]). Since the cells of arrays are writeable, this opens up a potential hole in the system. What if the grocery store wants to cheat and put some rotten tomatoes in the tray (let’s assume tomatoes are fruits for sake of this discussion; if you don’t agree with this, substitute “rotten tomato” for “rotten lemon” but that doesn’t sound as bad IMO, hence the use of tomatoes). The contract with the farmer stated that only the number of empty places in the tray will be considered in billing the grocery story; so fill a few empty places with unsellable rotten tomatoes and the price gets reduced. This might go unnoticed if the farmer doesn’t enforce runtime fruit/vegetable type safety.

If the contract with the juice factory states that only Apple-arrays can be passed in, but this didn’t get checked by the farmer at runtime after return from the grocery store, their juice will now contain tomato juice as well (a strange combination I guess). Or worse, the juice factory will blow up because the apple peeling machine expects a certain toughness from the apple being peeled and tomatoes are much softer (note: I don’t know anything at all about the factory process involved in creating juice, so I’m just fantasizing about possible horror stories).

This mishap is illustrated below.

But who to blame? The grocery story followed the array-based contract exactly; arrays do not prevent writing operations, so putting tomatoes in is not a violation. Clearly the farmer needs a tighter contract that ensures – statically – the grocery store cannot put other fruits or vegetables in. What the farmer should use is an “pull-based enumerating device for apples” (an IEnumerator<Apple> that is) as shown below:

The spring depicts the enumerating behavior – you can only get things out but can’t push things in (ignore forceful mechanisms and ignoring the fact the spring might crush the apples :-)). Such an IEnumerable<T> is safely covariant because you can’t push anything in, so even if the farmer treats the IEnumerable<Apple> as an IEnumerable<Fruit> all he can do is get pieces of fruit (which always will happen to be apples) out.

This illustrates why arrays T[] are not safely covariant and why IEnumerable<T> is. Or in code:

namespace Rural.Fields
using Market;
using Industry;

internal sealed class Farm
private GroceryStore _store = …;
private JuiceFactory _juice = …;
private const decimal APPLE_PRICE = 0.5;

private Apple[] PluckAppleTrees() { … }

public void Work()
Apple[] apples = PluckAppleTrees();
_store.SellFruits(apples); // here the array is treated covariantly!
int sold = apples.Where(apple => apple != null).Count();
_store.SendInvoice(sold * APPLE_PRICE);
_juice.ProduceAppleJuice(apples); // here the array is treated invariantly
_juice.SendInvoice((apples.Length – sold) * APPLE_PRICE * 0.8 /* price of rotten apple */);

namespace Market
public sealed class GroceryStore
public void SellFruits(Fruit[] fruits) { … }
public void SendInvoice(decimal amount) { … }

namespace Industry
public sealed class JuiceFactory
public void ProduceAppleJuice(Apple[] apples) { … }
public void SendInvoice(decimal amount) { … }

To ensure type safety for covariant arrays, the CLR injects runtime type checks for every attempt to store an element in an array. If an object with a type incompatible with the array in memory is attempted to be written to the array, an ArrayTypeMismatchException occurs. In our case above, if the SellFruits method of GroceryStory would try to write a Tomato object to the array that’s passed in (as Fruit[] but that was created as an Apple array, in the PluckAppleTrees method of the Farm) the CLR would detect this and throw the exception.

A more isolated sample:

    string[] names = new string[] { “Bart”, “John” };
object[] things = names;
things[0] = 123; // ArrayTypeMismatchException – 123 is not a string
Console.WriteLine(names[0].ToUpper() /* if the above would work, we’d call the non-existing ToUpper method on System.Int32 */);

Today’s co- and contra-variance support

C# actually has places where co- and contra-variance principles are being used, more specifically in delegate types. A typical sample where this comes in useful is when dealing with events.

delegate void CancelEvent(object sender, CancelEventArgs e);
delegate void ProgressEvent(object sender, ProgressEventArgs e);

sealed class Engine
public event CancelEvent Cancelled;
public event ProgressEvent ProgressChanged;

public void Run() { … }
public void Cancel() { … }

static class Program
static void Main()
var engine = new Engine();
engine.Cancelled += Logger;
engine.ProgressChanged += Logger;
// run engine, etc

void Logger(object sender, EventArgs e) { … }

Here we’re attaching an event handler to two events which have different signatures. However, the signature of Logger uses a common supertype of both events’ event arguments type, so the compiler allows this. The reason it does is because input parameters can safely be treated contravariantly. In other words, we’re getting in a more derived type for the event arguments but we treat it as less derived. As the argument appears in an input position, this is safe to do.

Here’s a little exercise for the reader. The following fragment compiles fine because of contravariant treatment for delegate parameters:

class Contra
static void Main()
var b = new Bar();
b.Event += Handler;

static void Handler(object o /* contravariant treatment when used with delegate type D */)

delegate void D(string s);

class Bar
public event D Event;

However, if we change the parameter on D by adding a ref modifier, it doesn’t work anymore.

    static void Handler(ref object o /* ??? */)

delegate void D(ref string s);

Why? Illustrate with a sample showing why contravariant treatment would be malicious if it were allowed here…


Covariant and contravariant generic parameters in the CLI

With the second release of the CLR, as part of .NET Framework 2.0, generics were introduced based on the work done by Don Syme and Andrew Kennedy in the Gyro project. Right from the start, generic parameters have supported the declaration of desired variance behavior. Section 8 in Partition I of the ECMA-335 spec for the CLI states:

In addition, CLI supports covariant and contravariant generic parameters, with the following characteristics:

  • It is type-safe (based on purely static checking)
  • Simplicity: in particular, variance is only permitted on generic interfaces and generic delegates (not classes or value-types)
  • Languages not wishing to support variance can ignore the feature, and treat all generic types as non-variant.
  • Enable implementation of more complex covariance scheme as used in some languages, e.g. Eiffel.

but so far, C# and VB have been following the third bullet, ignoring the feature. Before we go there, we should have a look at how generics variance is surfaced through IL, proving its support in the CLR today. More information on this can be found in paragraph 9.5 of Partition II:

The CLI supports covariance and contravariance of generic parameters, but only in the signatures of interfaces and delegate classes. 
The symbol “+” is used in the syntax of §10.1.7 to denote a covariant generic parameter, while “-” is used to denote a contravariant generic parameter.

Our two main samples have been IEnumerable<T> and IComparer<T>. Let’s define our own interfaces for both (in C#) and see how it looks like at the level of IL:

    interface IEnumerable<T>
IEnumerator<T> GetEnumerator();

interface IEnumerator<T>
bool MoveNext();
T Current { get; }

interface IComparer<T>
int Compare(T arg1, T arg2);

By default generic interface and delegate types are invariant:

However, we can roundtrip through IL to make those types covariant (for IEnumer*<T>) or contravariant (for IComparer<T>)in their generic parameter T. The difference is subtle: adding a + (covariant) or a - (contravariant) to the generic parameter T.

    .class interface private abstract auto ansi IEnumerable`1<+T>
.method public hidebysig newslot abstract virtual
instance class IEnumerator`1<!T>
GetEnumerator() cil managed
} // end of method IEnumerable`1::GetEnumerator

} // end of class IEnumerable`1

.class interface private abstract auto ansi IEnumerator`1<+T>
.method public hidebysig newslot abstract virtual
instance bool MoveNext() cil managed
} // end of method IEnumerator`1::MoveNext

.method public hidebysig newslot specialname abstract virtual
instance !T get_Current() cil managed
} // end of method IEnumerator`1::get_Current

.property instance !T Current()
.get instance !T IEnumerator`1::get_Current()
} // end of property IEnumerator`1::Current
} // end of class IEnumerator`1

.class interface private abstract auto ansi IComparer`1<-T>
.method public hidebysig newslot abstract virtual
instance int32 Compare(!T arg1,
!T arg2) cil managed
} // end of method IComparer`1::Compare

} // end of class IComparer`1

Notice the ilasm tool doesn’t statically verify the correct use of variance annotations. It’s possible to mark a generic parameter as covariant while it’s used in input positions. It’s the responsibility of language compilers, e.g. for C#, to enforce such rules:

  • Covariant parameters should only be used in output positions: method return values, get-only properties or indexers.
  • Contravariant parameters should only occur in input positions: method parameters, set-only properties or indexers.


C# 4.0 support

Starting with C# 4.0, the language does support co- and contra-variance on generic delegate and interface type parameters. This feature has two sides to it:

  • As a consumer of generic interface or delegate types that behave either co- or contra-variantly, you can now do what you couldn’t do before

    IEnumerable<Person> people = from Student s in db.Students
where s.Age <= 25
select s;

IComparer<object> comp = new MyStringComparer(); // implements IComparer<string>
  • As a provider of generic interface or delegate types you can now specify the intended behavior. This is done using the out and in keywords, which respectively stand for covariant (output positions only) and contravariant (input positions only). The compiler enforces that parameters that behave in a non-invariant way are used appropriately:
    interface IEnumerable<out T>
IEnumerator<T> GetEnumerator();

interface IEnumerator<out T>
bool MoveNext();
T Current { get; }

interface IComparer<in T>
int Compare(T arg1, T arg2);

You might wonder why the compiler doesn’t infer the applicable variance annotation on behalf of the user. This could easily lead to “accidental” incorrect treatment as things evolve. Although it’s not a good idea to start changing interfaces, during development your interface types might be in flux and it would be disruptive if consumer code starts breaking all of a sudden because a generic parameter’s variance treatment has changed. It was felt it’d be better to have developers be explicit about variance.

Obviously a bunch of interface and delegate types in the BCL have been modified with variance annotations. IComparer<T>, IEnumerable<T> and IEnumerator<T> are just a few samples. Others include the Func and Action delegates:

delegate R Func<in T1, in T2, …, in Tn, out R>(T1 arg1, T2 arg2, …, Tn argn);
delegate void Action<in T1, in T2, …, in Tn>(T1 arg1, T2 arg2, …, Tn argn);

That’s it. I told’ya it was simple, no? The good thing is, you shouldn’t really know much about all of this: It Just Works (now also available for C# – inside joke).



Published at DZone with permission of Bart De Smet, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}