15

According to this MSDN reference IEnumerable is covariant and this is possible to implicitly cast a list of objects to an enumerable:

IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;

In my own code i have written a line of code that works perfect when item type of the list is class Point (Point is a simple class with three properties double x,y,z):

var objects = (IEnumerable<object>)dataModel.Value;
// here property Value is a list that could be of any type. 

But the above code returns the following exception when item type of the list is double:

Unable to cast object of type System.Collections.Generic.List1[System.Double] 
to type System.Collections.Generic.IEnumerable1[System.Object].

What is the difference between string and double and what causes the code to work with string but not with double?

Update

According to this post we could simply cast a list to IEnumerable (without type argument) so as I only need to iterate over items and add new items to the list ( Actually I do not need to cast items of the list, at all). I decided to use this one:

var objects = (IEnumerable)dataModel.Value;

But if you need to cast items of the list to object and use them, the answer from Theodoros is the solution you most follow.

Community
  • 1
  • 1
a.toraby
  • 3,232
  • 5
  • 41
  • 73
  • 2
    Covariance only applies when you're *casting*. For value-types, a *conversion* is needed and variance has nothing to do with it. – haim770 Feb 08 '16 at 10:06
  • @haim770 I'm not sure I agree with that. There still needs to be an *implicit/identity conversion* between the types, as for the spec: *The purpose of variance annotations is to provide for more lenient (but still type safe) **conversions** to interface and delegate types. To this end the definitions of implicit (§6.1) and explicit conversions (§6.2) make use of the notion of variance-convertibility* – Yuval Itzchakov Feb 08 '16 at 10:15
  • @YuvalItzchakov, What exactly is it you don't agree with? I can't find anything in the spec you mentioned that contradicts my comment. – haim770 Feb 08 '16 at 10:20
  • @haim770 Perhaps I didn't understand your comment properly, not sure what you mean when you say "Covariance only applies when you're casting". There is no casting needed here. – Yuval Itzchakov Feb 08 '16 at 10:22
  • @YuvalItzchakov, Perhaps we first need to clarify the distinction between *casting* and *conversion*. As far as I understand, casting doesn't change (or create a new) value of the object. It only changes the way the compiler treats it. With conversion, you *do* end up with a new value (internally, that can be a result of an implicit/explicit casting but it's still a new value). – haim770 Feb 08 '16 at 10:27
  • @haim770 I see what you mean. AFAIK, there are conversions where the compiler ends up treating the object differently without producing a new value, such as *identity* and [*implicit reference*](https://msdn.microsoft.com/en-us/library/aa691284(v=vs.71).aspx) conversions, for example. Those implicit reference conversions are what apply to co/contravariance, and why value types don't actually apply to the variance game. – Yuval Itzchakov Feb 08 '16 at 10:31
  • 1
    _Very_ related thread: [Why covariance and contravariance do not support value type](http://stackoverflow.com/questions/12454794/) – Jeppe Stig Nielsen Feb 08 '16 at 11:04

1 Answers1

22

Constructed types of variant interfaces (such as IEnumerable<out T>) are variant only for reference type arguments, because their implicit conversion to a supertype (such as object) is a non-representation-changing conversion. This is why IEnumerable<string> is covariant.

Constructed types for value type arguments are invariant, because their implicit conversion to a supertype (such as object) is representation-changing, due to the boxing that's required. This is why IEnumerable<double> is invariant.

See the relevant documentation:

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.

A possible workaround is to use LINQ cast:

var sequence = list.Cast<object>();

This will first check whether the source is assignment-compatible to the IEnumerable<TResult> that you are trying to cast it to.

Community
  • 1
  • 1
Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104
  • This means that I can convert `List` to `IEnumerable` But this is not possible to convert `List` or `List` to `IEnumerable` ? If this is the case what is the alternative way? Thanks for your fast response – a.toraby Feb 08 '16 at 10:03
  • 1
    That depends what you want to do with it. You can always wrap it and expose the new enumerator. – TomTom Feb 08 '16 at 10:07
  • 1
    @a.toraby You'll need to explicitly cast those values to an `IEnumerable`: `IEnumerable objects = dataModel.Value.Cast();`. – Yuval Itzchakov Feb 08 '16 at 10:08