Leveling Up in Covariant Return Types

19 Jun 23 (2 min read)

Let's begin with some definitions because this one is confusing (liberated from the original Microsoft docs).

Covariance enables you to use a more derived type than originally specified. You can assign an instance of IEnumerable<Derived> to a variable of type IEnumerable<Base>.

Contravariance enables you to use a more generic (less derived) type than originally specified. You can assign an instance of Action<Base> to a variable of type Action<Derived>.

Invariance means that you can use only the type originally specified. An invariant generic type parameter is neither covariant nor contravariant. You cannot assign an instance of List<Base> to a variable of type List<Derived> or vice versa.

The covariance examples are pretty much what you are used to in the real world. You can make assignment the look like ordinary polymorphism (Microsoft's words not mine).

// Assignment compatibility. 
string str = "test"; 
// An object of a more derived type is assigned to an object of a less derived type. 
object obj = str; 

// Covariance. 
IEnumerable<string> strings = new List<string>(); 
// An object that is instantiated with a more derived type argument 
// is assigned to an object instantiated with a less derived type argument. 
// Assignment compatibility is preserved. 
IEnumerable<object> objects = strings;   

Covariance makes sense when doing assignments. You can always cast down. Covariant return type now makes sense. This feature allows you to return a more derived type.

class Compilation
{
    public virtual Compilation WithOptions(Options options)...
}

class CSharpCompilation : Compilation
{
    public override CSharpCompilation WithOptions(Options options)...
}  

The only problem is I don't know when I would ever use this. Maybe I am just too used to naming the method something different.

< back