I think the most common interface for accessing a set of elements is IEnumerable<T> and IEnumerator<T> . Almost all, if not all, containers support it. This pattern is good because it allows access to absolutely any set regardless of its internal storage structure. For example, all T [] arrays, containers from System.Collections.Generic , methods with yield return , etc.

1) However, I was faced with the need to process some sequence of elements with the possibility of going back. Those. for processing the Nth element, it may be necessary to obtain N + m elements. In general, the solution to this problem is to clone the main iterator on the Nth element. The clone is then used only to run up to the N + m element. Then the main iterator continues to work with the Nth on which it stopped. This solution is good because it is very easy to implement. To do this, simply implement the Clone method of the ICloneable interface. Usually this is only 1 line:

 public class MyEnumerator<T> : IEnumerator<T>, ICloneable { // Π’ΠΎ Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½ΠΈΡ… полях ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ хранится ΡƒΠΊΠ°Π·Π°Ρ‚Π΅Π»ΡŒ Π½Π° ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€ ΠΈ индСкс Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ элСмСнта Π² Π½Π΅ΠΌ, // Π»ΠΈΠ±ΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚Π΅Π»ΡŒ Π½Π° элСмСнт связанной структуры. // РСализация ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² IEnumerable<T> ... public object Clone() { // ΠŸΡ€ΠΎΡΡ‚ΠΎΠ΅ повСрхностноС ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ нСмногочислСнных Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½ΠΈΡ… ΠΏΠΎΠ»Π΅ΠΉ return MemberwiseClone(); } } 

As an example, the System.CharEnumerator class is described with its use in MSDN and the cloning script.

This solution is very simple to implement, universally and economically. Nevertheless, only 2 cases of its application were found in the vskidku - enumerator in the string (the aforementioned System.CharEnumerator ) and non-typed enumerator in the array:

 static void Main(string[] args) { { // Π‘Ρ‚Ρ€ΠΎΠΊΠ° ΠΈΠΌΠ΅Π΅Ρ‚ Π΅Π΄ΠΈΠ½Ρ‹ΠΉ System.CharEnumerator, ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‰ΠΈΠΉ ΠΊΠ»ΠΎΠ½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ string str = "123"; IEnumerator<char> enumerator = ((IEnumerable<char>)str).GetEnumerator(); // System.CharEnumerator ICloneable cloneable = (ICloneable)enumerator; // System.CharEnumerator IEnumerator<char> enumerator2 = (IEnumerator<char>)cloneable.Clone(); // System.CharEnumerator } { // IEnumerator<T> Π² массивах ΠΊΠ»ΠΎΠ½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π½Π΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ char[] arr = new char[3]; IEnumerator<char> enumerator = ((IEnumerable<char>)arr).GetEnumerator(); // System.SZArrayHelper.SZGenericArrayEnumerator<char> //ICloneable cloneable = (ICloneable)enumerator; // System.InvalidCastException } { // IEnumerator Π² массивах ΠΊΠ»ΠΎΠ½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ char[] arr = new char[3]; IEnumerator enumerator = ((IEnumerable)arr).GetEnumerator(); // System.Array.SZArrayEnumerator ICloneable cloneable = (ICloneable)enumerator; // System.Array.SZArrayEnumerator IEnumerator enumerator2 = (IEnumerator)cloneable.Clone(); // System.Array.SZArrayEnumerator } { // IEnumerator ΠΎΡ‚ List<T> ΠΊΠ»ΠΎΠ½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π½Π΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ List<char> lst = new List<char>(); IEnumerator enumerator = ((IEnumerable)lst).GetEnumerator(); // System.Collections.Generic.List<char>.Enumerator //ICloneable cloneable = (ICloneable)enumerator; // System.InvalidCastException } { // IEnumerator<T> ΠΎΡ‚ List<T> ΠΊΠ»ΠΎΠ½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π½Π΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ List<char> lst = new List<char>(); IEnumerator<char> enumerator = ((IEnumerable<char>)lst).GetEnumerator(); // System.Collections.Generic.List<char>.Enumerator //ICloneable cloneable = (ICloneable)enumerator; // System.InvalidCastException } { // IEnumerator<T> Π² yield return ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ ΠΊΠ»ΠΎΠ½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π½Π΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ IEnumerator<char> enumerator = ((IEnumerable<char>)YieldReturnMethod()).GetEnumerator(); // tmp.Program.<YieldReturnMethod>d__1 //ICloneable cloneable = (ICloneable)enumerator; // System.InvalidCastException } { // IEnumerator Π² yield return ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ ΠΊΠ»ΠΎΠ½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π½Π΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ IEnumerator enumerator = ((IEnumerable)YieldReturnMethod()).GetEnumerator(); // tmp.Program.<YieldReturnMethod>d__1 //ICloneable cloneable = (ICloneable)enumerator; // System.InvalidCastException } } static IEnumerable<char> YieldReturnMethod() { yield return 'a'; yield return 'b'; yield return 'c'; } 

The fact of such a small ICloneable support is puzzling. It turns out that almost for everything you have to write your own class that implements IEnumerable<T> and the 2nd class that implements IEnumerator<T> . Probably the most common option would be to pre-retrieve the entire sequence, store it in its container and then use its own set of classes that implement the necessary interfaces. However, this option in some cases may be either inapplicable or very resource-intensive. I would like to ask who thinks about this.

  • one
    Regarding the second part of the question, give a concrete example better. - VladD pm
  • one
    And even better - issue the second part of the question with a separate question - PashaPash ♦

1 answer 1

The fact is that usually enumerator cloning is meaningless.

For example, if your data comes from the network, there is no point in cloning an enumerator: you cannot go through the data two times without caching it (and this is obviously a large and unnecessary memory waste).

If your sequence is obtained using a generator (with a yield return function), you will not be able to clone the data either. Worse, the sequence may change during the second run (for example, if the output is dependent on the outside world).

The case when the sequence is known in advance, materialized and fixed, is known. In this case, you have an IList<T> interface in which you can simply remember the current index.


If you know how much you need to look ahead, it is easy to write a wrapper.

 class LookaheadEnumerable<T> : IEnumerable<T> { readonly IEnumerable<T> wrapped; readonly int maxLookaheadSize; public LookaheadEnumerable(IEnumerable<T> wrapped, int maxLookaheadSize) { this.wrapped = wrapped; this.maxLookaheadSize = maxLookaheadSize; } public LookaheadEnumerator<T> GetEnumerator() => new LookaheadEnumerator<T>(wrapped.GetEnumerator(), maxLookaheadSize); IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } class LookaheadEnumerator<T> : IEnumerator<T>, IEnumerator { List<T> lookaheadBuffer; readonly int maxLookaheadSize; readonly IEnumerator<T> wrapped; public LookaheadEnumerator(IEnumerator<T> wrapped, int maxLookaheadSize) { if (maxLookaheadSize < 1) throw new ArgumentException("expect positive lookahead size"); this.maxLookaheadSize = maxLookaheadSize; this.wrapped = wrapped; } public T Current => lookaheadBuffer[0]; object IEnumerator.Current => Current; public void Dispose() => wrapped.Dispose(); public bool MoveNext() { if (lookaheadBuffer == null) lookaheadBuffer = new List<T>(maxLookaheadSize); else if (lookaheadBuffer.Count > 0) lookaheadBuffer.RemoveAt(0); while (lookaheadBuffer.Count < maxLookaheadSize) { if (!wrapped.MoveNext()) break; lookaheadBuffer.Add(wrapped.Current); } return lookaheadBuffer.Count > 0; } public void Reset() { wrapped.Reset(); lookaheadBuffer = null; } public bool Lookahead(int idx, out T t) { bool good = idx >= 0 && idx < lookaheadBuffer.Count; t = good ? lookaheadBuffer[idx] : default(T); return good; } } 

Example of use:

 var seq = new LookaheadEnumerable<int>(Enumerable.Range(0, 4), 7); using (var en = seq.GetEnumerator()) while (en.MoveNext()) { var value = en.Current; int lookehead; if (en.Lookahead(2, out lookehead)) Console.WriteLine($"value: {value}, lookahead + 2: {lookehead}"); else Console.WriteLine($"value: {value}, no lookahead"); } 
  • Comments are not intended for extended discussion; conversation moved to chat . - Nick Volynkin ♦