2010-05-13 92 views
3

如果我正在步行通過IEnumerable<T>,是否有任何方法可以獲得代表當前剩餘項目的新IEnumerable<T>IEnumerable <T>代表IEnumerable的「其餘」<T>序列

例如,我想編寫一個擴展方法IEnumerator<T>.Remaining()

IEnumerable<int> sequence = ... 
IEnumerator<int> enumerator = sequence.GetEnumerator(); 

if (enumerator.MoveNext() && enumerator.MoveNext()) { 
    IEnumerable<int> rest = enumerator.Remaining(); 
    // 'rest' would contain elements in 'sequence' start at the 3rd element 
} 

我想一種單鏈表中收集的,所以應該有代表任何剩餘的一種方式元素,對嗎?我看不出有什麼辦法可以在IEnumerable<T>IEnumerator<T>上看到,所以也許它與可能無界的,不確定的元素序列的概念是不相容的。

+0

這是一個恥辱,沒有'上的IEnumerable Clone'方法。這可能會幫助你。你可以實現一個EnumerableEx 類包裝一個IEnumerable 和支持克隆。 – 2010-05-13 20:15:57

+0

'休息= sequence.Skip(2)'會複製你的例子,但並不能一概而論... – jball 2010-05-13 20:16:55

回答

3

如果必須使用IEnumerator<T>而不是IEnumerable<T>(所有良好的擴展方法)這裏有兩個簡單的方法。

這一次只能列舉一個時間(並綁定到原來的枚舉,這意味着你可以用異常結束如果另一個線程更改源列表):

public static IEnumerable<T> Remaining<T>(this IEnumerator<T> value) { 
    while(value.MoveNext()) { 
     yield return value.Current; 
    } 
} 

而這一次建立一個列表,可反覆枚舉(並從原來的枚舉器斷開,所以你不必擔心你的源IEnumerable的變化):

public static IEnumerable<T> Remaining<T>(this IEnumerator<T> value) { 
    List<T> list = new List<T>(); 
    while(value.MoveNext()) list.Add(value.Current); 

    return list; 
} 
2

TakeSkip是要使用這兩種方法:

IEnumerable<int> sequence = ... 
IEnumerable<int> pair = sequence.Take(2); //First two elements 
IEnumerable<int> remaining = sequence.Skip(2); 
+0

我想你錯過了問題的要點... – jball 2010-05-13 20:26:01

+0

我所知道的這些方法,但他們要求你知道你在哪個數字元素。假設我已經通過序列走一部分的方式,並希望所有的休息,我寧願沒有一個計數器來跟蹤我已經走了多遠。 (請參閱@ jball對該問題的評論)。 – 2010-05-13 20:28:36

2

如果你想利用一個IEnumerator<T>,並得到一個IEnumerable<T>代表剩下的順序,從字面上看,你將不得不做一些魔術才能到達那裏。

其原因在於,一般來說,枚舉可以枚舉多次,而枚舉器不能,它只是其中的一個「多次」本身。

首先,您可以嘗試弄清楚您正在處理的是哪種集合,從而在原始枚舉器的其餘部分上返回適當的枚舉器。你要去的原因。

或...您可以將枚舉數的其餘部分緩存到新集合中並返回該集合。這當然會消耗你的原始枚舉器,無論可能是什麼,並且在時間或內存方面可能會很昂貴。

或者......你可以做幾件事提出的建議,不要實際返回枚舉器,而是使用可枚舉類的Skip和Take方法來返回你想要的結果。這將返回一個新的枚舉值,每次枚舉時,它將枚舉原始枚舉值,跳過前兩個項目,併產生其餘項目。

讓我改寫最後一段。如果您不嘗試將其餘的IEnumerator<T>作爲新的枚舉返回,而只是處理原始集合,則處理起來會更容易。

下面是一些緩存元素的代碼。它的好處是,如果你得到的枚舉產生2名或更多普查員(甚至只是1),然後讓枚舉走出去的範圍,作爲普查員開始通過元素移動,它將讓垃圾回收開始收集已通過的元素。

換句話說,如果你這樣做:

var enumerable = enumerator.Remaining(); 
var enumerator1 = enumerable.GetEnumerator(); 
var enumerator2 = enumerable.GetEnumerator(); 

enumerator1.MoveNext(); 
enumerator2.MoveNext(); 
<-- at this point, enumerable is no longer used, and the first (head) element 
    of the enumerable is no longer needed (there's no way to get to it) 
    it can be garbage collected. 

當然,如果你保持枚舉的周圍,並列舉了其中的所有元素,這將產生一個內存中拷貝所有正如我所說,這些元素來自最初的可枚舉元素,其成本可能很高。

無論如何,這是代碼。它不是線程安全的:

using System; 
using System.Collections.Generic; 
using System.Collections; 

namespace SO2829956 
{ 
    public class EnumeratorEnumerable<T> : IEnumerable<T> 
    { 
     private class Node 
     { 
      public T Value; 
      public Node Next; 
     } 

     private class Enumerator : IEnumerator<T> 
     { 
      private IEnumerator<T> _Enumerator; 
      private Node _Current; 

      public Enumerator(IEnumerator<T> enumerator, Node headElement) 
      { 
       _Enumerator = enumerator; 
       _Current = headElement; 
      } 

      public T Current 
      { 
       get { return _Current.Value; } 
      } 

      public void Dispose() 
      { 
       _Enumerator.Dispose(); 
      } 

      object IEnumerator.Current 
      { 
       get { return Current; } 
      } 

      public bool MoveNext() 
      { 
       if (_Current.Next != null) 
       { 
        _Current = _Current.Next; 
        return true; 
       } 
       else if (_Enumerator.MoveNext()) 
       { 
        _Current.Next = new Node 
        { 
         Value = _Enumerator.Current 
        }; 
        _Current = _Current.Next; 
        return true; 
       } 
       else 
       { 
        _Enumerator.Dispose(); 
        return false; 
       } 
      } 

      public void Reset() 
      { 
       throw new NotImplementedException(); 
      } 
     } 

     private IEnumerator<T> _Enumerator; 
     private Node _FirstElement; 

     public EnumeratorEnumerable(IEnumerator<T> enumerator) 
     { 
      _Enumerator = enumerator; 
      _FirstElement = new Node 
      { 
       Next = null, 
       Value = enumerator.Current 
      }; 
     } 

     public IEnumerator<T> GetEnumerator() 
     { 
      return new Enumerator(_Enumerator, _FirstElement); 
     } 

     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return GetEnumerator(); 
     } 
    } 

    public static class EnumeratorExtensions 
    { 
     public static IEnumerable<T> Remaining<T>(
      this IEnumerator<T> enumerator) 
     { 
      return new EnumeratorEnumerable<T>(enumerator); 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      List<int> values = new List<int> { 1, 2, 3, 4, 5 }; 
      IEnumerator<int> enumerator = values.GetEnumerator(); 
      enumerator.MoveNext(); 
      enumerator.MoveNext(); 

      var enumerable = enumerator.Remaining(); 
      foreach (var i in enumerable) 
       Console.Out.WriteLine(i); 
      foreach (var i in enumerable) 
       Console.Out.WriteLine(i); 
     } 
    } 
} 

運行這個程序的輸出是:

3 
4 
5 
3 
4 
5 
0

如果你的目標是能夠在一個IEnumerator<T>直接使用foreach,我建議像此:

public struct WrappedEnumerator<T> 
{ 
    T myEnumerator; 
    public T GetEnumerator() { return myEnumerator; } 
    public WrappedEnumerator(T theEnumerator) { myEnumerator = theEnumerator; } 
} 
public static class AsForEachHelper 
{ 
    static public WrappedEnumerator<IEnumerator<T>> AsForEach<T>(this IEnumerator<T> theEnumerator) 
     { return new WrappedEnumerator<IEnumerator<T>>(theEnumerator);} 

    static public WrappedEnumerator<System.Collections.IEnumerator> AsForEach(this System.Collections.IEnumerator theEnumerator) 
     { return new WrappedEnumerator<System.Collections.IEnumerator>(theEnumerator); } 

    [Obsolete("Structs implementing IEnumerator<T> should be boxed before use", false)] 
    static public WrappedEnumerator<System.Collections.IEnumerator> AsForEach<T>(this T theEnumerator) where T : struct, System.Collections.IEnumerator 
    { return new WrappedEnumerator<System.Collections.IEnumerator>(theEnumerator) ; } 
} 

foo如果是IEnumerator類型,IEnumerator<T>的變量,或任何從任一派生類型,可以簡單地說foreach (whatever in foo.AsForEach());如果循環提前退出,任何未讀取的項目都將保留在枚舉器中。然而請注意,像myEnumerator Foo=someList.GetEnumerator()這樣的語句,其中someListList<T>將會將myEnumerator定義爲結構類型,這與WrappedEnumerator<T>方法不兼容。如果真的很勇敢,可以刪除Obsolete標籤(或將其參數更改爲false),以允許將AsForEach與未裝箱的枚舉器結合使用,但應該注意,在結構類型枚舉器上調用AsForEach可能會獲取枚舉快照狀態,並枚舉該快照可能不會影響原始狀態。

相關問題