Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does List IndexOf allow out-of-range start index?

Why does List<T>.IndexOf allow out-of-range start index?

var list = new List<int>() { 100 };
Console.WriteLine(list.IndexOf(1/*item*/, 1/*start index*/));

There will not be any exceptions. But there is no item with 1 index in this collection! There is just one item with 0 index. So, why does .Net allow you to do it?

like image 686
Rustam Salakhutdinov Avatar asked Feb 20 '16 21:02

Rustam Salakhutdinov


2 Answers

First of all, if someone should take care of an invalid input it's the runtime and not the compiler since the input is of the same valid type (int).

With that said, actually, seeing the source code of IndexOf making it seem like an implementation bug:

[__DynamicallyInvokable]
public int IndexOf(T item, int index)
{
    if (index > this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
    }
    return Array.IndexOf<T>(this._items, item, index, this._size - index);
}

As you can see, it was intended to not allow you to insert an invalid index which is bigger than the size of the list, but the comparison is done with > instead of >=.

  • The following code returns 0:

    var list = new List<int>() { 100 };
    Console.WriteLine(list.IndexOf(100/*item*/, 0/*start index*/));
    
  • The following code returns -1:

    var list = new List<int>() { 100 };
    Console.WriteLine(list.IndexOf(100/*item*/, 1/*start index*/));
    
  • While The following code throws an Exception:

    var list = new List<int>() { 100 };
    Console.WriteLine(list.IndexOf(100/*item*/, 2/*start index*/));
    

There is no reason what so ever for the second and third cases to behave differently which makes it seem as a bug in the implementation of IndexOf.

Also, the documentation says:

ArgumentOutOfRangeException | index is outside the range of valid indexes for the List<T>.

Which as we have just seen is not what happening.

Note: the same behaviour happens with arrays:

int[] arr =  { 100 };

//Output: 0
Console.WriteLine(Array.IndexOf(arr, 100/*item*/, 0/*start index*/));

//Output: -1
Console.WriteLine(Array.IndexOf(arr, 100/*item*/, 1/*start index*/));

//Throws ArgumentOutOfRangeException
Console.WriteLine(Array.IndexOf(arr, 100/*item*/, 2/*start index*/));
like image 84
Tamir Vered Avatar answered Nov 15 '22 21:11

Tamir Vered


It allows it because someone decided this was OK, and that someone either wrote the spec or implemented the method.

It is also somewhat documented in List(T).IndexOf Method:

0 (zero) is valid in an empty list.

(which I also take that Count is a valid start index for any list)

Note that the same thing is documented, but slightly better documented, for Array.IndexOf Method:

If startIndex equals Array.Length, the method returns -1. If startIndex is greater than Array.Length, the method throws an ArgumentOutOfRangeException.

Let me clarify my answer here.

You're asking "Why does this method allow this input".

The only legal reason is "Because someone implemented the method so that it did".

Is it a bug? It may very well be. The documentation only says that 0 is legal start index for an empty list, it does not directly say that 1 is legal or not for a list with a single element in it. The exception documentation for the method seems to contradict this (as has been brought up in comments) which seems to be in favor of it being a bug.

But the only reason for "why does it do this" is that someone actually implemented the method that way. It may be a conscious choice, it may be a bug, it may be an oversight either in the code or in the documentation.

The only one that can tell which one it is would be the person or people that implemented this method.

like image 26
Lasse V. Karlsen Avatar answered Nov 15 '22 21:11

Lasse V. Karlsen