为什么 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);
}
如您所见,它的目的是不允许您插入大于列表大小的无效索引,但比较是使用 >
而不是 >=
.
如下代码returns 0
:
var list = new List<int>() { 100 };
Console.WriteLine(list.IndexOf(100/*item*/, 0/*start index*/));
如下代码returns -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 is outside the range of valid indexes for the 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
Method中也有一些记录:
0 (zero) is valid in an empty list.
(我也认为 Count
是任何列表的有效起始索引)
请注意,对于 Array.IndexOf
Method:
If startIndex
equals Array.Length
, the method returns -1. If startIndex
is greater than Array.Length
, the method throws an ArgumentOutOfRangeException
.
让我在这里澄清一下我的答案。
你问的是"Why does this method allow this input".
唯一合法的原因是 "Because someone implemented the method so that it did"。
这是一个错误吗?很可能是这样。文档只说 0 是空列表的合法起始索引,并没有直接说 1 对于其中包含单个元素的列表是否合法。该方法的异常文档似乎与此相矛盾(正如在评论中提出的那样),这似乎支持它是一个错误。
但是 "why does it do this" 的唯一原因是有人确实以这种方式实现了该方法。它可能是一个有意识的选择,它可能是一个错误,它可能是代码或文档中的疏忽。
只有实施此方法的人才能知道它是哪一个。
要了解发生了什么,我们可以查看 sources:
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
所以当你有一个项目时你可以使用 1
的 index
即使它不存在于列表中.
除非有我没有看到的特殊原因,否则这看起来像是框架中一个很好的旧错误。文档甚至提到如果
应该抛出异常
index
is outside the range of valid indexes for the List<T>
.
当然,唯一可以肯定地说出这是为什么的人就是做出那个决定的人。
我看到的唯一合乎逻辑的原因(这是我的猜测)是允许这样使用
for (int index = list.IndexOf(value); index >= 0; index = list.IndexOf(value, index + 1))
{
// do something
}
或者换句话说,能够从上次成功搜索的下一个索引安全地重新开始搜索。
这可能看起来不是很常见的情况,但却是处理字符串时的典型模式(例如,当人们想要避免 Split
时)。这让我想起 String.IndexOf 具有相同的行为并且有更好的记录(尽管没有说明原因):
The startIndex parameter can range from 0 to the length of the string instance. If startIndex equals the length of the string instance, the method returns -1.
继续,因为 Array
、string
和 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
的重载。
为什么 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);
}
如您所见,它的目的是不允许您插入大于列表大小的无效索引,但比较是使用 >
而不是 >=
.
如下代码returns
0
:var list = new List<int>() { 100 }; Console.WriteLine(list.IndexOf(100/*item*/, 0/*start index*/));
如下代码returns
-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 is outside the range of valid indexes for the
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
Method中也有一些记录:
0 (zero) is valid in an empty list.
(我也认为 Count
是任何列表的有效起始索引)
请注意,对于 Array.IndexOf
Method:
If
startIndex
equalsArray.Length
, the method returns -1. IfstartIndex
is greater thanArray.Length
, the method throws anArgumentOutOfRangeException
.
让我在这里澄清一下我的答案。
你问的是"Why does this method allow this input".
唯一合法的原因是 "Because someone implemented the method so that it did"。
这是一个错误吗?很可能是这样。文档只说 0 是空列表的合法起始索引,并没有直接说 1 对于其中包含单个元素的列表是否合法。该方法的异常文档似乎与此相矛盾(正如在评论中提出的那样),这似乎支持它是一个错误。
但是 "why does it do this" 的唯一原因是有人确实以这种方式实现了该方法。它可能是一个有意识的选择,它可能是一个错误,它可能是代码或文档中的疏忽。
只有实施此方法的人才能知道它是哪一个。
要了解发生了什么,我们可以查看 sources:
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
所以当你有一个项目时你可以使用 1
的 index
即使它不存在于列表中.
除非有我没有看到的特殊原因,否则这看起来像是框架中一个很好的旧错误。文档甚至提到如果
应该抛出异常
index
is outside the range of valid indexes for theList<T>
.
当然,唯一可以肯定地说出这是为什么的人就是做出那个决定的人。
我看到的唯一合乎逻辑的原因(这是我的猜测)是允许这样使用
for (int index = list.IndexOf(value); index >= 0; index = list.IndexOf(value, index + 1))
{
// do something
}
或者换句话说,能够从上次成功搜索的下一个索引安全地重新开始搜索。
这可能看起来不是很常见的情况,但却是处理字符串时的典型模式(例如,当人们想要避免 Split
时)。这让我想起 String.IndexOf 具有相同的行为并且有更好的记录(尽管没有说明原因):
The startIndex parameter can range from 0 to the length of the string instance. If startIndex equals the length of the string instance, the method returns -1.
继续,因为 Array
、string
和 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
的重载。