Laden...

Convert- und FindEnumerator

Erstellt von progger vor 17 Jahren Letzter Beitrag vor 17 Jahren 4.247 Views
progger Themenstarter:in
1.271 Beiträge seit 2005
vor 17 Jahren
Convert- und FindEnumerator

Hallo,

Vor einiger Zeit habe ich mir zwei nützliche Enumeratoren (Iteratoren) erstellt. Ich persönlich verwende sie oft und gerne und hoffe, dass sie auch für einige von euch hilfreich sein werden.
Beide Enumeratoren sind generisch und brauchen damit .NET 2.0. Außerdem implementieren sie das IEnumerable<T>-Interface, wodurch man sie per foreach-Schleife durchlaufen kann (das hab ich mir bei herbivores Iteratoren, siehe Hilfreiche Iteratoren / Improving Foreach, abgeschaut).

ConvertEnumerator
Eine praktische Methode in List<T> ist ConvertAll<TOutput>: Man gibt ein Converter-Delegat an, mit dessen Hilfe alle Elemente der Liste des Typs T in den Typ TOutput konvertiert und in einer neuen Liste zurückgegeben werden. Für Arrays kann man die statische Methode Array.ConvertAll<TInput, TOutput> verwenden (das verwendet auch die List<T> zum konvertieren des internen Arrays!).
Das Problem dabei: Wenn man die Ergebnis-Liste danach mit einer Schleife durchlaufen möchte, hat man insgesamt zwei Schleifendurchläufe: eine zum Konvertieren und der Durchlauf des Ergebnisses.
Jetzt könnte man das Konvertieren natürlich "händisch" machen ... oder man benutzt den ConvertEnumerator: Man übergibt eine Liste, die konvertiert werden soll, (muss IEnumerable<TInput> implementieren) und einen Convertert (z.B. eine anonyme Methode). Wenn man sich nun vorwärts bewegt, läuft der Enumerator in der Input-Liste mit und konvertiert immer nur das aktuelle Element.
Die Bedienung sollte eigentlich selbsterklärend bzw. jedem klar sein. Hier ist der Code:

/// <summary>
/// This class iterates through a collection of elements of type TInput and converts the current
/// element with a specified <see cref="T:Converter´2"/> delegate to type TOutput. This class
/// implements the <see cref="T:IEnumerable´1"/> interface, so you can iterate over the converted
/// elements with a foreach loop.
/// </summary>
/// <typeparam name="TInput">The type of the input.</typeparam>
/// <typeparam name="TOutput">The type of the output.</typeparam>
public class ConvertEnumerator<TInput, TOutput> : IEnumerator<TOutput>, IEnumerable<TOutput> {
IEnumerator<TInput> _inputEnumerator;
Converter<TInput, TOutput> _converter;
TOutput _current;

/// <summary>
/// Creates a new enumerator with the specified elements and converter.
/// </summary>
/// <param name="input"></param>
/// <param name="converter">A <see cref="T:Converter´2"/> delegate that converts each element from one type
/// to another type.</param>
public ConvertEnumerator(IEnumerable<TInput> input, Converter<TInput, TOutput> converter) {
_inputEnumerator = input.GetEnumerator();
_converter = converter;
}

/// <summary>
/// Gets the current converted element.
/// </summary>
public TOutput Current {
get { return _current; }
}

/// <summary>
/// Gets the current converted element.
/// </summary>
object System.Collections.IEnumerator.Current {
get { return Current; }
}

/// <summary>
/// Releases all resources used by the enumerator.
/// </summary>
public void Dispose() {
_inputEnumerator.Dispose();
_current = default(TOutput);
}

/// <summary>
/// Advances the enumerator to the next element of the input collection.
/// </summary>
/// <returns>true if the enumerator was succesfully advanced to the next element; false if the enumerator
/// has passed the end of the collection.</returns>
public bool MoveNext() {
if (_inputEnumerator.MoveNext()) {
_current = _converter(_inputEnumerator.Current);
return true;
}
_current = default(TOutput);
return false;
}

/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
public void Reset() {
_inputEnumerator.Reset();
_current = default(TOutput);
}

/// <summary>
/// Returns an enumerator that iterates over the converted elements.
/// </summary>
/// <returns></returns>
public IEnumerator<TOutput> GetEnumerator() {
return this;
}

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

FindEnumerator
Ene weitere praktische Methode von List<T> ist Find (und die Verwandten FindAll, FindLast und Find(Last)Index).
Wenn man nun wieder alle Elemente, die die angegebene Bedingung erfüllen, durchlaufen möchte, sind es insgesamt wieder zwei Schleifendurchläufe. Die "händische" Möglichkeit alles in einem Durchöauf zu erledigen ist wieder etwas komplizierter.
Dafür gibt es den FindEnumerator: Man übergibt eine Liste und eine Bedingung in Form eines Predicate-Delegats. Wenn man den Enumerator vorwärts bewegt, sucht er in der Liste das nächste Element, das die BEdingung erfüllt, und gibt dieses als aktuelles Element aus. Hier ist der Code:

/// <summary>
/// This class iterates through a collection of elements of type T matching a specified 
/// <see cref="T:Predicate´1"/>. This class implements the <see cref="T:IEnumerable´1"/> interface, 
/// so you can iterate over the converted elements with a foreach loop.
/// </summary>
/// <typeparam name="T">The type of the elements.</typeparam>
public class FindEnumerator<T> : IEnumerator<T>, IEnumerable<T> {
    IEnumerator<T> _inputEnumerator;
    Predicate<T> _match;
    T _current;

    /// <summary>
    /// Creates a new enumerator with the specified elements and predicate.
    /// </summary>
    /// <param name="input">The elements to iterate through.</param>
    /// <param name="match">The predicate indicating how an element should be matched.</param>
    public FindEnumerator(IEnumerable<T> input, Predicate<T> match) {
        _inputEnumerator = input.GetEnumerator();
        _match = match;
    }

    /// <summary>
    /// Gets the current found element.
    /// </summary>
    public T Current {
        get { return _current; }
    }

    /// <summary>
    /// Gets the current found element.
    /// </summary>
    object System.Collections.IEnumerator.Current {
        get { return Current; }
    }

    /// <summary>
    /// Releases all resources used by the enumerator.
    /// </summary>
    public void Dispose() {
        _inputEnumerator.Dispose();
        _current = default(T);
    }

    /// <summary>
    /// Advances the enumerator to the next element of the input collection that matches the specified 
    /// predicate.
    /// </summary>
    /// <returns>true if the enumerator was succesfully advanced to the next element; false if the enumerator 
    /// has passed all matching elements.</returns>
    public bool MoveNext() {
        while (_inputEnumerator.MoveNext()) {
            if (_match(_inputEnumerator.Current)) {
                _current = _inputEnumerator.Current;
            }
        }
        _current = default(T);
        return false;
    }

    /// <summary>
    /// Sets the enumerator to its initial position, which is before the first element in the collection.
    /// </summary>
    public void Reset() {
        _inputEnumerator.Reset();
        _current = default(T);
    }

    /// <summary>
    /// Returns an enumerator that iterates over the found elements.
    /// </summary>
    /// <returns></returns>
    public IEnumerator<T> GetEnumerator() {
        return this;
    }

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

Falls es irgendwelche Fragen dazu gibt, beantworte ich diese gerne. Viel Spaß damit!

Gruß,
progger

A wise man can learn more from a foolish question than a fool can learn from a wise answer!
Bruce Lee

Populanten von Domizilen mit fragiler, transparenter Außenstruktur sollten sich von der Translation von gegen Deformierung resistenter Materie distanzieren!
Wer im Glashaus sitzt, sollte nicht mit Steinen werfen.

347 Beiträge seit 2006
vor 17 Jahren

Hui, machst du dir das umständlich! 😉

Schaue mal hier ein "sinnbefreites" ( 😁 ) Beispiel für das Umwandeln:

public static class Iterator
{
  public static IEnumerable<TOutput> ConvertAll<TInput, TOutput>(IEnumerable<TInput> items,
                                                                 Converter<TInput, TOutput> converter)
  {
    foreach (TInput item in items)
    {
      yield return converter(item);
    }
  }
}


class Program
{
  static void Main(string[] args)
  {
    List<int> integers = new List<int>(5);
    integers.Add(1);
    integers.Add(2);
    integers.Add(3);
    integers.Add(4);
    integers.Add(5);

    Converter<int, string> toString = delegate(int item)
    {
      return item.ToString();
    };

    List<string> strings = new List<string>(Iterator.ConvertAll<int, string>(integers,
                                                                             toString));

  }
}
progger Themenstarter:in
1.271 Beiträge seit 2005
vor 17 Jahren

Du hast recht 😁
Aber warum einfach, wenn es umständlich auch geht 😉

A wise man can learn more from a foolish question than a fool can learn from a wise answer!
Bruce Lee

Populanten von Domizilen mit fragiler, transparenter Außenstruktur sollten sich von der Translation von gegen Deformierung resistenter Materie distanzieren!
Wer im Glashaus sitzt, sollte nicht mit Steinen werfen.

1.274 Beiträge seit 2005
vor 17 Jahren

Und für alle die mit yield genausowenig anfagnen können wie ich siehe OpenBook

"Das Problem kennen ist wichtiger, als die Lösung zu finden, denn die genaue Darstellung des Problems führt automatisch zur richtigen Lösung." Albert Einstein

347 Beiträge seit 2006
vor 17 Jahren

Original von LastGentleman
Und für alle die mit yield genausowenig anfagnen können wie ich siehe
>
Es gibt auch die gute alte Zaubertaste: F1. 😉

1.274 Beiträge seit 2005
vor 17 Jahren

Da steht mehr als auf der MSDN Seite .

Es stehen auch mehr Informationen drinnen.

"Das Problem kennen ist wichtiger, als die Lösung zu finden, denn die genaue Darstellung des Problems führt automatisch zur richtigen Lösung." Albert Einstein