为什么List<T>.IndexOf
允许超出范围的起始索引?
var list = new List<int>() { 100 };
Console.WriteLine(list.IndexOf(1/*item*/, 1/*start index*/));
不会有任何例外。但是这个集合中没有1
索引的项目!只有一个项目带有0
索引。
那么,为什么.Net
允许你这样做呢?
答案 0 :(得分:12)
首先,如果有人应该处理无效输入,那么它是运行时而不是编译器,因为输入具有相同的有效类型(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);
}
如您所见,它的目的是不允许您插入大于列表大小的无效索引,但是使用>
而不是>=
进行比较。< / p>
以下代码返回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*/));
答案 1 :(得分:9)
它允许它,因为有人认为这是好的,并且有人写了规范或实现了方法。
中也有一些记录0(零)在空列表中有效。
(我还认为Count
是任何列表的有效起始索引)
请注意,对于Array.IndexOf
Method:
如果
startIndex
等于Array.Length
,则该方法返回-1。如果startIndex
大于Array.Length
,则该方法会抛出ArgumentOutOfRangeException
。
让我在这里澄清我的答案。
您要问&#34;为什么此方法允许此输入&#34;。
唯一合法的原因是&#34;因为有人实施了这个方法,所以它确实做到了#34;。
这是一个错误吗?它很可能是。文档只说0是空列表的合法起始索引,它没有直接说1对于包含单个元素的列表是合法的。该方法的异常文档似乎与此相矛盾(正如评论中提到的那样),这似乎有利于它是一个错误。
但是&#34;为什么会这样做的唯一原因&#34;是有人实际实现了这种方法。它可能是一个有意识的选择,它可能是一个错误,它可能是代码或文档中的疏忽。
唯一可以判断实现这种方法的人是实现这种方法的人。
答案 2 :(得分:6)
当然,唯一一个可以肯定地说出这个问题的人是做出这个决定的人。
我看到的唯一合乎逻辑的原因(以及我的猜测)是允许这样使用
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>
共享相同的行为,显然它是有意的,绝对不是实施错误。
答案 3 :(得分:3)
要了解正在发生的事情,我们可以查看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
,因此当您有一个项目时,即使列表中不存在index
1
,也可以使用index
。< / p>
除非有一个我没有看到的特殊原因,否则这在框架中看起来是一个很好的旧的错误。文档甚至提到如果
,应该抛出异常
List<T>
超出{{1}}的有效索引范围。
答案 4 :(得分: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
的这种重载。