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>
toSystem.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
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With