-2

Recently I got a task to work on one of the FxCop warning - Do not expose generic lists. So I tried changing the List<T> to ICollection<T>. But later point of time, while doing the unit testing I found that the AddRange() is not working properly as expected. It is not adding the collection of elements to the collection object.

Here is the sample code

gc.ToList().AddRange(sampleList);

And I have two questions to ask

  1. Why it is not adding the items to the collection. Below is the code:

    public class GenericClass
    {
        public int Id;
        public string Name;
    }  
    
    
    class Program
    {
         static void Main(string[] args)
         {
    
          ICollection<GenericClass> gc = new  List<GenericClass>();
    
        var sampleList = new List<GenericClass>()
                             {
                                 new GenericClass {Id = 1, Name = "ASD"},
                                 new GenericClass {Id = 2, Name = "QWER"},
                                 new GenericClass {Id = 3, Name = "BNMV"},
                             };
    
        Console.WriteLine(gc.GetType()); // gc is of type
    
        gc.ToList().AddRange(sampleList); // sampleList items are not getting added to gc.
        Console.ReadKey();
    }
    

    }

  2. List<T> inherits from ICollection<T> and List<T> has AddRange(), etc functions. When I tried to cast to parent reference (ICollection<T>) to child class object (List<T>), why doesn't Intellisense suggest AddRange(). Instead I need to do .ToList() and then it shows AddRange(). Screenshot

I searched a lot for this. But couldn't find a reason that satisfied me. So please help me with the understanding. It will be a great help.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
Vijay
  • 745
  • 2
  • 9
  • 25
  • Change `ICollection` to `var`. There is no reason to use `ICollection` in that context. – mjwills Mar 28 '19 at 05:21
  • `But couldn't find a reason that satisfied me.` You told `gc` that it was a `ICollection`. `ICollection` lacks `AddRange` (it does have `Add` but not `AddRange`). It really is that simple. The solution is to use `var` so that the compiler **knows** that `gc` is a `List`. – mjwills Mar 28 '19 at 05:22
  • `Do not expose generic lists.` You aren't **exposing** lists anywhere. Unless you are **returning** the list from a function, or declaring a property / field as `List` then you aren't exposing it. 95% of the time in those scenarios (which, again, you aren't in) then you would define the property / field / return type as `IReadOnlyList` It avoids the issues with `IList`. – mjwills Mar 28 '19 at 05:25

2 Answers2

3

The AddRange() method is working fine. The problem is, you didn't read the documentation for the ToList() extension method closely, and so don't realize that the ToList() method returns an entirely new object.

From the documentation:

Creates a List<T> from an IEnumerable<T>.

Since the object you're calling AddRange() with is not in fact the original collection, the original collection remains unchanged.

In a sense, your question is the List<T> equivalent to the very common question "why isn't string.Replace() working?"

In the example you give, there is not really a much better solution than simply not hiding the List<T> in the first place. You can, since you are using the generic ICollection<T> interface, write your own AddRange() as an extension method:

public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> range)
{
    foreach (T t in range)
    {
        collection.Add(t);
    }
}

But I'm not convinced this is really all that much better than just leaving the type as List<T>, if the goal is to be able to modify the object and use an AddRange() method.

Casting an ICollection<T> reference back to its underlying List<T> (as another answerer proposes) is pointless, as doing so would negate any value in using the ICollection<T> interface in the first place.

Just leave the reference as List<T>, at least in any context where you actually need to modify the collection. (It's fine, and even possibly beneficial, to expose that list in other contexts using only ICollection<T>, but that's an entirely different discussion.)

As long as I'm answering, I will mention that I am skeptical that you've correctly understood the intent of the FxCop warning, as exposing generic lists is not in and of itself inherently harmful.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • **ICollection interface doesn't allow modification of the collection at all**. Could you please elaborate on that statement. And can you provide a resolution of how to add items to the collection object then?? – Vijay Mar 28 '19 at 05:17
  • 2
    `ICollection interface doesn't allow modification of the collection at all.` That is false. https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.icollection-1.add?view=netframework-4.7.2 – mjwills Mar 28 '19 at 05:23
  • ICollection interface doesn't allow modification of the collection at all. But when I am trying to do a ToList().AddRange(simpleList). Why doesn't the list is getting appended to the collection object. **gc.ToList().AddRange(sampleList);** – Vijay Mar 28 '19 at 06:18
  • @mjwills: thanks...you won't believe it, but I did in fact look at the docs, was sure there was an Add() method, but couldn't find it, and so wrote what I did. Looks like I got confused about `ICollection` vs `ICollection` (the OP's original post had some formatting issues, and the generic syntax wasn't showing up). – Peter Duniho Mar 28 '19 at 06:54
  • 1
    @Vijay: while I wrote `ICollection` in my statement, what I was actually thinking of was `ICollection`. You can modify `ICollection`...see my edited answer above. As for this: _"Why doesn't the list is getting appended to the collection object"_, I answered that directly in my first paragraph: when you call `AddRange()`, the `this` object the method applies to **is not the `gc` object you started with**. It's the entirely new `List` object that was returned by the `ToList()` method. – Peter Duniho Mar 28 '19 at 07:02
2

ToList() returns a new instance, which is why you are not able to see the result of AddRange in "gc". If you really want to use AddRange, you can do the following.

((List<GenericClass>)gc).AddRange(sampleList);
Anu Viswan
  • 17,797
  • 2
  • 22
  • 51
  • Could you please be more specific about statement as after I put .ToList() to the object, then only I am see the AddRange function. Is your answer is about question (1) or (2) – Vijay Mar 28 '19 at 05:12