Type variance defines how one type can be substituted for another type.
We will use the following class hierachy:
class Shape { } class Rectangle : Shape { }
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; } // ... }
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 definitions can be covariant or contravariant:
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);
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 } } }
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 } } }
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; } } }