147

A coworker asked me today how to add a range to a collection. He has a class that inherits from Collection<T>. There's a get-only property of that type that already contains some items. He wants to add the items in another collection to the property collection. How can he do so in a C#3-friendly fashion? (Note the constraint about the get-only property, which prevents solutions like doing Union and reassigning.)

Sure, a foreach with Property. Add will work. But a List<T>-style AddRange would be far more elegant.

It's easy enough to write an extension method:

public static class CollectionHelpers
{
    public static void AddRange<T>(this ICollection<T> destination,
                                   IEnumerable<T> source)
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

But I have the feeling I'm reinventing the wheel. I didn't find anything similar in System.Linq or morelinq.

Bad design? Just Call Add? Missing the obvious?

4
  • 6
    Remember that the Q from LINQ is 'query' and is really about data retrieval, projection, transformation, etc. Modifying existing collections really doesn't fall into the realm of LINQ's intended purpose, which is why LINQ doesn't provide anything out-of-the-box for this. But extension methods (and in particular your sample) would be ideal for this.
    – Levi
    Commented Sep 25, 2009 at 0:46
  • One problem, ICollection<T> does not seem to have an Add method. msdn.microsoft.com/en-us/library/… However Collection<T> has one. Commented Jul 11, 2013 at 5:58
  • @TimGoodman - That's the non-generic interface. See msdn.microsoft.com/en-us/library/92t2ye13.aspx
    – TrueWill
    Commented Jul 11, 2013 at 17:26
  • "Modifying existing collections really doesn't fall into the realm of LINQ's intended purpose". @Levi Then why even have Add(T item) in the first place? Seems like a half-baked approach to offer the ability to add a single item and then expect all callers to iterate in order to add more than one at a time. Your statement is certainly true for IEnumerable<T> but I have found myself frustrated with ICollections on more than one occasion. I don't disagree with you, just venting.
    – akousmata
    Commented Mar 18, 2019 at 16:57

9 Answers 9

77

No, this seems perfectly reasonable. There is a List<T>.AddRange() method that basically does just this, but requires your collection to be a concrete List<T>.

4
  • 1
    Thanks; very true, but most public properties follow the MS guidelines and are not Lists.
    – TrueWill
    Commented Sep 25, 2009 at 0:52
  • 7
    Yeah - I was giving it more as rationale for why I don't think there is a problem with doing this. Just realize it will be less efficient than the List<T> version (since the list<T> can pre-allocate) Commented Sep 25, 2009 at 1:12
  • Just take care that AddRange method in .NET Core 2.2 might show a weird behavior if used incorrectly, as shown in this issue: github.com/dotnet/core/issues/2667
    – Bruno
    Commented May 2, 2019 at 12:58
  • 1
    The only problem I see with this is that the extension method applies to arrays, which cannot be added to. E.g. the following code will now compile, which seems undesirable: new [] { "" }.AddRange(new [] { "Alice" });.
    – dbc
    Commented Jun 20, 2023 at 17:56
47

Try casting to List in the extension method before running the loop. That way you can take advantage of the performance of List.AddRange.

public static void AddRange<T>(this ICollection<T> destination,
                               IEnumerable<T> source)
{
    List<T> list = destination as List<T>;

    if (list != null)
    {
        list.AddRange(source);
    }
    else
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}
7
  • 3
    The as operator will never throw. If destination cannot be cast, list will be null and the else block will execute.
    – rymdsmurf
    Commented Oct 29, 2014 at 12:04
  • 5
    arrgggh! Swap the condition branches, for the love of all that's holy! Commented Jul 15, 2016 at 16:11
  • 19
    I am serious, actually.The main reason is that it's extra cognitive-load, which is often really quite difficult. You're constantly trying to evaluate negative conditions, which is usually relatively hard, you have both branches anyway, it's (IMO) easier to say 'if null' do this, 'else' do this, rather than the opposite. It's also about defaults, they should be the positive concept as often as possible, .e.g `if (!thing.IsDisabled) {} else {}' requires you to stop and think 'ah, not is disabled means is enabled, right, got that, so the other branch is when it IS disabled). Hard to parse. Commented Jul 20, 2016 at 7:42
  • 16
    Interpreting "something != null" is not more difficult than interpreting "something == null". The negation operator however is a completely different thing, and in your last example rewriting the if-else-statement would elliminate that operator. That is a objectively an improvement, but one that is not related to the original question. In that particular case the two forms are a matter of personal preferences, and I would prefer the "!="-operator, given the reasoning above.
    – rymdsmurf
    Commented Jul 25, 2016 at 9:39
  • 34
    Pattern matching will make everyone happy... ;-) if (destination is List<T> list) Commented Sep 1, 2017 at 21:47
46

Since .NET4.5 if you want one-liner you can use System.Collections.Generic ForEach.

source.ForEach(o => destination.Add(o));

or even shorter as

source.ForEach(destination.Add);

Performance-wise it's the same as for each loop (syntactic sugar).

Also don't try assigning it like

var x = source.ForEach(destination.Add) 

cause ForEach is void.

Edit: Copied from comments, Lippert's opinion on ForEach.

5
21

Remember that each Add will check the capacity of the collection and resize it whenever necessary (slower). With AddRange, the collection will be set the capacity and then added the items (faster). This extension method will be extremely slow, but will work.

1
  • 4
    To add to this, there will also be a collection change notification for each addition, as opposed to one bulk notification with AddRange.
    – Nick Udell
    Commented Sep 22, 2014 at 9:58
7

Here is a bit more advanced/production-ready version:

    public static class CollectionExtensions
    {
        public static TCol AddRange<TCol, TItem>(this TCol destination, IEnumerable<TItem> source)
            where TCol : ICollection<TItem>
        {
            if(destination == null) throw new ArgumentNullException(nameof(destination));
            if(source == null) throw new ArgumentNullException(nameof(source));

            // don't cast to IList to prevent recursion
            if (destination is List<TItem> list)
            {
                list.AddRange(source);
                return destination;
            }

            foreach (var item in source)
            {
                destination.Add(item);
            }

            return destination;
        }
    }
2
  • rymdsmurf's answer may look naive, too simple, but it works with heterogeneous lists. Is it possible to make this code support this use case? Commented Aug 11, 2020 at 18:07
  • E.g.: destination is a list of Shape, an abstract class. source is a list of Circle, an inherited class. Commented Aug 11, 2020 at 18:08
1

The C5 Generic Collections Library classes all support the AddRange method. C5 has a much more robust interface that actually exposes all of the features of its underlying implementations and is interface-compatible with the System.Collections.Generic ICollection and IList interfaces, meaning that C5's collections can be easily substituted as the underlying implementation.

0

You could add your IEnumerable range to a list then set the ICollection = to the list.

        IEnumerable<T> source;

        List<item> list = new List<item>();
        list.AddRange(source);

        ICollection<item> destination = list;
1
0

Or you can just make an ICollection extension like this:

 public static ICollection<T> AddRange<T>(this ICollection<T> @this, IEnumerable<T> items)
    {
        foreach(var item in items)
        {
            @this.Add(item);
        }

        return @this;
    }

Using it would be just like using it on a list:

collectionA.AddRange(IEnumerable<object> items);
0

Agree with some guys above and Lipert's opinion. In my case, it's quite often to do like this:

ICollection<int> A;
var B = new List<int> {1,2,3,4,5};
B.ForEach(A.Add);

To have an extension method for such operation a bit redundant in my view.

3
  • Your answer is not adding anything that was not already suggested by the numerous older answers. Commented Feb 16, 2021 at 7:03
  • what can I do when I want this: aList = If(True,Nothing,aList.AddRange(...)) In expressionB I want to addItems. But the If expression does not allow this. Expression makes no value... Commented Feb 26, 2021 at 14:31
  • Perhaps I am not fully understood your question, but if it about to add items by the condition, it can be done like this: ICollection<int> A = new List<int>(); var B = new List<int> { 1, 2, 3, 4, 5 }; B.ForEach(s =>{ if (s > 3) A.Add(s); }); Commented Feb 28, 2021 at 14:52

Not the answer you're looking for? Browse other questions tagged or ask your own question.