views:

233

answers:

2

Possible Duplicate:
IList&lt;Type&gt; to IList<BaseType>

I am programming in C# using .NET 2.0 and I don't understand why the cast below results in a null reference.

If you have an IList<IChild>, why can't you cast it to an IList<IParent> where IChild implements IParent.

using System.Collections.Generic;

namespace InterfaceTest
{
    public interface IParent
    {
    }

    public interface IChild : IParent
    {
    }

    public abstract class Parent : IParent
    {
    }

    public sealed class Child : Parent, IChild
    {
    }

    public sealed class Container
    {
     public IList<IChild> ChildInterfaceList
     {
      get;
      set;
     }

     public Container()
     {
      ChildInterfaceList = new List<IChild>();
     }
    }

    class Program
    {
 static void Main(string[] args)
 {
      Container container = new Container();

      var childInterfaceList = container.ChildInterfaceList;

      System.Diagnostics.Debug.Assert(childInterfaceList != null);

      var parentInterfaceList = container.ChildInterfaceList as IList<IParent>;

      //I don't expect parentInterfaceList to be null, but it is
      System.Diagnostics.Debug.Assert(parentInterfaceList != null);
     }
    }
}
+1  A: 

C# mutable collections do not support variance on the collection element type. Consider what would happen if you did this:

IList<IChild> kids = new List<IChild> {
    new Egg("Shelly"), new Egg("Egbert"), new Egg("Yoko")
};

var parents = kids as IList<IParent>;

parents.Add(new Sloth("Sid")); // what would happen here?

If the cast succeeded, the run-time type of parents would still be List<IChild> which would not accept something that does not implement IChild, and would have to throw an exception.

An acceptable conversion would be:

using System.Linq;
var parents = kids.Cast<IParent>().ToList();

which would create a copy of the original list, but with List<IParent> as its run-time type.

Generic variance is supported in C# 4.0, but mutable collections cannot safely be made variant. Only pure read-only interfaces like IEnumerable can safely be made covariant, and pure write-only interfaces (are there any?) can safely be made contravariant.

Jeffrey Hantin
Yes there are. IComparer<T> for example is effectively a write-only interface for the purposes of variance. T's go in, results come out, but T's never come out. A comparer that can compare any two animals can also compare any two giraffes, so IComparer<Animal> can be converted to IComparer<Giraffe> contravariantly.
Eric Lippert
An object of type IEnumerable is not guaranteed to be readonly.IEnumerable<T> someList = new List<T>(); //someList very much writable :)
Rune FS
Right, but irrelevant. There's no way to get at the mutating methods via IEnumerable, which is what is relevant. That fact allows us to safely make IE<T> covariant in T.
Eric Lippert
@Eric Lippert: I guess I got my directions of variance mixed up -- contravariance inverts the narrower-than relationship between types, making IComparer<Animal> onto IComparer<Giraffe> a widening conversion. I think the use of a cast attempting to widen the *type parameter* in the question mixed me up. Serves me right for posting while feeling ill. :)
Jeffrey Hantin
A: 

This is a common pitfall.

Consider this simple explanation-by-example:

Everything in .NET inherits from Object, right? So, let's suppose what you wanted were possible...

List<int> ints = new List<int>();
List<object> objects = ints as List<object>;
objects.Add("Hello there!");

You've just tried to add a string to what is actually a list of integers.

Dan Tao