Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do covariant implicit casts ignore generic constraints?

Tags:

c#

.net

static IEnumerable<U> DoSomething<T, U>(IEnumerable<T> a)
    where T : U
{
    // Works, compiler can compile-time statically cast
    // T as U.
    T testA = default(T);
    U testB = testA;

    // And the following works, though:
    IEnumerable<string> test2A = null;
    IEnumerable<object> test2B = test2A;

    // Doesn’t work. Compiler cannot compile-time statically
    // cast IEnumerable<T> to IEnumerable<U> even though it is
    // out and T is U.
    return a;
}

I have code where being able to perform this type of implicit cast would save me writing a lot of boilerplate interface implementation code. This seems to be the sort of thing which covariance was supposed to help with. But I always get this error on the return a; line above:

error CS0266: Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<T>' to 'System.Collections.Generic.IEnumerable<U>'. An explicit conversion exists (are you missing a cast?)

Why is this this way and is there a way to work around this without doing something like return from o in a select o;?

like image 253
binki Avatar asked Aug 22 '16 21:08

binki


1 Answers

When messing around with my minimal repro and reading similar, but unrelated, question about interface casting, I realized that the following compiles:

static IEnumerable<U> DoSomethingElse<T, U>(IEnumerable<T> a)
    where T : class, U
{
    // Works! Ridik!
    return a;
}

And also that the following fails with the same error message:

static void Blah()
{
    // Fails for I bet the same reason that DoSomething{T, U} fails:
    IEnumerable<int> a = null;
    IEnumerable<object> b = a;
}

error CS0266: Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<int>' to 'System.Collections.Generic.IEnumerable<object>'. An explicit conversion exists (are you missing a cast?)

So this seems to be related to how .net restricts certain types of assignments to reference types because boxing in these situations would either be the wrong thing (e.g., you might assume reference types and actually be working on a copy of a value type) or very hard/impossible to implement in the runtime (given an IEnumerable<int> you’d have to implement a wrapping adapting class. OK, so that sounds like something .net can’t/shouldn’t try to do for you at runtime). I think of it as a situation where .net allows pointer-style polymorphism which, by its very nature, is incompatible with the concept of value types.

So, for my case, I don’t need to support value types in my API here and adding the class constraint makes everything magical!

like image 85
binki Avatar answered Nov 15 '22 09:11

binki