-1

A boxed T can only be unboxed to T. For example, this is not working:

object o1 = 5;
double n3 = (double)o1;

Using the Linq's Cast() method on a List<T> throws a similar exception:

List<int> _numbers = new List<int>() { 1, 2, 3, 4 };
// this is also not working
IEnumerable<double> nums = _numbers.Cast<double>();
  1. Does that mean that each element of a List<T> is boxed ?
  2. Is it possible to cast each element of the List<> without creating a copy of the list ?
    (Please do not post any answer with the Linq's Select() method, since it is creating a copy of the collection)
xanatos
  • 109,618
  • 12
  • 197
  • 280
A.B.
  • 2,374
  • 3
  • 24
  • 40
  • 1
    A string will never be boxed, as it already **is** an object. Boxing only applies to valuetypes. And yes, boxing requires to create a new (boxed) object, so no, you cannot "cast" your list of numbers to another number-type – MakePeaceGreatAgain Feb 05 '21 at 11:25
  • But `_numbers.Select(n => (double)n))` works. Of course value types are not boxed when storing in a `List`. – Klaus Gütter Feb 05 '21 at 11:28
  • 8
    There is no boxing going on in the `List`. Generics have two separate runtime implementations; one for references, one for values. `Enumerable.Cast` is specifically intended to cast items from a non-generic `IEnumerable`, however, so when you do this you *do* get boxing as the items are first boxed, then attempted to be unboxed as `double` -- but this is specific to `Enumerable.Cast`. And no -- `.Select()` does *not* create a copy of the collection, it creates an iteration -- just as `.Cast` does, so if you don't like one you shouldn't like the other either. – Jeroen Mostert Feb 05 '21 at 11:30
  • For first example you can use [Convert.ToDouble()](https://dotnetfiddle.net/bvtu7B), similarly for second `_numbers.Select()` with converting should also do. `List` won't become `List` without something. – Sinatr Feb 05 '21 at 11:34
  • 4
    To add to @JeroenMostert's comment, `Cast` is an extension method on `IEnumerable`, and `IEnumerable.Current` is an `object`, which means that every `int` will be boxed as the `Cast` method retrieves it from the `IEnumerable`. If you want to create a list of doubles from your list of ints you will need to create new list. If you just want an `IEnumerable`, then `Select` is the correct tool for the job, and does *not* copy the whole list – canton7 Feb 05 '21 at 11:35
  • 1
    @JeroenMostert If I remember it is false... There is a single implementation for all the reference types plus a specialized implementation for each type of value type (see [here](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/generics-in-the-run-time)) – xanatos Feb 05 '21 at 11:37
  • 1
    @xanatos: yes, couldn't edit my comment in time unfortunately. I should have said there are two *kinds* of runtime implementations for generics, not *exactly* two. – Jeroen Mostert Feb 05 '21 at 11:38
  • 1
    _"A boxed T can only be unboxed to T"_ - technically this is not true. Try this: `int i = 42; object o = i; long l = (long)(DateTimeKind)o;` – vasily.sib Feb 05 '21 at 11:38
  • @vasily.sib That can be simplified to `object o = 42; var d = (DateTimeKind)o;`, so you can unbox to an enum that uses the same basetype. – xanatos Feb 05 '21 at 11:41

1 Answers1

5

We will speak here only of value types. Reference types follow different rules.

  1. Does that mean that each element of a List<T> is boxed ?

No. Java does it, erases all the types T at runtime and redirect all the types to a single List<Object> implementation (it is called type erasure). In .NET, for value types, every List<ValueType1>, List<ValueType2>, List<ValueType3> receives a distinct implementation at runtime (see here).

  1. Is it possible to cast each element of the List<> without creating a copy of the list ?

When you cast a value type to something to else you are making a copy of it. Often on the stack, but the copy exists. You can't simply access a value type as a different value type.

For example:

public static void M1(long n) {
    M2((int)n);
}

public static void M2(int n) {
}

is translated in IL asm to

IL_0000: ldarg.0
IL_0001: conv.i4
IL_0002: call void C::M2(int32)
IL_0007: ret

Where conv.i4:

If the conversion is successful, the resulting value is pushed onto the stack.

Note that even getting out an element of List<int> causes a copy to be made: you aren't accessing the element INSIDE the List<int>:

public void M1(List<int> a) {
    int v = a[0];
    v = 6; // this won't modify a[0]
}

With arrays (and with newer C#) you can access directly the element of an array:

public void M1(int[] a) {
    ref int r = ref a[0];
    r = 6; // this will modify a[0]
}

The difference here is that the indexer of List<T> (the things that is called when you make a var foo = list[5] is a method, while the indexer of an array is an IL instruction (array are defined at the lowest level in the .NET virtual machine, and the have special OPs to handle them, List<T> are built "on top" of arrays). Even this can be easily seen on SharpLab.

xanatos
  • 109,618
  • 12
  • 197
  • 280