#1,067 – Covariance and Generic Interfaces

Generic interfaces in C# are covariant, provided that their type parameters are constrained with the out keyword.

Let’s assume that we have the following interface.

        public interface FirstAndLast<T>
            T First();
            T Last();

Then assume that we define a generic class that implements this interface:

        public class LineOf<T> : IFirstAndLast<T>
            private Queue<T> theQueue = new Queue<T>();

            public void AddToEnd(T d)

            public void RemoveFromFront(T d)

            public T First()
                return theQueue.Peek();

            public T Last()
                return theQueue.Last();

Great, now we can use this class as follows:

            LineOf<Dog> dogs = new LineOf<Dog>();
            dogs.AddToEnd(new Dog("Lassie"));
            dogs.AddToEnd(new Dog("Rin Tin Tin"));
            Console.WriteLine(dogs.First().ToString() + ", " + dogs.Last().ToString());

            LineOf<BorderCollie> herders = new LineOf<BorderCollie>();
            herders.AddToEnd(new BorderCollie("Kirby"));
            herders.AddToEnd(new BorderCollie("Shep"));
            Console.WriteLine(herders.First().ToString() + ", " + herders.Last().ToString());

At this point, we might want to convert LineOf<BorderCollie> to IFirstAndLast<Dog>, for example we might have a method that returns IFirstAndLast<Dog>.

However, if we do the following, we get a compiler error, saying that we can’t implicitly cast LineOf<BorderCollie> to IFirstAndLast<Dog>:

            IFirstAndLast<Dog> dogLine = herders;

We could do a cast, as shown below. The code now compiles, but the conversion fails at runtime.

            IFirstAndLast<Dog> dogLine = (IFirstAndLast<Dog>)herders;

We want the IFirstAndLast<T> interface to be covariant, i.e. to allow this assignment.  To support this, we just need to add the out keyword in the interface.

        public interface IFirstAndLast<out T>

We can do this because T is only used in this interface as a return value.  Having done this, we can now do the following.  This compiles and the assignment succeeds at runtime.

            IFirstAndLast<Dog> dogLine = herders;