繁体   English   中英

为什么List IndexOf允许超出范围的起始索引?

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

为什么List<T>.IndexOf允许超出范围的起始索引?

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

没有任何例外。 但是这个系列中没有1索引的项目! 只有一个项目有0索引。 那么,为什么.Net允许你这样做呢?

首先,如果有人应该处理无效输入,那么它是运行时而不是编译器,因为输入具有相同的有效类型( 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*/));

它允许它,因为有人认为这是好的,有人写了规范或实现了方法。

它在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。

要恢复,因为ArraystringList<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>的有效索引范围。

我想,我理解为什么。 实现这样的方法更容易。 看:

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这种重载。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM