#1,156 – Covariance and Generic Delegate Types

As with generic interfaces, generic delegate types are covariant if you mark output parameters as out.

In the example below, we’re unable to assign an instance of ReturnDelegate<Terrier> to ReturnDelegate<Dog>.  The delegate type is not covariant.

        public delegate T ReturnDelegate<T>();

        public static Terrier GenerateTerrier()
        {
            return new Terrier("Bubba");
        }

        static void Main(string[] args)
        {
            ReturnDelegate<Terrier> del = GenerateTerrier;

            // Error: Cannot implicitly convert ReturnDelegate<Terrier>
            // to ReturnDelegate<Dog>
            ReturnDelegate<Dog> del2 = del;
        }

We can get the delegate type to behave covariantly by marking its type parameter with the out keyword.

        public delegate T ReturnDelegate<out T>();

We can now assign an instance of ReturnDelegate<Terrier> to a variable of type ReturnDelegate<Dog>.

#1,148 – When to Use a Generic Covariant Interface

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);
        }

1184-001

#1,147 – Why Generics Don’t Support Covariance

In C#, arrays support covariance, but generics do not.  Generics aren’t covariant in order to avoid the problem that we have with covariant arrays.  With arrays, we get type mismatch exceptions when trying to put something into the array of the wrong type.  The core problem here is that the array looks syntactically like an array of the base type, but is actually an array of the more derived type.

Generics in C# aren’t covariant in order to avoid this same problem.  The language designers decided to support covariance for arrays but not for generics.  The reasons are likely more historical than technical.

            // Array covariance--OK
            Dog[] dogs = new Terrier[5];

            // The problem with array covariance.
            // Compiler allows, but throws ArrayTypeMismatchException
            // at run-time
            dogs[0] = new BorderCollie("Shep");

            // Generic covariance--not allowed (compiler error)
            List<Dog> moreDogs = new List<Terrier>();

#1,068 – Generic IEnumerable Interface Is Covariant

A covariant generic interface is one that allows an assignment from a constructed version of the interface on a derived class to a constructed version of the interface for a base class.

For example:

            // If herders is (or implements) IFirstAndLast<BorderCollie>:
            IFirstAndLast<Dog> dogLine = herders;

The IEnumerable<T> interface in System.Collections.Generic is covariant.  This means that you can assign a collection of a given type to an IEnumerable<T> where the represents a type further up the inheritance chain.

For example:

            List<BorderCollie> someBCs = new List<BorderCollie> {
                new BorderCollie("Shep"),
                new BorderCollie("Kirby")
            };

            // Because BorderCollie derives from Dog,
            // we can do the following
            IEnumerable<Dog> dogList = someBCs;

If MerleBorderCollie inherits from BorderCollie, which in turn inherits from Dog, we can also do:

            List<MerleBorderCollie> merles = new List<MerleBorderCollie>
            {
                new MerleBorderCollie("Lady")
            };
            IEnumerable<Dog> moredogs = merles;

#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)
            {
                theQueue.Enqueue(d);
            }

            public void RemoveFromFront(T d)
            {
                theQueue.Dequeue();
            }

            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;

 

#1,066 – Constraining a Type Parameter on a Generic Interface

You can constrain a type parameter on a generic interface so that it can be used only as the output type of methods in the interface and not as a type of any method parameters.  You do this using the out keyword on the type parameter.

        public interface FirstAndLast<out T>
        {
            T First();
            T Last();
            void AddThing(T item);   // won't compile
        }

This seems like an odd thing to do, but ensuring that a particular type parameter is used only as output allows us to later use this generic interface covariantly.  (I’ll explain this in an upcoming post).

#1,065 – Cases When Array Covariance Doesn’t Work

Array covariance in C# allows you to assign an array of objects of a more derived type to an array of objects of a base type.  An array of type B can be assigned to an array of type A is type B is implicitly convertible to A.  More specifically, an implicit reference conversion from B to A must exist.

This restriction means that array covariance will not work for implicit conversions that are not implicit reference conversions.  Specifically, arrays are not covariant for implicit numeric conversions, boxing conversions or implicit custom conversions.

            // Covariance does work when implicit reference conversion
            // exists for underlying types
            //   - From S to T when S derives from T
            //   - From S to object or dynamic
            //   - From S to interface-type T when S implements T
            //   - From interface-type S to interface-type T when S derives from T
            // All examples below work (compile)
            Terrier[] tarr1 = new Terrier[2];
            Dog[] darr1 = tarr1;
            object[] oarr1 = tarr1;
            dynamic[] dynarr1 = tarr1;
            Dog[] darr2 = new Dog[2];
            IBark[] ibarr1 = darr2;
            IBarkBetter[] ibb1 = new IBarkBetter[2];
            IBark[] ibarr2 = ibb1;

            // Covariance does NOT work with implicit numeric conversions
            //  e.g. byte to short
            byte[] barr1 = new byte[2];
            short[] sarr1 = barr1;  // Compile-time error

            // Covariance does NOT work with implicit nullable conversions
            int[] inarr1 = new int[2];
            int?[] innarr1 = inarr1;  // Compile-time error

            // Covariance does NOT work with implicit boxing conversions
            int[] inarr2 = new int[2];
            object[] oarr2 = inarr2;  // Compile-time error

            // Covariance does NOT work with implicit custom conversions
            Cow c = new Cow("Bessie");
            Dog d = c;  // Implicit Cow to Dog works (custom conversion)
            Cow[] herd = new Cow[2];
            Dog[] pack = herd;  // Compile-time error