簡體   English   中英

為什么Linq擴展方法不位於IEnumerator而不是IEnumerable?

[英]Why don't the Linq extension methods sit on IEnumerator rather than IEnumerable?

許多Linq算法只需要對輸入進行一次遍歷,例如Select。

但是,所有Linq擴展方法都位於IEnumerable而非IEnumerator上

    var e = new[] { 1, 2, 3, 4, 5 }.GetEnumerator(); 
    e.Select(x => x * x); // Doesn't work 

這意味着在從“已打開”流中讀取的任何情況下都不能使用Linq。

對於我當前正在處理的項目,這種情況經常發生-我想返回一個IEnumerator,其IDispose方法將關閉流,並使所有下游Linq代碼對此進行操作。

簡而言之,我有一個“已經開放”的結果流,可以將其轉換為適當的一次性IEnumerator-但不幸的是,所有下游代碼都需要IEnumerable而不是IEnumerator,即使它只是要做一個“通過”。

即我想在各種不同的來源(CSV文件,IDataReaders等)上“實現”此返回類型:

class TabularStream 
{ 
    Column[] Columns; 
    IEnumerator<object[]> RowStream; 
}

為了獲得“列”,我必須已經打開CSV文件,啟動SQL查詢或其他操作。 然后,我可以返回一個“ IEnumerator”,其Dispose方法關閉資源-但是所有Linq操作都需要一個IEnumerable。

我知道最好的解決方法是實現一個IEnumerable,它的GetEnumerator()方法返回一個和唯一的IEnumerator,並且如果某事嘗試兩次執行GetEnumerator()調用則拋出錯誤。

這一切聽起來還不錯,還是有一種更好的方法可以用Linq易於使用的方式實現“ TabularStream”?

在我看來,直接使用IEnumerator<T>很少是一個好主意。

一方面,它編碼了具有破壞性的事實-而LINQ查詢通常可以運行多次。 它們本來是沒有副作用的,但是在IEnumerator<T>進行迭代的行為自然是副作用。

這實際上使在LINQ to Objects中執行某些優化幾乎是不可能的,例如,如果您實際上是在向ICollection<T>詢問其計數,則使用Count屬性。

至於您的解決方法:是的, OneShotEnumerable是一種合理的方法。

盡管我通常都同意Jon Skeet的回答 ,但我也遇到了很少的情況,與IEnumerator一起工作確實比將它們包裝在僅一次的IEnumerable更合適。

我將首先說明一個這樣的案例,並描述我自己的解決方案。

案例示例:僅轉發,不可撤消的數據庫游標

ESRI的用於訪問地理數據庫( ArcObjects )的API具有只能重置的僅向前數據庫游標。 從本質上講,它們等效於IEnumerator API。 但是沒有等效於IEnumerable 因此,如果您想以“ .NET方式”包裝該API,則有三個選項(我按以下順序進行了探討):

  1. 將游標包裝為IEnumerator (因為它實際上就是它)並直接使用IEnumerator (這很麻煩)。

  2. 將光標或包裝為(1)的IEnumerator包裝為僅一次的IEnumerable (使其與LINQ兼容,並且通常更易於使用)。 這里的錯誤是它不是 IEnumerable ,因為它不能被多次枚舉,並且代碼的用戶或維護者可能會忽略它。

  3. 光標本身作為IEnumerable ,但其可用於檢索光標 (例如查詢條件和參照數據庫對象被查詢)。 這樣,只需簡單地重新執行整個查詢,就可以進行多次迭代。 這是我當時最終決定的。

最后一個選擇是實用的解決方案,對於類似情況(如果適用),我通常會建議這樣做。 如果您正在尋找其他解決方案,請繼續閱讀。


重新實現IEnumerator<T>接口的LINQ查詢運算符?

從技術上講,可以為IEnumerator<T>接口實現LINQ的部分或全部查詢運算符。 一種方法是編寫一堆擴展方法,例如:

public static IEnumerator<T> Where(this IEnumerator<T> xs, Func<T, bool> predicate)
{
    while (xs.MoveNext())
    {
        T x = xs.Current;
        if (predicate(x)) yield return x;
    }
    yield break;
}

讓我們考慮一些關鍵問題:

  • 運算符絕不能返回IEnumerable<T> ,因為這意味着您可以突破自己的“ LINQ to IEnumerator ”世界,轉而進入常規的LINQ。 到此為止,您將遇到上面已經描述的不可重復性問題。

  • 您無法使用foreach循環來處理某些查詢的結果…除非查詢運算符返回的每個IEnumerator<T>對象都實現了返回thisGetEnumerator方法。 提供該其他方法將意味着您不能使用yield return/break ,而必須手動編寫IEnumerator<T>類。

    這只是很奇怪,並且可能濫用了IEnumerator<T>foreach構造。

  • 如果返回IEnumerable<T>是被禁止的並返回IEnumerator<T>是麻煩的(因為foreach不工作),為什么不返回純數組? 因為這樣查詢不再是懶惰的。


IQueryable + IEnumerator = IQueryator

將查詢的執行推遲到完全組成該怎么辦? IEnumerable世界中, IQueryable就是這樣做的。 因此,從理論上講,我們可以構建一個IEnumerator等效項,我將其稱為IQueryator

  • IQueryator可以檢查邏輯錯誤,例如在諸如Count類的先前操作完全消耗完序列后,對該序列執行任何操作。 也就是說,像Count這樣的所有消耗大量運算符都必須始終是查詢運算符串聯中的最后一個。

  • IQueryator可以返回一個數組(如上面的建議)或其他只讀集合,但不能由單個運算符返回; 僅在查詢執行時。

實施IQueryator需要花費一些時間...問題是,實際上值得付出努力嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM