100

There is no AddRange() method for IList<T>.

How can I add a list of items to an IList<T> without iterating through the items and using the Add() method?

Pang
  • 9,564
  • 146
  • 81
  • 122
mohsen dorparasti
  • 8,107
  • 7
  • 41
  • 61

6 Answers6

98

If you look at the C# source code for List<T>, I think List<T>.AddRange() has optimizations that a simple loop doesn't address. So, an extension method should simply check to see if the IList<T> is a List<T>, and if so use its native AddRange().

Poking around the source code, you see the .NET folks do similar things in their own LINQ extensions for things like .ToList() (if it is a list, cast it... otherwise create it).

public static class IListExtension
{
    public static void AddRange<T>(this IList<T> list, IEnumerable<T> items)
    {
        if (list == null) throw new ArgumentNullException(nameof(list));
        if (items == null) throw new ArgumentNullException(nameof(items));

        if (list is List<T> asList)
        {
            asList.AddRange(items);
        }
        else
        {
            foreach (var item in items)
            {
                list.Add(item);
            }
        }
    }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
BlackjacketMack
  • 5,472
  • 28
  • 32
  • 6
    From the optimization point of view, you are actually casting `list` to `List` two times here. One of which could be optimized away with the `as` keyword. – bashis Jun 28 '16 at 18:04
  • Good call @bashis. I always go back and forth on the cost of a double cast vs. GC cleanup of our new var. But we could indeed do var listCasted = list as List; if(listCasted != null)... Maybe c# 6 declaration expressions will change this pattern: if(myVar.As(out myVarCasted)) myVarCasted...) – BlackjacketMack Jun 29 '16 at 14:29
  • Can you update the code with the optimization mentioned? I am not as fluent in the newest features yet. @BlackjacketMack –  Apr 04 '17 at 13:46
  • 2
    @zuckerthoben - I just ran a test iterating one million iterations using both approaches and there was no difference in performance. So I wouldn't call it an optimization...additionally, it adds a line of code (but reduces a parens casting). Anyhow, I probably would use 'as' these days: var listCasted = list as List; if(listCasted != null){listCasted.AddRange(items);}. Not worth updating the answer IMHO, but good to introduce as a syntactical alternative. – BlackjacketMack Apr 04 '17 at 14:15
  • 3
    As of now you can do `if (list is List castedList) { castedList.AddRange(items); }` – André Mantas Jun 18 '19 at 10:43
74

AddRange is defined on List<T>, not the interface.

You can declare the variable as List<T> instead of IList<T> or cast it to List<T> in order to gain access to AddRange.

((List<myType>)myIList).AddRange(anotherList);

This is not good practice (see comments below), as an IList<T> might not be a List<T>, but some other type that implemented the interface and may very well not have an AddRange method - in such a case, you will only find out when your code throws an exception at runtime.

So, unless you know for certain that the type is indeed a List<T>, you shouldn't try to use AddRange.

One way to do so is by testing the type with the is or as operators (since C# 7).

if(myIList is List<T>)
{
   // can cast and AddRange
}
else
{
   // iterate with Add
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Oded
  • 489,969
  • 99
  • 883
  • 1,009
  • Does this really accomplish anything? Doesn't `AddRange` just loop anyway? – Jon B Oct 31 '12 at 12:42
  • as it's a property of a model (being used with EF ) which way is preferred ? – mohsen dorparasti Oct 31 '12 at 12:42
  • @JonB - Sure, as do all the LINQ options (`Concat`, for example). This is just a succinct way of doing the same thing. – Oded Oct 31 '12 at 12:43
  • 2
    @mohsen.d - If the type is generated, you don't want to change the generated code (as it is liable to be overwritten). Either cast or use LINQ `Concat`, as @Self_Taught_Programmer [answered](http://stackoverflow.com/a/13158155/1583). – Oded Oct 31 '12 at 12:44
  • @Oded : no I'm using code first . and that solution doesn't look good . – mohsen dorparasti Oct 31 '12 at 12:45
  • 2
    @mohsen.d - If it is your code, might as well declare the type as `List` (or, if this is not a good choice for you, do the cast where you need to `AddRange` to keep it localized - it is a very low cost operation). – Oded Oct 31 '12 at 12:46
  • 58
    No, no, no. The reason this is an IList to begin with is because it may be something else than a List implementation. Write an extension method as shown by BlackjacketMack if you really need an AddRange method. – Derek Greer Nov 12 '15 at 21:55
  • 6
    No idea why this received so many upvotes as it will clearly throw something like `InvalidCastException` if used on anything other than `List` (array, for instance). – bashis Jun 28 '16 at 18:43
  • 3
    But, casting is not a good idea. It can lead to performance overhead. – Gul Ershad Mar 30 '17 at 05:24
  • Casting is not a good idea from the Interface point of view either. If the object being cast is a Property or return value declared as IList, there is no guarantee the object can be cast to List. – Pramod B R Jun 25 '20 at 05:45
  • 1
    Do not cast from IList to List because IList is not List. – markb Jul 27 '20 at 13:57
27

You could do something like this:

IList<string> oIList1 = new List<string>{"1","2","3"};
IList<string> oIList2 = new List<string>{"4","5","6"};
IList<string> oIList3 = oIList1.Concat(oIList2).ToList();

So, basically you would use the Concat() extension and ToList() to get a similar functionality as AddRange().

Source

Jeankowkow
  • 814
  • 13
  • 33
Rayshawn
  • 2,603
  • 3
  • 27
  • 46
  • 2
    The problem with your approach is that `Enumerable.Concat` is implemented by `System.Linq.Enumerable` and that method's return value is `IEnumerable`, so I believe it shouldn't be cast back to `IList` - it might return something else due to implementation details that we don't know without checking the source code - even though, there's no guarantee it won't change - so special attention must be taken when supporting multiple .NET versions. – jweyrich Feb 11 '15 at 16:42
10

You could also write an extension method like this:

internal static class EnumerableHelpers
{
    public static void AddRange<T>(this IList<T> collection, IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            collection.Add(item);
        }
    }
}

Usage:

IList<int> collection = new MyCustomList(); //Or any other IList except for a fixed-size collection like an array
var items = new[] {1, 4, 5, 6, 7};
collection.AddRange(items);

Which is still iterating over items, but you don't have to write the iteration or cast every time you call it.

bashis
  • 1,200
  • 1
  • 16
  • 35
4

Another answer using LINQ, provided the thing you're adding is a List<T> or you are able to call ToList() on it:

IEnumerable<string> toAdd = new string[] {"a", "b", "c"};
IList<string> target = new List<string>();

toAdd.ToList().ForEach(target.Add);
RedRain
  • 103
  • 5
1

IList don't has AddRange() ,but has Concat() which combine yours collection

edena
  • 11
  • 1
  • 1
    it would be better to add a code sample to make you answer more useful – AndrewSilver Nov 13 '21 at 07:15
  • 1
    `IList.Concat(IEnumerable)` returns the concatenated sequence as an enumerable so leaves the list untouched. That may or may not be the desired result. – Manfred Apr 18 '22 at 03:38