Covariance & Contravariance in C#

 

Before making the video, I checked for a Spanish term, and it does not exist; only the English version exists, so we will continue with that.

The first thing we're going to see is the object structure, or the model we will need to understand the post, since it can be quite complex without a clear structure:

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

public class Empleado : Persona
{
    public int Id { get; set; }
}

public class Jefe : Empleado
{
}

public class Becario : Empleado
{
}

This is the resulting entity relationship model:

example model

As you can see, you need to understand object-oriented programming very well, especially inheritance. If you need clarification, here is a link.

Our specific case is also closely related to generic types, for which there is also a post.

 

1 - Invariance

Before we explain covariance and contravariance, let's talk about the default case.

As we recall from the generics post, elements like List use T as a generic type, which means they do not have the in or out keywords or decorators.

As we can see in this example, we create a method that accepts a List of Persona and prints their types:

public static void ImprimirPersonas(List<Persona> personas)
{
    foreach (var persona in personas)
    {
        Console.WriteLine($"El elemento actual es de tipo {persona.GetType().ToString()}");
    }
}

And this code will accept all entities that inherit from Persona:

var listaPersonas = new List<Persona>()
{
    new Becario(), 
    new Jefe()
};

Utils.ImprimirPersonas(listaPersonas);

However, passing a collection that is a child of our original Persona type gives us an error:

var listaBecarios = new List<Becario>()
{
    new Becario(),
    new Becario()
};

Utils.ImprimirPersonas(listaBecarios);

And the error says it cannot convert from List<Persona> to List<Becario>.

So far so good, we've seen this before. But why?

The reason is that the generic type of List<T> is not covariant. As mentioned, List<T> is invariant, which means it only accepts collections where the T type is Persona.

The reason the compiler does this is to prevent us from doing this:

public static void ImprimirPersonas(List<Persona> personas)
{
    personas.Add(new Becario());
}

As you can see, it's the same method signature, but its content is completely wrong. We are adding an element to the list, which could violate other constraint conditions for the methods or types. Remember that we discussed how to restrict conditions in the post about generic types.

 

2 - Covariance

We use covariance mainly when we create immutable collections. With these collections, we prevent adding or removing elements.

To indicate a type is covariant, we must add the out keyword to the generic type.

In this example, I will use IEnumerable<out T>, which is a built-in .NET type very similar to lists, but includes the out keyword.

So, let's create the following method:

public static void ImprimirPersonas(IEnumerable<Persona> personas)
{
    foreach (var persona in personas)
    {
        Console.WriteLine($"El elemento actual es de tipo {persona.GetType().ToString()}");
    }
}

You can see it's almost identical to the previous one, we just change the type we pass to the method.

Since it's a type that allows generic and covariant, it means we can pass a list of any type that inherits from our main list type.

In this particular case, we can pass a list where the generic type could be Persona, Becario, Empleado, or Jefe.

If we go back to the previous example:

var listaBecarios = new List<Becario>()
{
    new Becario(),
    new Becario()
};

Utils.ImprimirPersonas(listaBecarios);

You can see it no longer gives us an error.

 

3 - Contravariance

We commonly use contravariance when passing functions as parameters, and we must indicate the generic type with the in keyword. In C#, a built-in type that accepts in is Action<in T>, which is a delegate.

We can create a method that receives an Action<Becario> as a parameter and invokes it with a new instance of Becario.

public static void RealizarActionBecario(Action<Becario> becarioAction)
{
    Becario becario = new Becario();
    becarioAction(becario);
}

We write the code to call this method, and it works correctly:

var accionBecario = new Action<Becario>(z => Console.WriteLine("Preparo el café"));
Utils.RealizarActionBecario(accionBecario);

However, the following code fails:

var accionJefe = new Action<Jefe>(z => Console.WriteLine("Organizo un meeting"));
Utils.RealizarActionBecario(accionJefe);

And that's because it can't convert from Action<Becario> to Action<Jefe>.

But here's where our in decorator comes into play. If we run the following code:

var accionEmpleado = new Action<Empleado>(z => Console.WriteLine("trabajo duro"));
Utils.RealizarActionBecario(accionEmpleado);

It works perfectly, and this is because by using the reserved word in, we allow its class and its base classes, in this case Empleado and Persona, to work correctly.

 

Conclusion

  • The use of Covariance and Contravariance is highly linked to generic types.
  • Invariance: only allows the specified type, with the reserved word in.
  • Covariance: reserved word out; allows a type or its derived types.
  • Contravariance: reserved word in; allows a type or its implementing types, or parent types.
  • Understanding how this works is important.
  • It helps us write more robust and structured code.
This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café