Ayende has posted up a question about why the following code won’t work:

  1 public IList<object> GetList()
  2 {
  3    return new List<string>();
  4 }

The issue here is that List<string> does not implement IList<object>, instead it implements IList<string>. We can tell this by looking at the class definition:

  1 public class List<T> :
  2       IList<T>,
  3       ICollection<T>,
  4       IEnumerable<T>,
  5       IList,
  6       ICollection,
  7       IEnumerable
  8 {
  9       ...
 10 }

The issue becomes apparent when you do the substitution yourself and compare what the generated type would look like in each case:

  1 List<string> stringList = new List<string>();
  2 public class List<string> :
  3       IList<string>,
  4       ICollecftion<string>,
  5       IEnumerable<string>,
  6       IList,
  7       ICollection,
  8       IEnumerable
  9 {
 10       ...
 11 }
 12 
 13 List<object> objectList = new List<object>();
 14 public class List<object> :
 15       IList<object>,
 16       ICollecftion<object>,
 17       IEnumerable<object>,
 18       IList,
 19       ICollection,
 20       IEnumerable
 21 {
 22       ...
 23 }

You can clearly see here that one list implements IList<string> whereas the other implements IList<object>. The interesting thing here is that because both System.String and System.Object are reference types – they both end up sharing the same constructed type at the runtime level, but the runtime enforces some rules – for our own safety.

If you take a look at the inheritence hierarchy for IList<T> then you can see that the first time you have a common concrete class is IEnumerable – so in Ayende’s case he would need to return an IEnumerable for the code to compile.

ListHierarchy

The thing about IEnumerable is that using that interface, you can’t add any items to the list. Thats just as well, because if you were able to get an IList<object> back and add an item to it, what would happen? Well, because the constructed type is List<string> it means that the code is actually incapable of accepting an object – so the interface that List<object> would expose would essentially be a lie.

So this is a really good example of where a static type system can save you from yourself, and generics are really there to enable your code to be flexible whilst at the same time be statically verifyable. This behaviour is by design.