簡體   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