Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why I can't add items to the Generic List with indexer?

Here is a strange situation I have seen today:

I have a generic list and I want add items to my list with it's indexer like this:

List<string> myList = new List<string>(10);
myList[0] = "bla bla bla...";

When I try this, I'm getting ArgumentOutOfRangeException

enter image description here

Then I looked at List<T> indexer set method, and here it is:

[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"),    __DynamicallyInvokable] 
  set
  {
    if ((uint) index >= (uint) this._size)  
      ThrowHelper.ThrowArgumentOutOfRangeException();  //here is exception
    this._items[index] = value;
    ++this._version;
  }

And also looked at Add method:

[__DynamicallyInvokable]
public void Add(T item)
{
  if (this._size == this._items.Length)
    this.EnsureCapacity(this._size + 1);
  this._items[this._size++] = item;
  ++this._version;
}

Now, as I see both methods are using the same way:

// Add() Method
this._items[this._size++] = item; 
// Setter method
this._items[index] = value;

The _items is an array of type T :

private T[] _items;

And in the constructor _items initialized like this:

this._items = new T[capacity]

Now, after all of these I'm curious about why I can't add items into my list with an index
,although I specify list capacity explicitly?

like image 410
Selman Genç Avatar asked Dec 30 '13 13:12

Selman Genç


People also ask

How to use List with class in C#?

A List<T> can be resized dynamically but arrays cannot. List<T> class can accept null as a valid value for reference types and it also allows duplicate elements. If the Count becomes equals to Capacity, then the capacity of the List increased automatically by reallocating the internal array.

How to assign List C#?

The following example shows how to create list and add elements. In the above example, List<int> primeNumbers = new List<int>(); creates a list of int type. In the same way, cities and bigCities are string type list. You can then add elements in a list using the Add() method or the collection-initializer syntax.

What is generic List in C#?

Generic List<T> is a generic collection in C#. The size can be dynamically increased using List, unlike Arrays.

How does C# List insert work?

The Insert method of List<T> class inserts an object at a given position. The first parameter of the method is the 0th based index in the List. <T>. The InsertRange() method can insert a collection at the given position.


2 Answers

The reason is that you don't add to the list with the indexer, you replace existing items.

Since you have not yet added any items to the list, it is empty, and any attempt at using the indexer to "add" items to it will throw that exception.

This:

new List<string>(11);

does not create a list with 11 elements, it creates a list with capacity for 11 elements initially. This is an optimization. If you add more elements, the list will have to be resized internally, and you can pass in the expected or known capacity to avoid too many of those resizes.

Here's a LINQPad program that demonstrates:

void Main()
{
    var l = new List<string>(10);
    l.Dump(); // empty list

    l.Add("Item");
    l.Dump(); // one item

    l[0] = "Other item";
    l.Dump(); // still one item

    l.Capacity.Dump(); // should be 10
    l.AddRange(Enumerable.Range(1, 20).Select(idx => idx.ToString()));
    l.Capacity.Dump(); // should be 21 or more
}

Output:

output of linqpad program


Internals

Internally, inside a List<T>, an array is actually used to hold the elements. Additionally, a Count property/value is kept to keep track of how many of those array elements have actually been used.

When you construct an empty list, not passing in a capacity, a default one is used, and this is the initial size of that array.

As you keep adding new elements to the list, slowly you will fill up that array, towards the end. Once you have filled the entire array, and add another element to it, a new, bigger, array will have to be constructed. All the elements in the old array are then copied over to this new, bigger, array, and from now on, that array is used instead.

That is why the internal code calls that EnsureCapacity method. This method is the one doing the resize operation, if necessary.

Every time the array has to be resized, a new array is constructed and all the elements copied over. As the array grows, this operation grows in cost. It's not all that much, but it still isn't free.

That is why, if you know that you will need to store, say, 1000 elements in the list, it is a good idea to pass in a capacity value to begin with. That way, the initial size of that array might be large enough to never need to be resized/replaced. At the same time, it's not a good idea to just pass in a very large value for capacity, as this might end up using a lot of memory unnecessary.

Also know that everything in this section of the answer is undocumented (as far as I know) behavior, and should any details or specific behavior you might learn from it should never influence the code you write, other than the knowledge about passing in a good capacity value.

like image 74
Lasse V. Karlsen Avatar answered Sep 17 '22 08:09

Lasse V. Karlsen


No, it's not strange. You are mistakenly assuming the constructor that accepts capacity initializes that many elements in the list. It does not. The list is empty and you must add elements.

If for some reason you need to initialize it with elements, you could do this:

 new List<string>(Enumerable.Repeat<string>(string.Empty, 11));

Sounds like a string[] array is a better fit though.

like image 25
James World Avatar answered Sep 18 '22 08:09

James World