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

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

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

Исключений не будет. Но в этой коллекции нет элемента с индексом 1! Существует только один элемент с индексом 0. Итак, почему .Net позволяет вам это делать?

Ответы

Ответ 1

Я думаю, я понимаю, почему. Это немного легче реализовать такие методы. Посмотрите:

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, используя общую перегрузку без этого допуска.

Ответ 2

Прежде всего, если кто-то должен позаботиться о некорректном вводе, это время выполнения, а не компилятор, поскольку вход имеет тот же допустимый тип (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 | index находится за пределами допустимых индексов для 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*/));

Ответ 3

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

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

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

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

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

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

Позвольте мне пояснить свой ответ здесь.

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

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

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

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

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

Ответ 4

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

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

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> используют одно и то же поведение, по-видимому, оно предназначено и определенно не является ошибкой реализации.

Ответ 5

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

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>.