#1,148 – When to Use a Generic Covariant Interface
July 29, 2014 1 Comment
You can use a generic covariant interface as a workaround for the lack of covariance in generic types.
Suppose that we have a generic Pile<T> type as follows:
public class Pile<T> where T: class { List<T> pile = new List<T>(); public void Add(T item) { if (!pile.Contains(item)) pile.Add(item); } public T GetFirst() { return (pile.Count > 0) ? pile[0] : null; } }
Because this generic type doesn’t support covariance, we can run into the problem shown below.
static void Main(string[] args) { Pile<Terrier> lilDogs = new Pile<Terrier>(); lilDogs.Add(new Terrier("Jack")); lilDogs.Add(new Terrier("Eddie")); // Compile error: can't convert from Pile<Terrier> to // Pile<Dog> ShowFirstDog(lilDogs); } static void ShowFirstDog(Pile<Dog> dogs) { Console.WriteLine(dogs.GetFirst().Name); }
We could write a method that took a Pile<Terrier>, rather than Pile<Dog>, but the it still might be useful to have a Pile<Dog> version that could handle various subtypes of Dog.
The workaround is to add an interface to Pile<T> that contains only methods that have T as an output parameter. The interface marks the T parameter with the out keyword. The updated code is shown below.
public interface IGetFirst<out T> { T GetFirst(); } public class Pile<T> : IGetFirst<T> where T: class { List<T> pile = new List<T>(); public void Add(T item) { if (!pile.Contains(item)) pile.Add(item); } public T GetFirst() { return (pile.Count > 0) ? pile[0] : null; } }
We can now use IGetFirst<T> covariantly, rather than trying to use Pile<T> covariantly.
static void Main(string[] args) { Pile<Terrier> lilDogs = new Pile<Terrier>(); lilDogs.Add(new Terrier("Jack")); lilDogs.Add(new Terrier("Eddie")); // This works ! ShowFirstDog(lilDogs); } static void ShowFirstDog(IGetFirst<Dog> dogs) { Console.WriteLine(dogs.GetFirst().Name); }