views:

511

answers:

4

While I can upcast a string to an object, I cannot upcast an IList of strings to an IList of objects. How come? What to do now other that coping all items to a new IList?

static void ThisWorks()
{
     IList<object> list = new List<object>();
     list.Add("I can add a string since string : object");
}

static void ThisDoesNotWork()
{
     // throws an invalid cast exception
     IList<object> list = (IList<object>) new List<string>(); 

     list.Add("I'm never getting here ... why?");
}
+5  A: 

This is not possible as generics are invariant (as of C# 3.0).

You can workaround it with:

var objectList = list.Cast<object>().ToList();
Mehrdad Afshari
I think you mean to say that generics *are* invariant. They're not *covariant*. They will be in C# 4, though.
John Feminella
Yeah, I just noticed and I've already fixed it :)
Mehrdad Afshari
@John: Even in C# 4 only certain things will be covariant. In particular, classes can't be variant - only delegates and interfaces.
Jon Skeet
Jon: It was my fault. I incorrectly wrote "are not invariant" and John was pointing that mistake in the sentence :)
Mehrdad Afshari
@Jon: You're right. But in this case you could treat the List<T> like an IList<in T> and get the same effect. The general operation he wants (add a more derived entry to a less derived list) would now be legal.
John Feminella
+1  A: 

string inherits from object but IList<string> does not inherit from IList<object> they are unrelated types and therefor you can't cast between them.

Just think what would happen if this worked:

// THIS CODE DOES NOT WORK
IList<object> list = new List<string>(); // this doesn't compile
list.Add(5); // because this is perfectly valid on IList<object> but not on IList<string>
Nir
+2  A: 

Look at it like this: while a banana is a fruit, a basket of bananas is not a basket of fruit, since you can add oranges to the latter, but not the former. Your List<string> has stronger constraints than a List<object>.

Casting should always respect Liskow. For containers and iterators which do not admit modification, such casting is safe, but once things can be changed, you are skating close to the thin ice.

Pontus Gagge
This does respect LSP: he's treating a *more* derived type (string) as a *less* derived type (object). In general, this is supposed to be safe.
John Feminella
Nope. He's trying to make a fruit basket out of a banana basket. As long as he's only doing reads it's safe, but things get strange if non-strings are added.
Pontus Gagge
In fact, this could be possible in some cases -like C# 4.0 that will support **safe** generic co/contravariance-, so your example is not entirely true.
Mehrdad Afshari
And also, arrays has supported variance since the first release of Microsoft .NET Framework. You can cast Orange[] to Fruit[].
Mehrdad Afshari
@Pontus: That's the point. The act of treating a basket of Bananas as a basket of Fruit is just fine from a type theory perspective. It shouldn't cause an exception, but it does because generics are invariant.
John Feminella
@Mehrdad: Array covariance isn't safe, actually for the reason that Pontus points out. You can add an Apple to a Fruit[] at compile time, but that's not safely covariant.
John Feminella
It's not safe, but nevertheless it is. I have not fundamental problem with the answer. It's correct. I just wanted to point out that the example can be misleading in some cases.
Mehrdad Afshari
@everyone: Thanks for your help. I actually asked two questions -- sorry --, still I have to choose a single answer. I feel that reading this answer WITH all the comments will make a pretty good answer for anyone finding the question in google. Thanks!
Felix Alcala
+3  A: 

What you asked is essentially a question of Contravariance and Covariance. It is a concept in programming language design which talks about how methods and collections behave with respect to objects of two classes in the same inheritance hierarchy. Reading the Wikipedia article may help place your above curiosity in a larger, more general perspective.

Frederick