處理可能多次枚舉IEnumerable的警告

本文翻譯自:Handling warning for possible multiple enumeration of IEnumerable

In my code in need to use an IEnumerable<> several times thus get the Resharper error of "Possible multiple enumeration of IEnumerable ". 在我的代碼中需要使用IEnumerable<>幾次因此得到Resharper錯誤“可能多次枚舉IEnumerable ”。

Sample code: 示例代碼:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null || !objects.Any())
        throw new ArgumentException();

    var firstObject = objects.First();
    var list = DoSomeThing(firstObject);        
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}
  • I can change the objects parameter to be List and then avoid the possible multiple enumeration but then I don't get the highest object that I can handle. 我可以將objects參數更改爲List ,然後避免可能的多次枚舉,但後來我沒有得到我能處理的最高對象。
  • Another thing that I can do is to convert the IEnumerable to List at the beginning of the method: 我能做的另一件事是在方法的開頭將IEnumerable轉換爲List

 public List<object> Foo(IEnumerable<object> objects)
 {
    var objectList = objects.ToList();
    // ...
 }

But this is just awkward . 但這只是尷尬

What would you do in this scenario? 在這種情況下你會做什麼?


#1樓

參考:https://stackoom.com/question/YZoq/處理可能多次枚舉IEnumerable的警告


#2樓

I usually overload my method with IEnumerable and IList in this situation. 在這種情況下,我通常使用IEnumerable和IList重載我的方法。

public static IEnumerable<T> Method<T>( this IList<T> source ){... }

public static IEnumerable<T> Method<T>( this IEnumerable<T> source )
{
    /*input checks on source parameter here*/
    return Method( source.ToList() );
}

I take care to explain in the summary comments of the methods that calling IEnumerable will perform a .ToList(). 我注意在調用IEnumerable的方法的摘要註釋中解釋將執行.ToList()。

The programmer can choose to .ToList() at a higher level if multiple operations are being concatenated and then call the IList overload or let my IEnumerable overload take care of that. 如果連接多個操作,程序員可以選擇更高級別的.ToList(),然後調用IList重載或讓我的IEnumerable重載處理。


#3樓

Using IReadOnlyCollection<T> or IReadOnlyList<T> in the method signature instead of IEnumerable<T> , has the advantage of making explicit that you might need to check the count before iterating, or to iterate multiple times for some other reason. 在方法簽名中使用IReadOnlyCollection<T>IReadOnlyList<T>而不是IEnumerable<T> ,具有明確的優勢,即您可能需要在迭代之前檢查計數,或者由於某些其他原因而多次迭代。

However they have a huge downside that will cause problems if you try to refactor your code to use interfaces, for instance to make it more testable and friendly to dynamic proxying. 但是,如果您嘗試重構代碼以使用接口,它們會產生很大的缺點,例如使其更易於測試並且對動態代理更友好。 The key point is that IList<T> does not inherit from IReadOnlyList<T> , and similarly for other collections and their respective read-only interfaces. 關鍵點在於IList<T>不從IReadOnlyList<T>繼承 ,對於其他集合及其各自的只讀接口也是如此。 (In short, this is because .NET 4.5 wanted to keep ABI compatibility with earlier versions. But they didn't even take the opportunity to change that in .NET core. ) (簡而言之,這是因爲.NET 4.5希望保持ABI與早期版本的兼容性。 但他們甚至沒有機會在.NET核心中改變它。

This means that if you get an IList<T> from some part of the program and want to pass it to another part that expects an IReadOnlyList<T> , you can't! 這意味着如果從程序的某個部分獲得IList<T>並希望將其傳遞給需要IReadOnlyList<T>另一個部分,則不能! You can however pass an IList<T> as an IEnumerable<T> . 但是,您可以將IList<T>作爲IEnumerable<T>傳遞

In the end, IEnumerable<T> is the only read-only interface supported by all .NET collections including all collection interfaces. 最後, IEnumerable<T>是所有.NET集合(包括所有集合接口)支持的唯一隻讀接口。 Any other alternative will come back to bite you as you realize that you locked yourself out from some architecture choices. 任何其他選擇都會回來咬你,因爲你意識到你把自己鎖定在某些架構選擇之外。 So I think it's the proper type to use in function signatures to express that you just want a read-only collection. 所以我認爲在函數簽名中使用它是正確的類型來表示你只需要一個只讀集合。

(Note that you can always write a IReadOnlyList<T> ToReadOnly<T>(this IList<T> list) extension method that simple casts if the underlying type supports both interfaces, but you have to add it manually everywhere when refactoring, where as IEnumerable<T> is always compatible.) (請注意,如果底層類型支持兩個接口,您總是可以編寫一個簡單強制轉換的IReadOnlyList<T> ToReadOnly<T>(this IList<T> list)擴展方法,但您必須在重構時手動添加它,其中as IEnumerable<T>始終兼容。)

As always this is not an absolute, if you're writing database-heavy code where accidental multiple enumeration would be a disaster, you might prefer a different trade-off. 一如既往,這不是絕對的,如果你正在編寫數據庫密集的代碼,意外的多次枚舉將是一場災難,你可能更喜歡不同的權衡。


#4樓

If you only need to check the first element you can peek on it without iterating the whole collection: 如果你只需要檢查第一個元素就可以查看它而不需要迭代整個集合:

public List<object> Foo(IEnumerable<object> objects)
{
    object firstObject;
    if (objects == null || !TryPeek(ref objects, out firstObject))
        throw new ArgumentException();

    var list = DoSomeThing(firstObject);
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}

public static bool TryPeek<T>(ref IEnumerable<T> source, out T first)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    IEnumerator<T> enumerator = source.GetEnumerator();
    if (!enumerator.MoveNext())
    {
        first = default(T);
        source = Enumerable.Empty<T>();
        return false;
    }

    first = enumerator.Current;
    T firstElement = first;
    source = Iterate();
    return true;

    IEnumerable<T> Iterate()
    {
        yield return firstElement;
        using (enumerator)
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current;
            }
        }
    }
}

#5樓

The problem with taking IEnumerable as a parameter is that it tells callers "I wish to enumerate this". IEnumerable作爲參數的問題在於它告訴調用者“我希望枚舉這個”。 It doesn't tell them how many times you wish to enumerate. 它沒有告訴他們你想要枚舉多少次。

I can change the objects parameter to be List and then avoid the possible multiple enumeration but then I don't get the highest object that I can handle . 我可以將objects參數更改爲List,然後避免可能的多次枚舉,但後來我沒有得到我能處理的最高對象

The goal of taking the highest object is noble, but it leaves room for too many assumptions. 採取最高目標的目標是高尚的,但它爲太多的假設留下了空間。 Do you really want someone to pass a LINQ to SQL query to this method, only for you to enumerate it twice (getting potentially different results each time?) 你真的希望有人將LINQ to SQL查詢傳遞給這個方法,只爲你枚舉它兩次(每次得到可能不同的結果嗎?)

The semantic missing here is that a caller, who perhaps doesn't take time to read the details of the method, may assume you only iterate once - so they pass you an expensive object. 這裏缺少的語義是,調用者可能沒有花時間閱讀方法的細節,可能假設您只迭代一次 - 因此他們會傳遞給您一個昂貴的對象。 Your method signature doesn't indicate either way. 您的方法簽名不表示任何一種方式。

By changing the method signature to IList / ICollection , you will at least make it clearer to the caller what your expectations are, and they can avoid costly mistakes. 通過將方法簽名更改爲IList / ICollection ,您至少可以使調用者更清楚您的期望是什麼,並且可以避免代價高昂的錯誤。

Otherwise, most developers looking at the method might assume you only iterate once. 否則,大多數查看該方法的開發人員可能會假設您只迭代一次。 If taking an IEnumerable is so important, you should consider doing the .ToList() at the start of the method. 如果獲取IEnumerable非常重要,您應該考慮在方法的.ToList()執行.ToList()

It's a shame .NET doesn't have an interface that is IEnumerable + Count + Indexer, without Add/Remove etc. methods, which is what I suspect would solve this problem. 遺憾的是.NET沒有IEnumerable + Count + Indexer的接口,沒有Add / Remove等方法,這是我懷疑會解決這個問題的方法。


#6樓

If your data is always going to be repeatable, perhaps don't worry about it. 如果您的數據總是可重複的,也許不用擔心。 However, you can unroll it too - this is especially useful if the incoming data could be large (for example, reading from disk/network): 但是,您也可以將其展開 - 如果傳入的數據很大(例如,從磁盤/網絡讀取),這尤其有用:

if(objects == null) throw new ArgumentException();
using(var iter = objects.GetEnumerator()) {
    if(!iter.MoveNext()) throw new ArgumentException();

    var firstObject = iter.Current;
    var list = DoSomeThing(firstObject);  

    while(iter.MoveNext()) {
        list.Add(DoSomeThingElse(iter.Current));
    }
    return list;
}

Note I changed the semantic of DoSomethingElse a bit, but this is mainly to show unrolled usage. 注意我稍微改變了DoSomethingElse的語義,但這主要是爲了顯示展開的用法。 You could re-wrap the iterator, for example. 例如,您可以重新包裝迭代器。 You could make it an iterator block too, which could be nice; 你也可以把它變成一個迭代器塊,這可能很好; then there is no list - and you would yield return the items as you get them, rather than add to a list to be returned. 然後沒有list - 你會在你得到它們時yield return這些項目,而不是添加到要返回的列表中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章