DEV Community

mohamed Tayel
mohamed Tayel

Posted on

2

Understanding Variance in C#

Variance in C# is a powerful concept that determines how types can be substituted for one another when using generics. This article simplifies variance and demonstrates its practical use with clear examples.


What is Variance?

Variance answers the question: "Can one type be assigned to another?"

This is especially important when working with generics, where you need to decide whether one generic type (e.g., IEnumerable<string>) can replace another (e.g., IEnumerable<object>).

Variance in C# is divided into three categories:

  1. Covariance: Allows assigning a more specific type to a less specific type.
  2. Contravariance: Allows assigning a less specific type to a more specific type.
  3. Invariance: Requires exact type matches (no substitution allowed).

1. Covariance (out)

  • Definition: Allows assigning a more specific type to a less specific type (e.g., string to object).
  • When to Use: For output-only scenarios, where a generic type only produces data.

Example: Covariance with IEnumerable<T>

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        IEnumerable<string> strings = new List<string> { "Hello", "World" };
        IEnumerable<object> objects = strings; // Covariance works!

        foreach (object obj in objects)
        {
            Console.WriteLine(obj); // Outputs: Hello, World
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Why it works:

The IEnumerable<string> produces strings, which are assignable to object since string is derived from object.


2. Contravariance (in)

  • Definition: Allows assigning a less specific type to a more specific type (e.g., object to string).
  • When to Use: For input-only scenarios, where a generic type only consumes data.

Example: Contravariance with IComparer<T>

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        IComparer<object> objectComparer = Comparer<object>.Default;
        IComparer<string> stringComparer = objectComparer; // Contravariance works!

        List<string> words = new List<string> { "Apple", "Banana", "Cherry" };
        words.Sort(stringComparer);

        foreach (string word in words)
        {
            Console.WriteLine(word); // Outputs: Apple, Banana, Cherry
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Why it works:

The IComparer<object> can compare any objects, so it can also handle strings since every string is an object.


3. Invariance

  • Definition: No type substitution is allowed; types must match exactly.
  • When to Use: For collections or scenarios where substitution could lead to runtime errors.

Example: Invariance with List<T>

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> strings = new List<string> { "Hello", "World" };
        // List<object> objects = strings; // ❌ This won't compile.

        List<object> objects = new List<object> { "Hello", "World" };
        objects.Add(42); // Works fine since it's a List<object>.

        foreach (object obj in objects)
        {
            Console.WriteLine(obj); // Outputs: Hello, World, 42
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Why it works:

List<T> is invariant to maintain type safety. Allowing List<string> to act as List<object> would risk runtime errors if the wrong type is added.


4. Array Covariance (The Pitfall)

Arrays in C# are covariant, but this can lead to runtime errors.

Example: Unsafe Array Covariance

using System;

class Program
{
    static void Main()
    {
        string[] strings = { "Hello", "World" };
        object[] objects = strings; // Array covariance works.

        try
        {
            objects[0] = DateTime.Now; // ❌ Runtime error.
        }
        catch (ArrayTypeMismatchException ex)
        {
            Console.WriteLine("Error: " + ex.Message); // Outputs an error message.
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Why it fails:

The array expects only string values, but assigning a DateTime causes a runtime exception.


5. Covariance and Contravariance with Delegates

Variance also applies to delegates, allowing flexible type assignments.

Covariant Delegate Example

using System;

delegate object CovariantDelegate();

class Program
{
    static void Main()
    {
        CovariantDelegate delString = GetString;
        CovariantDelegate delObject = delString; // Covariance works!

        Console.WriteLine(delObject()); // Outputs: Hello, Covariance!
    }

    static string GetString() => "Hello, Covariance!";
}
Enter fullscreen mode Exit fullscreen mode

Contravariant Delegate Example

using System;

delegate void ContravariantDelegate(string input);

class Program
{
    static void Main()
    {
        ContravariantDelegate delObject = PrintObject;
        ContravariantDelegate delString = delObject; // Contravariance works!

        delString("Hello, Contravariance!"); // Outputs: Received: Hello, Contravariance!
    }

    static void PrintObject(object obj)
    {
        Console.WriteLine("Received: " + obj);
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

Type Direction Use Case Example
Covariance More → Less Specific Reading data IEnumerable<T>
Contravariance Less → More Specific Writing data IComparer<T>
Invariance No substitution Collections List<T>

Summary

  1. Use covariance (out) when types are used as outputs.
  2. Use contravariance (in) when types are used as inputs.
  3. Generics are invariant by default for safety and consistency.
  4. Be cautious with array covariance, as it can lead to runtime errors.

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

Heroku

This site is powered by Heroku

Heroku was created by developers, for developers. Get started today and find out why Heroku has been the platform of choice for brands like DEV for over a decade.

Sign Up

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay