Variance in Generic Types (C# Programming Guide)

One of the main benefits of the addition of generics to C# is the ability to easily create strongly typed collections by using types in the System.Collections.Generic namespace. For example, you can create a variable of type List<int>, and the compiler will check all accesses to the variable, ensuring that only ints are added to the collection. This is a big usability improvement over the untyped collections available in version 1.0 of C#.

Unfortunately, strongly typed collections have drawbacks of their own. For example, suppose you have a strongly typed List<object> and you want to append all the elements from a List<int> to your List<object>. You might want to be able to write code as in the following example:

List<int> ints = new List<int>();
ints.Add(1);
ints.Add(10);
ints.Add(42);
List<object> objects = new List<object>();

// The following statement does not compile 
// becuase �ints� is not IEnumerable<object>. 
//objects.AddRange(ints); 

In this case, you would want to treat a List<int> which is also an IEnumerable<int>, as an IEnumerable<object>. This seems like a reasonable thing to do, as int is convertible to object. It is very similar to being able to treat a string[] as an object[] as you can do today. If you find yourself in this situation, the feature you are looking for is called generics variance, which treats an instantiation of a generic type, in this case IEnumerable<int>, as a different instantiation of that same type, in this case IEnumerable<object>.

Because C# does not support variance for generic types, when you encounter cases such as this you will have to try one of several techniques you can use to work around this problem. For the simplest cases, such as the case of a single method such as AddRange in the previous example, you can declare a simple helper method to do the conversion for you. For example, you could write this method:

// Simple workaround for single method 
// Variance in one direction only 
public static void Add<S, D>(List<S> source, List<D> destination)
    where S : D
{
    foreach (S sourceElement in source)
    {
        destination.Add(sourceElement);
    }
}

Which enables you to do this:

// does compile
VarianceWorkaround.Add<int, object>(ints, objects);

The previous example shows some characteristics of a simple variance workaround. The helper method takes two type parameters, for the source and destination, and the source type parameter S has a constraint which is the destination type parameter D. This means that the List<> being read from must contain elements which are convertible to the element type of the List<> being inserted into. This enables the compiler to enforce that int is convertible to an object.

Defining a single method to work around variance problems is not too bad. Unfortunately variance issues can become very complex quickly. The next level of complexity is when you want to treat an interface of one instantiation as an interface of another instantiation. For example, you have an IEnumerable<int>, and you want to pass it to a method that only takes an IEnumerable<object>. Again, this makes some sense because you can think of an IEnumerable<object> as a sequence of objects, and an IEnumerable<int> is a sequence of ints. Since ints are objects, a sequence of ints should be treatable as a sequence of objects. For example:

static void PrintObjects(IEnumerable<object> objects)
{
    foreach (object o in objects)
    {
        Console.WriteLine(o);
    }
}

Which you would want to call as in the following example:

// The following statement does not compile 
// becuase �ints� is not IEnumerable<object>. 
//PrintObjects(ints);

The workaround for the interface case, is to create a wrapper object that does the conversions for each member of the interface. This would look something like the following code segment, taken from the example at the end of the topic.

// Workaround for interface 
// Variance in one direction only so type expressinos are natural 
public static IEnumerable<D> Convert<S, D>(IEnumerable<S> source)
    where S : D
{
    return new EnumerableWrapper<S, D>(source);
}

private class EnumerableWrapper<S, D> : IEnumerable<D>
    where S : D
{

Which enables you to write the following code.

PrintObjects(VarianceWorkaround.Convert<int, object>(ints));

Again, notice the use of a type parameter as a constraint on the wrapper class and helper method. The machinery is becoming more complex, but the code in the wrapper class is fairly straightforward. It just delegates to the members of the wrapped interface, doing nothing more than straightforward type conversions along the way. Why not have the compiler allow the conversion from IEnumerable<int> to IEnumerable<object> directly?

Although variance is type-safe in the case where you are looking at read-only views of your collections, variance is not type-safe in the case where both read and write operations are involved. For example, the IList<> interface could not be dealt with in this automatic way. You can still write a helper that will wrap all read operations on an IList<> in a type-safe manner, but the wrapping of write operations cannot be done so easily.

Here is part of a wrapper for dealing with variance on the IList<T> interface that shows the problems that occur with variance in both the read and write directions:

private class ListWrapper<S, D> : CollectionWrapper<S, D>, IList<D>
    where S : D
{
    public ListWrapper(IList<S> source) : base(source)
    {
        this.source = source;
    }

    public int IndexOf(D item)
    {
        if (item is S)
        {
            return this.source.IndexOf((S) item);
        }
        else
        {
            return -1;
        }
    }

    // variance the wrong way ... 
    // ... can throw exceptions at runtime 
    public void Insert(int index, D item)
    {
        if (item is S)
        {
            this.source.Insert(index, (S)item);
        }
        else
        {
            throw new Exception("Invalid type exception");
        }
    }

The Insert method on the wrapper has a problem. It takes as an argument a D, but it must insert it into an IList<S>. Because D is a base type of S, not all Ds are Ss, and the Insert operation may fail. This example has an analogue with variance with arrays. When inserting an object into an object[], a dynamic type check is performed because the object[] may in fact be a string[] at run time. For example:

object[] objects = new string[10];

// no problem, adding a string to a string[]
objects[0] = "hello"; 

// runtime exception, adding an object to a string[]
objects[1] = new object(); 

In the IList<> example, the wrapper for the Insert method can just throw when the actual type does not match the desired type at run time. So again, you could imagine that the compiler would automatically generate the wrapper for the programmer. There are cases where this policy is not the correct thing to do, however. The IndexOf method searches the collection for the item provided, and returns the index in the collection if the item is found. However, if the item is not found, the IndexOf method just returns -1, it does not throw. This kind of wrapping cannot be provided by an automatically generated wrapper.

So far, the two simplest work arounds for generic variance issues have been described. However, variance issues can get arbitrarily complex. For example, when you treat a List<IEnumerable<int>> as a List<IEnumerable<object>>, or treating a List<IEnumerable<IEnumerable<int>>> as a List<IEnumerable<IEnumerable<object>>>.

When you generate these wrappers to work around variance problems in your code, you can introduce a significant overhead in your code. Also, it can introduce referential identity issues, as each wrapper does not have the same identity as the original collection which could lead to subtle bugs. When using generics, you should select your type instantiation to reduce mismatches between components that are tightly coupled. This may require some compromises in the design of your code. As always, design involves tradeoffs between conflicting requirements, and the constraints of the types system in the language should be considered in your design process.

There are type systems that include generic variance as a first class part of the language. Eiffel is the prime example of this. However, the inclusion of generics variance as a first class part of the type system would significantly increase the complexity of the type system of C#, even in relatively straightforward scenarios that do not involve variance. As a result, the C# designers felt that not including variance was the appropriate choice for C#.

Here is the full source code for the previous examples.

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

static class VarianceWorkaround
{
    // Simple workaround for single method 
    // Variance in one direction only 
    public static void Add<S, D>(List<S> source, List<D> destination)
        where S : D
    {
        foreach (S sourceElement in source)
        {
            destination.Add(sourceElement);
        }
    }

    // Workaround for interface 
    // Variance in one direction only so type expressinos are natural 
    public static IEnumerable<D> Convert<S, D>(IEnumerable<S> source)
        where S : D
    {
        return new EnumerableWrapper<S, D>(source);
    }

    private class EnumerableWrapper<S, D> : IEnumerable<D>
        where S : D
    {
        public EnumerableWrapper(IEnumerable<S> source)
        {
            this.source = source;
        }

        public IEnumerator<D> GetEnumerator()
        {
            return new EnumeratorWrapper(this.source.GetEnumerator());
        }

        IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private class EnumeratorWrapper : IEnumerator<D>
        {
            public EnumeratorWrapper(IEnumerator<S> source)
            {
                this.source = source;
            }

            private IEnumerator<S> source;

            public D Current
            {
                get { return this.source.Current; }
            }

            public void Dispose()
            {
                this.source.Dispose();
            }

            object IEnumerator.Current
            {
                get { return this.source.Current; }
            }

            public bool MoveNext()
            {
                return this.source.MoveNext();
            }

            public void Reset()
            {
                this.source.Reset();
            }
        }

        private IEnumerable<S> source;
    }

    // Workaround for interface 
    // Variance in both directions, causes issues 
    // similar to existing array variance 
    public static ICollection<D> Convert<S, D>(ICollection<S> source)
        where S : D
    {
        return new CollectionWrapper<S, D>(source);
    }


    private class CollectionWrapper<S, D> 
        : EnumerableWrapper<S, D>, ICollection<D>
        where S : D
    {
        public CollectionWrapper(ICollection<S> source)
            : base(source)
        {
        }

        // variance going the wrong way ...  
        // ... can yield exceptions at runtime 
        public void Add(D item)
        {
            if (item is S)
            {
                this.source.Add((S)item);
            }
            else
            {
                throw new Exception(@"Type mismatch exception, due to type hole introduced by variance.");
            }
        }

        public void Clear()
        {
            this.source.Clear();
        }

        // variance going the wrong way ...  
        // ... but the semantics of the method yields reasonable semantics 
        public bool Contains(D item)
        {
            if (item is S)
            {
                return this.source.Contains((S)item);
            }
            else
            {
                return false;
            }
        }

        // variance going the right way ...  
        public void CopyTo(D[] array, int arrayIndex)
        {
            foreach (S src in this.source)
            {
                array[arrayIndex++] = src;
            }
        }

        public int Count
        {
            get { return this.source.Count; }
        }

        public bool IsReadOnly
        {
            get { return this.source.IsReadOnly; }
        }

        // variance going the wrong way ...  
        // ... but the semantics of the method yields reasonable  semantics 
        public bool Remove(D item)
        {
            if (item is S)
            {
                return this.source.Remove((S)item);
            }
            else
            {
                return false;
            }
        }

        private ICollection<S> source;
    }

    // Workaround for interface 
    // Variance in both directions, causes issues similar to existing array variance 
    public static IList<D> Convert<S, D>(IList<S> source)
        where S : D
    {
        return new ListWrapper<S, D>(source);
    }

    private class ListWrapper<S, D> : CollectionWrapper<S, D>, IList<D>
        where S : D
    {
        public ListWrapper(IList<S> source) : base(source)
        {
            this.source = source;
        }

        public int IndexOf(D item)
        {
            if (item is S)
            {
                return this.source.IndexOf((S) item);
            }
            else
            {
                return -1;
            }
        }

        // variance the wrong way ... 
        // ... can throw exceptions at runtime 
        public void Insert(int index, D item)
        {
            if (item is S)
            {
                this.source.Insert(index, (S)item);
            }
            else
            {
                throw new Exception("Invalid type exception");
            }
        }

        public void RemoveAt(int index)
        {
            this.source.RemoveAt(index);
        }

        public D this[int index]
        {
            get
            {
                return this.source[index];
            }
            set
            {
                if (value is S)
                    this.source[index] = (S)value;
                else 
                    throw new Exception("Invalid type exception.");
            }
        }

        private IList<S> source;
    }
}

namespace GenericVariance
{
    class Program
    {
        static void PrintObjects(IEnumerable<object> objects)
        {
            foreach (object o in objects)
            {
                Console.WriteLine(o);
            }
        }

        static void AddToObjects(IList<object> objects)
        {
            // this will fail if the collection provided is a wrapped collection 
            objects.Add(new object());
        }
        static void Main(string[] args)
        {
            List<int> ints = new List<int>();
            ints.Add(1);
            ints.Add(10);
            ints.Add(42);
            List<object> objects = new List<object>();

            // The following statement does not compile 
            // becuase �ints� is not IEnumerable<object>. 
            //objects.AddRange(ints);  

            // does compile
            VarianceWorkaround.Add<int, object>(ints, objects);

            // The following statement does not compile 
            // becuase �ints� is not IEnumerable<object>. 
            //PrintObjects(ints);

            PrintObjects(VarianceWorkaround.Convert<int, object>(ints));

            AddToObjects(objects); // this works fine
            AddToObjects(VarianceWorkaround.Convert<int, object>(ints));
        }
        static void ArrayExample()
        {
            object[] objects = new string[10];

            // no problem, adding a string to a string[]
            objects[0] = "hello"; 

            // runtime exception, adding an object to a string[]
            objects[1] = new object(); 
        }
    }
}

See Also

Concepts

C# Programming Guide

Reference

Generics (C# Programming Guide)

Other Resources

Covariance and Contravariance in C#, Part One