User Tools

Site Tools


notes:csharp:covariance_contravariance

Covariance & Contravariance

Type variance defines how one type can be substituted for another type.

  • Covariance specifies how you can substitute a more derived type than the declared type.
  • Contravariance specifies how you can substitute a more base type than the declared type.

Generic Variance

We will use the following class hierachy:

class Shape { }
class Rectangle : Shape { }

Covariance

The DoSomething method can be called with a List<Shape>:

void DoSomething(IEnumerable<Shape> s) { }

It's because IEnumerable<T> as well as IEnumerator<T> limit T to output positions in its interface. The output positions are: function return values, property get accessors, and certain delegate positions. The out modifier tells the compiler that you won't modify any element in the sequence.

// IEnumerable<T> can be covariant only because IEnumerator<T> is also covariant.
public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}
 
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
    T Current { get; }
    // ...
}

Contravariance

The in modifier tells the compiler that the type parameter may only appear in input positions which are method parameters and some locations in delegate parameters. For example:

public interface IComparable<int T>
{
  int CompareTo(T other);
}

Delegate Variance

Delegate definitions can be covariant or contravariant:

  • Method parameters are contravariant (in).
  • Method return types are covariant (out).

Examples:

public delegate TResult Func<out TResult>();
public delegate TResult Func<in T, out TResult>(T arg);
public delegate void Action<in T>(T arg);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);

Delegate Variance

Covariance

using System.IO;
 
namespace ConsoleTest
{
    // TextWriter is a base class for both StreamWriter and StringWriter.
    public delegate TextWriter MyDelegate();
 
    class Program
    {
        // StreamWriter derives from TextWriter.
        public static StreamWriter Method1() { return null; }
 
        // StringWriter derives from TextWriter.
        public static StringWriter Method2() { return null; }
 
        static void Main()
        {
            MyDelegate d;
 
            // Covariance makes the following assignments possible.
            d = Method1; // StreamWriter --> TextWriter
            d = Method2; // StringWriter --> TextWriter
        }
    }
}

Contravariance

A single method MyMethod that accepts EventArgs is invoked by two different delegates, one passing a MouseEventArgs and the other passing a KeyEventArgs.

namespace ConsoleTest
{
    public delegate void MouseDelegate(MouseEventArgs args);
    public delegate void KeyDelegate(KeyEventArgs args);
 
    class Program
    {
        // EventArgs is a base type for MouseEventArgs and KeyEventArgs.
        public static void MyMethod(EventArgs args) { }
 
        static void Main()
        {
            MouseDelegate d1;
            KeyDelegate d2;
 
            // Both delegates can represent MyMethod.
            d1 = MyMethod; // EventArgs --> MouseEventArgs
            d2 = MyMethod; // EventArgs --> KeyEventArgs
        }
    }
}

Generic Delegate Type Parameter Variance

  • Mark a type parameter used only on the return value as covariant (out).
  • Mark any type parameters used only on parameters as contravariant (in).
namespace ConsoleTest
{
    // A delegate that supports covariance.
    public delegate T Delegate1<out T>();
 
    // A delegate that supports contravariance.
    public delegate void Delegate2<in T>(T arg);
 
    class Program
    {
        // handlers
        public static T Method1<T>() { T n = default(T); return n; }
        public static void Method2<T>(T arg) { }
 
        static void Main()
        {
            // A delegate that supports covariance.
            Delegate1<string> d1 = Method1<string>;
 
            // Covariance enables this assignment.
            Delegate1<object> o1 = d1;
 
            // ...
 
            // A delegate that supports contravariance.
            Delegate2<object> d2 = Method2<object>;
 
            // Contravariance enables this assignment.
            Delegate2<string> o2 = d2;
        }
    }
}
notes/csharp/covariance_contravariance.txt · Last modified: 2020/08/26 (external edit)