1

I'm pretty sure I'm missing the obvious answer, but why this:

public static void Foo(IEnumerable<string> strings) { }

public static void Bar<T>(IEnumerable<T> ts)
{
    Foo((IEnumerable<string>)ts);
}

is allowed, while this:

public static void Foo(List<string> strings) { }

public static void Bar<T>(List<T> ts)
{
    Foo((List<string>)ts);
}

fails with CS0030 Cannot convert type 'System.Collections.Generic<T>' to type 'System.Collections.Generic<string>'.? Both have the potential to fail at runtime and both have the potential to succeed at runtime. What language rule governs this?

V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • 1
    Does this answer your question? [Why can't I cast from a List to List?](https://stackoverflow.com/questions/5881677/why-cant-i-cast-from-a-listmyclass-to-listobject) espiecially the statement saying: "*It is safe because `IEnumerable` does not expose any method that takes in a T.*" – Bagus Tesa Nov 18 '19 at 01:15
  • @BagusTesa Specifically T is defined as out only for IEnumerable which enforces your statement that IEnumerable does not not take in anything of type T – Darryl Braaten Nov 18 '19 at 01:21
  • No it doesn't, and the "duplicate" questions don't seem to either. I know what generic variance is, but allowing a cast from `IEnumerable` to `IEnumerable` _for arbitrary `T`_ is unsafe either way. `IEnumerable` would have to be both co- and contravariant for this to never fail at runtime. – V0ldek Nov 18 '19 at 09:21

1 Answers1

0

I think that this is an edge case example of the following question:
Cast List<T> to List<Interface>

I agree that in this case it seems weird because it's not possible to be casting up from something that extends string (the string class is sealed), but I'm guessing that the rule is just that you can't cast List<T> to List<SomethingElse> because the method might be called in a scenario where T is a class that extends the class SomethingElse or implements an interface called SomethingElse.

e.g.:

public static void Foo(List<string> strings) { }

public static void Bar<T>(List<T> ts)
{
    Foo((List<MyInterface>)ts);
}

public static void OtherMethod1()
{
    Bar<MyClass>(new List<MyClass>)
}

public static void OtherMethod2()
{
    Bar<MyInterface>(new List<MyInterface>)
}

public classs MyClass : MyInterface
{
}

Therefore, because it is possible to write a method like OtherMethod1 that fails at runtime, the compiler doesn't allow this whole scenario.
Although, again, sealed classes appears to be a weird edge case that could possibly be allowed if Microsoft wanted to.

Ross Gurbutt
  • 969
  • 1
  • 8
  • 15
  • But calling the `IEnumerable` version with `T = object` will also fail at runtime, yet it is allowed. Why? – V0ldek Nov 18 '19 at 09:16
  • Casts are allowed to fail at run-time. What the compiler doesn't allow is a cast that will work, but then fail when trying to call valid methods on the casted type. There is no method on IEnumerable that would break if you cast from T to object and the cast was successful. There is for list. See the linked examples / duplicate. – Ross Gurbutt Nov 18 '19 at 09:38