7

I have the following short C# program:

IList<string> listString = new List<String>();
IList<object> listObject;

listObject = listString;

This program doesn't compile. The last line causes the following compilation error:

Cannot implicitly convert type 'System.Collections.Generic.IList' to 'System.Collections.Generic.IList'. An explicit conversion exists (are you missing a cast?)

So, I've added the cast:

listObject = (IList<object>)listString;

Now the program compiles properly, but fails at runtime. An InvalidCastException is raised with the following message:

Unable to cast object of type 'System.Collections.Generic.List'1[System.String]' to type 'System.Collections.Generic.IList'1[System.Object]'.

Either the cast is illegal and should be caught by the compiler, or it is legal and shouldn't throw an exception at runtime. Why the inconsistent behavior?

CLARIFICATION: I am not asking why the cast fails. I understand why such casting is problematic. I am asking why the cast fails only at runtime.

zmbq
  • 38,013
  • 14
  • 101
  • 171
  • 2
    this is a by-the-book covariance problem. Please see http://stackoverflow.com/questions/5832094/covariance-and-ilist – Cristian Lupascu Jul 08 '13 at 08:15
  • possible duplicate of [covariance in c#](http://stackoverflow.com/questions/4038125/covariance-in-c-sharp) – Euphoric Jul 08 '13 at 08:16
  • And to comment on your reasoning. What are you trying is logically wrong. That's why compiler doesn't like it. Trying to force it by using explicit cast is not going to help you. Using explicit cast is like saying "i don't care what type this is, just compile it". – Euphoric Jul 08 '13 at 08:21
  • I'm asking about the inconsistency in behavior - why is the explicit cast allowed if it fails? – zmbq Jul 08 '13 at 08:34
  • Because casts are only checked at runtime. They are never checked at compile time because a cast, by definition, is using information the compiler does not know about (else an implicit conversion would be allowed). – thecoop Jul 08 '13 at 08:39
  • 1
    @thecoop, that's simply not true. Try `(double)DateTime.Now`, the compiler will not allow it. – zmbq Jul 08 '13 at 08:44
  • @Euphoric, why does the explicit cast exist? It shouldn't be there, as it will always fail. Under what circumstances will it succeed? – zmbq Jul 08 '13 at 08:46
  • It will succeed when internal type of the object is assignable to converted type. Something like "Object o = new A(); A a = (A)o" will succeed. In your case, IList is not assignable to IList. – Euphoric Jul 08 '13 at 08:52
  • @Euphoric, when *is* an IList assignable to an IList? – zmbq Jul 08 '13 at 08:53
  • Never. Thats why it won't compile. Explicit casting is for cases when you are sure the type is assignable. You use "is" operator to check it. Also, check "as" operator. It will return null instead of failing. – Euphoric Jul 08 '13 at 08:55
  • @zmbq: Oops. Yeah. It's more complicated than 'never'. But what will compile is `(double)(object)DateTime.Now` – thecoop Jul 08 '13 at 08:58
  • If the compiler can determine with certainty the the cast could never succeed, it is not allowed. For example a `struct` or a `sealed class` can not be derived from, so the compiler knows exactly which interfaces it implements, and knows all its base classes. So trying to cast a struct or a sealed class to something unrelated is not allowed compile-time. On the other hand, a class which is not sealed can be explicitly cast to _any_ interface since someone might have inherited from the class and made the derived class implement the "unrelated" interface. And so on ... – Jeppe Stig Nielsen Jul 08 '13 at 09:28

5 Answers5

13

The reason why the implicit cast from IList<string> to IList<object> won't compile is, as you seem to know, that the IList<T> interface is not covariant in T. If, with .NET 4.5, you used IReadOnlyList<out T> instead, it would work.

The reason why the explicit cast

listObject = (IList<object>)listString;

will compile, is not that IList<string> and IList<object> are related in any way. Neither type is assignable to the other. The reason is that the runtime type of your variable (listString) might be a class (or struct) which implemented both interfaces! Suppose I made this class:

class CrazyList : IList<string>, IList<object>  // not very smart
{
  // a lot of explicit interface implementations here
}

Then if some IList<string> happened to be a CrazyList at runtime, the explicit cast would succeed. When you write an explicit cast, you're telling the compiler "I know the type is going to be convertible to this type I'm casting into". Since the compiler can't prove you're wrong, of course it believes you.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 2
    @zmbq And you can make an explicit cast from _any_ interface to _any_ other interface for the same reason, for example `ICloneable cloneable = XXXX; IDisposable disposable = (IDisposable)cloneable;`. – Jeppe Stig Nielsen Jul 08 '13 at 09:12
  • @zmbq, this is equally true for non generics. The idea of covariance is a red herring here. The static analysis necessary to detect developer error is expensive and usually unesscessary. – Jodrell Jul 09 '13 at 08:17
4

One way to come up with list of strings to a list of objects would be:

IList<object> objects = listOfStrings.Cast<Object>().ToList();

Please note it would be best for the maintainability of your code to limit such casts, and be very explicit about them when they need to happen, marking them with a comment if the reason behind the conversion is not immediately evident.

EDIT: An important note is that the code above does not actually cast one type of list to another. Rather, it casts individual members of the initial list to another type, then the .ToList() method creates a separate list with the converted objects.

SECOND EDIT: Actually, none of the commented questions explains the problem adequately. Please check John Skeet 's answer here. An explicit cast will not fail at compile time, since the compiler expects that it will encounter the explicit cast 's implementation at some point, while a covariant assignment using a generic IList<T> interface will fail, since it 's not supported.

Community
  • 1
  • 1
Ioannis Karadimas
  • 7,746
  • 3
  • 35
  • 45
  • 1
    Also note that adding a string to the resulting list won't add that string to the original list. – Craig Gidney Jul 08 '13 at 08:28
  • Thanks, but I'm not asking how to actually perform the cast. I want to know why the compiler allows the cast even though it's doomed to fail at runtime. – zmbq Jul 08 '13 at 08:35
  • @zmbq, because the compiler doesn't do that kind of static analysis. – Jodrell Jul 09 '13 at 08:12
2

listObject.Add(10); // ok
string s = listString[0]; // WTF?!!!

Due to IList mutability such conversion is meaningless.

Konstantin Oznobihin
  • 5,234
  • 24
  • 31
  • Which would be an excellent reason for the compiler to prevent the cast. However, the cast is allowed and fails at runtime. Why the inconsistency here? – zmbq Jul 08 '13 at 08:35
  • There is no inconsistency, a class implementing IList can also implement IList and this cast (run-time) will work for objects of such types. The incorrectnes of case I've shown is obvious to human only, compiler can't make such analysis and there could be lot of cases when interfaces can and should be convertible to other interfaces. So there is no reason to forbid such casts for all cases and there is no way to distinguish valid and invalid casts at compile time. – Konstantin Oznobihin Jul 08 '13 at 09:03
1

You can do this by Collection Initializer. The below code snippets works for me.

IList<string> listString = new List<String>();
IList<object> listObject;

listObject = new List<object>(listString);
Vimal CK
  • 3,543
  • 1
  • 26
  • 47
  • Yes! Not called a collection initializer, though; this is using the constructor overload of `List` which takes in an `IEnumerable` as its argument. An `IList` is also an `IEnumerable` by inheritance. But then an `IEnumerable` is also an `IEnumarable` because the type `IEnumerable` is covariant in `T` (and because `string` is a reference type inheriting from `object`). So this shows why this works, and why covariance in generic types can be useful. This has been possible since C# 4.0 and .NET Framework 4.0 from 2010. – Jeppe Stig Nielsen Jun 04 '22 at 08:46
1

If you can't access the underlying List,

IList<object> listObject = listString.Cast<object>().ToList();

If you can access the underlying List,

Ilist<object> listobject = (new List<string>()).ConvertAll(s => (object)s);
Jodrell
  • 34,946
  • 5
  • 87
  • 124