Почему List IndexOf разрешает стартовый индекс вне диапазона?

Почему List<T>.IndexOf разрешить стартовый индекс вне диапазона?

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

Там не будет никаких исключений. Но нет пункта с 1 Индекс в этой коллекции! Есть только один элемент с 0 индекс. Итак, почему .Net разрешить тебе это сделать?

5 ответов

Решение

Я думаю, я понимаю, почему. Это как-то проще в реализации таких методов. Посмотрите:

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);
}

Перегрузка этого метода использует другую, более распространенную перегрузку:

return Array.IndexOf<T>(this._items, item, index, this._size - index);

Так что этот метод также использовать его:

public int IndexOf(T item)

Так что не имеет смысла, если этот код:

var list = new List<int>(); /*empty!*/
Console.WriteLine(list.IndexOf(1/*item*/));

бросит Exception, Но нет никакого способа использовать эту перегрузку IndexOf используя общую перегрузку без этого допуска.

Прежде всего, если кто-то должен позаботиться о недопустимом вводе, это среда выполнения, а не компилятор, так как вход имеет тот же действительный тип (int).

С учетом сказанного, на самом деле, видя исходный код IndexOf заставляя это казаться ошибкой реализации:

[__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);
}

Как вы можете видеть, он был предназначен, чтобы не позволить вам вставить неверный индекс, который больше, чем размер списка, но сравнение сделано с > вместо >=,

  • Следующий код возвращает 0:

    var list = new List<int>() { 100 };
    Console.WriteLine(list.IndexOf(100/*item*/, 0/*start index*/));
    
  • Следующий код возвращает -1:

    var list = new List<int>() { 100 };
    Console.WriteLine(list.IndexOf(100/*item*/, 1/*start index*/));
    
  • В то время как следующий код Exception:

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

Нет никаких причин, по которым второй и третий случаи ведут себя по-разному, что делает его ошибкой в ​​реализации IndexOf,

Также в документации сказано:

ArgumentOutOfRangeException | Индекс выходит за пределы диапазона допустимых индексов для List<T>,

Что, как мы только что видели , не то, что происходит.

Примечание: такое же поведение происходит с массивами:

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*/));

Это позволяет, потому что кто-то решил, что это нормально, и что кто-то либо написал спецификацию, либо внедрил метод.

Это также несколько документировано в List(T).IndexOf Метод:

0 (ноль) действует в пустом списке.

(что я тоже так понимаю Count допустимый начальный индекс для любого списка)

Обратите внимание, что то же самое задокументировано, но немного лучше задокументировано, для Array.IndexOf Метод:

Если startIndex равняется Array.Length метод возвращает -1. Если startIndex больше, чем Array.Length метод бросает ArgumentOutOfRangeException,

Позвольте мне уточнить мой ответ здесь.

Вы спрашиваете: "Почему этот метод позволяет этот ввод".

Единственная законная причина - "Потому что кто-то реализовал метод так, чтобы он сделал".

Это ошибка? Это может быть очень хорошо. Документация только говорит, что 0 является допустимым начальным индексом для пустого списка, это прямо не говорит, что 1 является допустимым или нет для списка с одним элементом в нем. Документация об исключениях для метода, кажется, противоречит этому (как было упомянуто в комментариях), что, кажется, в пользу того, что это ошибка.

Но единственная причина "почему он это делает" заключается в том, что кто-то на самом деле реализовал метод таким образом. Это может быть осознанный выбор, это может быть ошибка, это может быть упущение либо в коде, либо в документации.

Единственный, кто может сказать, кто это, будет человек или люди, которые реализовали этот метод.

Конечно, единственный, кто может точно сказать, почему это люди, которые приняли это решение.

Единственная логическая причина, которую я вижу (и это мое предположение), заключается в том, чтобы разрешить такое использование

for (int index = list.IndexOf(value); index >= 0; index = list.IndexOf(value, index + 1))
{
    // do something
}

или, другими словами, чтобы можно было безопасно перезапустить поиск из следующего индекса последнего успешного поиска.

Это может выглядеть не очень распространенным сценарием, но это типичный шаблон при обработке строк (например, когда нужно избежать Split). Что напоминает мне, что String.IndexOf имеет такое же поведение и немного лучше задокументирован (хотя без указания причины):

Параметр startIndex может варьироваться от 0 до длины экземпляра строки. Если startIndex равен длине экземпляра строки, метод возвращает -1.

Чтобы возобновить, так как Array, string а также List<T> использовать то же поведение, по-видимому, оно предназначено и определенно не является ошибкой реализации.

Чтобы понять, что происходит, мы можем взглянуть на источники:

public int IndexOf(T item, int index) {
    if (index > _size)
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
    Contract.Ensures(Contract.Result<int>() >= -1);
    Contract.Ensures(Contract.Result<int>() < Count);
    Contract.EndContractBlock();
    return Array.IndexOf(_items, item, index, _size - index);
}

_size здесь эквивалентно list.Count поэтому, когда у вас есть один предмет, вы можете использовать index из 1 даже если его нет в списке.

Если нет особой причины, которую я не вижу, это выглядит как старая добрая ошибка в фреймворке. В документации даже упоминается, что исключения должны быть выброшены, если

index находится вне диапазона допустимых индексов для List<T>,

Другие вопросы по тегам