#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,146 – Generics Don’t Support Covariance

In C#, arrays are covariant, so you can do the following:

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

Generics in C#, however, are not covariant (they are invariant). The following code will not compile.

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

#1,145 – Using Reflection on Generic Type Parameters

You can use the typeof operator to get information about one or more type parameters within a generic type.  In the example below, we get information about the type argument used when the generic type is constructed.

    public class Pile<T>
    {
        List<T> pile = new List<T>();

        public Pile()
        {
            Type t = typeof(T);
            Console.WriteLine("Constructing Pile<T> type, with T as [{0}]", t.Name);
        }

        public void Add(T item)
        {
            if (!pile.Contains(item))
                pile.Add(item);
        }
    }

Here’s the output when we construct Pile<Dog> and Pile<int>:

            Pile<Dog> pack = new Pile<Dog>();
            Pile<int> justNumbers = new Pile<int>();

1145-001

#1,144 – Getting Type Information about a Generic Type

You can use the typeof operator to get information about a particular type.  The operator returns an instance of the Type class, which you can then query to get info about the type.

You can get type information about generic types in two different ways.  You can use the name of the type with empty angle brackets to get information about the generic type.  Or you can supply type arguments to get information about a particular constructed type.

        private static void DumpInfoForType(Type t)
        {
            Console.WriteLine("Type {0}:", t.Name);
            Console.WriteLine("  IsGenericType: {0}", t.IsGenericType);
            Console.WriteLine("  IsGenericTypeDefinition: {0}", t.IsGenericTypeDefinition);
            Console.WriteLine("  IsConstructedGenericType: {0}", t.IsConstructedGenericType);
            Console.WriteLine("  ContainsGenericParameters: {0}", t.ContainsGenericParameters);

            if (t.IsConstructedGenericType)
            {
                foreach (Type targ in t.GenericTypeArguments)
                    Console.WriteLine("Arg: {0}", targ.Name);
            }
        }

        static void Main(string[] args)
        {
            DumpInfoForType(typeof(Pile<>));
            DumpInfoForType(typeof(Pile<Dog>));
        }

The first type is a generic type definition with generic parameters. The second is a constructed generic type with a Dog argument.

1144-001

#1,143 – Implement IEquatable in a Generic Type

You can use the EqualityComparer<T> type to implement IEquatable<T> for a generic type.

In the example below, we implement IEquatable<TwoThings<T1,T2>> for our TwoThings<T1,T2> type.  The EqualityComparer<T> type is used to properly compare member data, regardless of the types.  Without this, the compiler wouldn’t know how to compare two T1 or two T2 objects.

    public class TwoThings<T1,T2> : IEquatable<TwoThings<T1,T2>>
    {
        T1 thing1;
        T2 thing2;

        EqualityComparer<T1> comparer1 = EqualityComparer<T1>.Default;
        EqualityComparer<T2> comparer2 = EqualityComparer<T2>.Default;

        public TwoThings(T1 thing1, T2 thing2)
        {
            this.thing1 = thing1;
            this.thing2 = thing2;
        }

        // IEquatable
        public bool Equals(TwoThings<T1, T2> other)
        {
            return (comparer1.Equals(thing1, other.thing1) &&
                    comparer2.Equals(thing2, other.thing2));
        }
    }

Now suppose that we construct this class using types that implement value equality.  The Equals method then behaves as we’d expect.

            Dog myDog = new Dog("Jack", 5);
            TwoThings<Dog, string> first = new TwoThings<Dog, string>(myDog, "Feisty");

            Dog otherDog = new Dog("Jack", 5);
            TwoThings<Dog, string> second = new TwoThings<Dog, string>(otherDog, "Feisty");

            // Returns true
            bool eq = first.Equals(second);

#1,142 – Using EqualityComparer to Compare Objects in Generic Types

By default, you can’t use equality operators on objects whose type is a type argument within a generic class.  If you constrain an argument to be a reference type, you can use equality operators to do reference equality checks.  But you can‘t make use of overloaded equality functionality.

You can do a true equality check between objects using the EqualityComparer<T> class.  Below is an example.  EqualityComparer<T> makes use of overloaded equality logic in the Dog class, even though we haven’t constrained the T argument.

    public class Pile<T>
    {
        List<T> pile = new List<T>();
        EqualityComparer<T> comparer = EqualityComparer<T>.Default;

        public void Add(T item)
        {
            if (!pile.Contains(item))
                pile.Add(item);
        }

        public bool IsFirst(T item)
        {
            // Reference equality only, if we do
            //   where T : class
            //bool equalStd = (pile[0] == item);

            // Makes use of overloaded Equality
            // functionality in T
            bool equalBetter = comparer.Equals(pile[0], item);

            return equalBetter;
        }
    }

1142-001

Follow

Get every new post delivered to your Inbox.

Join 415 other followers