views:

719

answers:

4

I have a routine

public void SomeRoutine(List<IFormattable> list) { ... }

I then try to call this routine

List<Guid>list = new List<Guid>();
list.Add(Guid.NewGuid());
SomeRoutine(list);

And it fails with a compile-time error. System.Guid implements IFormattable, but the error I get is

cannot convert from 'System.Collections.Generic.List' to 'System.Collections.Generic.List'

NOTE: You will get the same error if you just use an array of Guids. Generics is NOT the cause....

But! Given this

public void SomeRoutine2(IFormattable obj) { ... }

and this

Guid a = Guid.NewGuid();
SomeRoutine2(a);

It compiles! So the question is WHY? Why am I able to pass a Guid object (which implements IFormattable) into a routine that accepts an object of IFormattable, but when I try to expand that to a collection (a generic list, or an array, or anything else), I get a conversion error?

I have had a heck of a time finding an answer, and I figured this would be the best place to go.

+8  A: 

This is that whole covariance thing everyone talks about. It will work in .NET 4.0.

See here: http://blogs.msdn.com/charlie/archive/2008/10/28/linq-farm-covariance-and-contravariance-in-visual-studio-2010.aspx

Some more reading:

http://www.infoq.com/news/2008/08/GenericVariance;jsessionid=188695B18864997E8D360E0EEED5983E

http://blogs.msdn.com/lucian/archive/2008/10/02/co-and-contra-variance-how-do-i-convert-a-list-of-apple-into-a-list-of-fruit.aspx

BFree
C# 4.0 covariance doesn't apply to concrete classes (only to interfaces), and doesn't apply to lists (only IEnumerable<T> - or things that are pure "out")
Marc Gravell
What do you mean exactly when you say "pure out"?
BFree
I mean, the `IEnumerable<T>` and `IEnumerator<T>` only have "out" usage - i.e. "T Current {get;}". It is marking things as "in" or "out" that enables co/contra-variance; but it only works with one **or** the other. You can't have "in" and "out" usage (which lists would require).
Marc Gravell
correction to first note; interfaces + delegates; but generics is still a better answer here; re lists and "in"+"out"; Add(T) would be "in", the enumerator would be "out", and the indexer is both; hence lists don't work with c# 4.0 covariance.
Marc Gravell
Ok, that actually makes sense. Thanks :)
BFree
See also: http://marcgravell.blogspot.com/2009/02/what-c-40-covariance-doesn-do.html
Marc Gravell
+2  A: 

Have you tried declaring list as a List<IFormattable> object instead of a List<Guid>? That should get you past the compile error.

Mike
Generics are a far nicer option...
Marc Gravell
+3  A: 

The problem is that a List<IFormattable> is not only a collection from which you can read some IFormattables, it's also a collection to which you can add IFormattables. A List<Guid> meets the first requirement, but not the second. What if SomeRoutine was

public void SomeRoutine(List<IFormattable> list)
{
    list.Add(5);
}

That Int32 is an IFormattable, so the method should be able to add it to the List<IFormattable> that it asked for. That's not going to work if the compiler lets you pass in your List<Guid>.

The new C# 4.0 features that BFree refers to will let you tell the compiler when these things are safe. You can say either

  • Although I asked for an IFormattable reference, I'll only be reading it. If it's really a Guid reference then I can safely treat it as a IFormattable, and I won't try to assign an Int32 to it.
  • Although I asked for an IFormattable reference, I'll only be writing to it. If it's really an Object reference then I can safely assign my IFormattable to it, and it doesn't matter if the current value isn't an IFormattable because I won't read it.
stevemegson
C# 4.0 covariance doesn't apply to concrete classes (only to interfaces), and doesn't apply to lists (only IEnumerable<T> - or things that are pure "out"
Marc Gravell
correction; interfaces + delegates; but generics is still a better answer here...
Marc Gravell
Good point. Of course a List<T> can't make either of the 'in' or 'out' assertions that C# 4.0 will allow because you still need to be able to both insert and retrieve T's, so you're no better off. +1 for your generics answer.
stevemegson
+3  A: 

This is a classic generics use-case; try:

public static void SomeRoutine<T>(IList<T> list) where T : IFormattable
{ ... }

Now inside SomeRoutine you have access to all the IFormattable members, but it will work with:

List<Guid>list; ...
SomeRoutine(list); // note no need to specify T

Edit: I've additionally blogged on the differences between 4.0 covariance and generics for this scenario.

Marc Gravell