9

I was reading C# in Depth (3rd edition) and in chapter 13, in a section which talks about inclusion of covariant and contravariant type parameters in c# 4, this claim is made:

The parameter for List.AddRange is of type IEnumerable<T>, so in this case you’re treating each list as an IEnumerable <IShape>—something that wouldn’t have been possible before. AddRange could have been written as a generic method with its own type parameter, but it wasn’t—doing this would’ve made some optimizations hard or impossible.

Could anyone provide some justification for this claim? It is not obvious why it is true for me.

DavidG
  • 113,891
  • 12
  • 217
  • 223
user1242967
  • 1,220
  • 3
  • 18
  • 30
  • 4
    Taking this snippet out of context makes it hard to understand what it says. Especially since `AddRange` has a generic parameter. What it says though, is that making `AddRange` accept a *different* type, eg Y, that can be cast to T, would make optimizations hard. – Panagiotis Kanavos Aug 08 '18 at 10:42
  • Maybe I misunderstand the question, but why anyone would create a generic version for `AddRange`where you can use `IEnumerable`? – Rickless Aug 08 '18 at 10:43
  • 1
    @Mahmoud to allow types that implement or inherit from T – Panagiotis Kanavos Aug 08 '18 at 10:43
  • 2
    I don´t understand your question neither. As `List` allready *is* generic with `T` as the generic argument, also `AddRange` is with the *exact same argument*. Thus cou can´t add instances of another type to the list. So what´s your question? – MakePeaceGreatAgain Aug 08 '18 at 10:45
  • 2
    Anyway: why is this question so heavily up-voted although no-one seems to understand it? – MakePeaceGreatAgain Aug 08 '18 at 10:46
  • @HimBromBeere i think that's the reason, must be secret Jon Skeet knowledge – TheGeneral Aug 08 '18 at 10:46
  • 5
    My guess is Jon was implying a signature something like `void AddRange(IEnumerable collection) where TDerived : T` but that means you need to need to iterate the new items instead of possibly being able to use `Array.Copy` as [it does now](https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,701)? – DavidG Aug 08 '18 at 10:51
  • Let's say we have `List` and this list contains method: `AddRange` - compiler must check whether a T2 can be upcasted to T before adding it to internal array of values. Even though this can be done using type constraints but it'll add overhead. Apart from that there probably is some optimization on a lower lever that efficiently upcasts the entire collection to a collection of base type – Fabjan Aug 08 '18 at 11:00
  • @DavidG, yeah I think I see the problem. You would still be able to do `var c = collection as ICollection` but you would not be able `c.CopyTo(itemsToInsert, 0)`. But on the other hand, if you would do `var c = collection as TDerived[]` it should work but that would be more restricting I guess – user1242967 Aug 08 '18 at 11:06
  • In other words: "doing this would’ve made some optimizations hard or impossible" means basically "doing this will lead to a hell of a mess in code" (that's a Jon to Eng translation) – Fabjan Aug 08 '18 at 11:08
  • Pinging @JonSkeet, guessing that he can easily *definitively* answer this question :) – Lasse V. Karlsen Aug 08 '18 at 11:16
  • @Lasse Unfortunately pinging won't work or I'd have done that already :) – DavidG Aug 08 '18 at 11:18
  • Have now commented on MineR's answer, which is basically what I'd have written as an answer. – Jon Skeet Aug 11 '18 at 16:55
  • @JonSkeet, cool, thanks for checking this question out. – user1242967 Aug 11 '18 at 18:07

2 Answers2

1

I think reason for that is, that IEnumerable<T> may only contain objects that are of type/implement interface T, and this condition would need to be checked on every element in AddRange<T2>

Lets try some code that would crash with generic AddRange

public class MyClass1 : IFoo, IBar
{
  /* some code */
}

public class MyClass2 : IFoo
{
  /* some code */
}

var fooList = new List<IFoo>
{
  new MyClass1(),
  new MyClass2()
}
var barList = new List<IBar>();
barList.AddRange<IFoo>(fooList);

Now, question is, how should it react? You can add object of MyClass1 to barList, because it implements IBar, but what happens when you try to add MyClass2? We would need to check each element in list before adding it to prevent throwing exceptions.

stasiaks
  • 1,268
  • 2
  • 14
  • 31
  • 1
    But adding a type constraint on this would fix it. – DavidG Aug 08 '18 at 10:55
  • But once you add type constraint, there is no actual point in having this at all, because if you make `AddRange where T2:T`, you can just have `AddRange`, it's same thing. You could have `AddRange where T2:IConvertible` and then call `ToType(T)` inside. Then, is there a point? Clearly any need of such generic `AddRange` is bad code design in first place. – stasiaks Aug 08 '18 at 11:06
1

I would guess it was not written as void AddRange<T>(IEnumerable<T> items) is because of optimisations it does when the IEnumerable<T> is an ICollection<T>. When the IEnumerable<T> is an ICollection<T>, AddRange internally calls ICollection<T>.CopyTo, the first parameter of which is T[]. (Note that the underlying storage mechanism for a List<T> is a T[]).

An array of a base type is not the same as an array of a derived type, so you cannot do this for example:

object[] objs = new object[4];
var collection = (new string[4]) as ICollection<string>;
collection.CopyTo(objs,0); //Cannot convert object[] to string[]

This is the optimisation that would be "impossible".

You can view the source here: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,79de3e39e69a4811

It seems like AddRange should check for T[] and List<T>, and do an Array.Copy in those cases, but -100, I guess. You might be somewhat surprised by what Array.ToArray() doesn't do too.

MineR
  • 2,144
  • 12
  • 18
  • 1
    Yes, I think this is basically what I was getting at. It's entirely possible that it was an incorrect assertion, but I *think* that was my line of reasoning at the time. – Jon Skeet Aug 11 '18 at 16:55