Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does ToList<Interface> not work for value types?

If I implement an interface for a value type and try to cast it to a List of it's interface type, why does this result in an error whereas the reference type converts just fine?

This is the error:

Cannot convert instance argument type System.Collections.Generic.List<MyValueType> to System.Collections.Generic.IEnumerable<MyInterfaceType>

I have to explicitely use the Cast<T> method to convert it, why? Since IEnumerable is a readonly enumeration through a collection, it doesn't make any sense to me that it cannot be cast directly.

Here's example code to demonstrate the issue:

    public interface I{}

    public class T : I{}

    public struct V: I{}

    public void test()
    {
        var listT = new List<T>();
        var listV = new List<V>();

        var listIT = listT.ToList<I>();     //OK
        var listIV = listV.ToList<I>();     //FAILS to compile, why?

        var listIV2 = listV.Cast<I>().ToList(); //OK

    }
like image 805
Marwie Avatar asked Feb 13 '15 18:02

Marwie


1 Answers

Variance (covariance or contravariance) doesn't work for value types, only reference types:

Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type. (MSDN)

The values contained inside reference type variables are references (for example, addresses) and data addresses have the same size and are interpreted the same way, without any required change in their bit patterns.

In contrast, the values contained inside value type variables do not have the same size or the same semantics. Using them as reference types requires boxing and boxing requires type-specific instructions to be emitted by the compiler. It's not practical or efficient (sometimes maybe not even possible) for the compiler to emit boxing instructions for any possible kind of value type, therefore variance is disallowed altogether.

Basically, variance is practical thanks to the extra layer of indirection (the reference) from the variable to the actual data. Because value types lack that layer of indirection, they lack variance capabilities.


Combine the above with how LINQ operations work:

A Cast operation upcasts/boxes all elements (by accessing them through the non-generic IEnumerable, as you pointed out) and then verifies that all elements in a sequence can be successfully cast/unboxed to the provided type and then does exactly that. The ToList operation enumerates the sequence and returns a list from that enumeration.

Each one has its own job. If (say) ToList did the job of both, it would have the performance overhead of both, which is undesirable for most other cases.

like image 199
Theodoros Chatzigiannakis Avatar answered Oct 03 '22 18:10

Theodoros Chatzigiannakis