6

The following is a simplified version of the problem I'm having:

var list = new List<int> {1, 2, 3, 4, 5};
// list Count = 5 System.Collections.Generic.List<int>

var obj = list as object;
// obj  Count = 5   object {System.Collections.Generic.List<int>}

var enumerable = obj as IEnumerable<object>;
// enumerable   null    System.Collections.Generic.IEnumerable<object>

The comments are the values from the watch window in Visual Studio 2017

However if I enter:

obj as IEnumerable<object>

in the watch window, I get:

obj as IEnumerable<object>  Count = 5   System.Collections.Generic.IEnumerable<object> {System.Collections.Generic.List<int>}

So how come I can cast it in the watch window and it works, but if I do it in the code it evaluates to null?

Steve Wright
  • 534
  • 1
  • 4
  • 14
  • 1
    The watch window seems to be buggy, since interface covariance doesn't cover boxing of value types. – CodesInChaos Mar 19 '18 at 16:58
  • @CodesInChaos: It's not just the debugger. The language doesn't support it. – Joey Mar 19 '18 at 17:02
  • Seems to me that you're doing something fundamentally wrong. You can go from a List to an IEnumerable directly. You're doing boxing / unboxing and that practice has been abandoned years ago. With good reason. – Toni Kostelac Mar 19 '18 at 17:02
  • Indeed, it is important to never forget that value types are boxed, **not** cast to object. It makes all the difference in all the wrong places. Plus you should not need to do either anyway. Preventing that is what generics are there for. – Christopher Mar 19 '18 at 17:03
  • @ToniKostelac there is no boxing/unboxing in code sample... What exactly *you* mean when saying "boxing"? – Alexei Levenkov Mar 19 '18 at 17:03
  • @ToniKostelac "that practice [boxing] has been abandoned years ago" - Source? Boxing often occurs with enums for example. – Ryan Searle Mar 19 '18 at 17:06
  • @AlexeiLevenkov The conversion from `int` to `object` is boxing and thus not representation preserving. Thus `IEnumerator.Current` and `IEnumerator.Current` can't share an implementation, which is why the CLR specification doesn't allow such conversions in interface covariance. – CodesInChaos Mar 19 '18 at 17:07
  • @CodesInChaos yes, but where it is in the sample code? Casting list of integers to `IEnumerable` simply fails without giving any chance for boxing to happen... – Alexei Levenkov Mar 19 '18 at 17:10
  • @RyanSearle we don't do it explicitly, it's handled by the framework for us. If you are doing it explicitly chances are you're doing something wrong. – Toni Kostelac Mar 19 '18 at 17:10
  • @AlexeiLevenkov This cast is forbidden because it'd require boxing. – CodesInChaos Mar 19 '18 at 17:12
  • This look so wrong. I think my eye are bleeding :) – Franck Mar 19 '18 at 17:20
  • @CodesInChaos The only thing I wanted to confirm from Toni Kostelac is whether they use that same meaning of "boxing" as in the C# spec. I've seen it used as "any cast to `object` is boxing". We can argue if actual boxing "happens in the post" because OP wanted it for some reason in they cast to `IEnumerable` or "does not happen in the post" because this cast is forbidden :) – Alexei Levenkov Mar 19 '18 at 17:20
  • @AlexeiLevenkov I use both definitions interchangeably. – Toni Kostelac Mar 19 '18 at 18:29
  • @ToniKostelac please **do not** use "boxing" as synonym of "cast to object" when you write/speak with others about C#. The only valid usage of word "boxing" in relation to C# is casting value type (like `int`) to `object`. – Alexei Levenkov Mar 19 '18 at 18:55

1 Answers1

9

From the C# documentation:

Variance in generic interfaces is supported for reference types only. Value types do not support variance. For example, IEnumerable<int> cannot be implicitly converted to IEnumerable<object>, because integers are represented by a value type.

What helps to know here is that the following two things are very different operations under the hood:

var l = (object) new List<int>();
var i = (object) 42;

despite the syntax looking the same. The first line merely treats the list as another type (with some checks whether the cast actually can work), while the second converts the integer into an object containing it (boxing). There's a third variant, which calls a conversion operator, and still looks the same. Since the first variant can get away with changing nothing about the object and just interpreting it differently it's vastly cheaper than the second. And I'd guess in this context here the only option that can work for interface covariance.

Joey
  • 344,408
  • 85
  • 689
  • 683