Gastbeitrag: Der Lehrling und DotNET

Ich der Gastschreiber

Erst einmal sollte ich mich vorstellen! Mein Name ist Dominic oder auch unter dem Pseudonym pilipu bekannt. Ich bin Dualer Student für Angewandte Informatik, seit einigen Jahren mit Pawel über das Internet sehr gut bekannt und so wie er arbeite ich mit DotNET. Er hat mich eingeladen hier auf seinem Blog Gastbeiträge zu schreiben und ab diesem Jahr versuche ich dies umzusetzen und den einen oder anderen Beitrag zu tippen, unter dem Gesichtpunkt: “Der Lehrling und DotNET”. So werde ich ich kurze HowTo’s zu verschiedensten Dingen zum Besten geben, die mir in meiner täglichen Arbeit mit DotNET die eine oder andere Stunde Kopfzerbrechen bereitet haben. Heute fange ich mit mit der Implementierung der Schnittstelle IEnumerable<T> an und wie man dies in nur 2 Zeilen bewerkstelligen kann. Dieses Thema hatte ich bereits auf meinem eigenen Blog vor ca. einem halben Jahr behandelt. Da dieser Post auf meinem Blog immer wieder aufgerufen wird, halte ich ihn auch hier für sinnvoll.

IEnumerable<T> in 2 Zeilen implementieren

Warum IEnumerable<T> implementieren? Die Schnittstelle IEnumerable<T> macht es möglich über die Klasse, die diese Schnittstelle implementiert, mit einer foreach-Schleife zu iterieren. Ein Beispiel dafür ist die DotNET-Klasse List<T>. List<T> implementiert auch das nicht generische Interface IEnumerable. Es ähnelt dem IEnumerable<T> Interface, jedoch unterscheidet es sich in einem ganz wichtigen Punkt: Da IEnumerable keine Typsicherheit bietet, ist es nicht möglich, LINQ-Abfragen auf Klassen zu schrieben, die dieses Interface implementieren. Deshalb lasse ich IEnumerable in diesem HowTo auch unbetrachtet und werde es auch nicht weiter erwähnen.

Um zu verstehen wie man IEnumerable<T> in nur 2 Zeilen implementieren kann, muss man verstehen wie dieses Interface tickt und was man tuen muss um es überhaupt zu implementieren. Für das Beispiel habe ich mir die generische Klasse GenericData<T> geschrieben, die ein Proberty enthält, ein Array vom generischen Datentyp TSource. Desweiteren besitzt die Klasse 2 Methoden, die wir implementieren müssen wenn die Klasse die Schnittstelle IEnumerable<T> implementieren möchte.

public class GenericData<TSource> : IEnumerable<TSource>
{
private TSource[] elements;
 
public IEnumerator<TSource> GetEnumerator()
{
     throw new NotImplementedException();
}
 
 
IEnumerator IEnumerable.GetEnumerator()
{
     throw new NotImplementedException();
}
}
Um diese beiden Methoden zu implementieren benötigen wir eine innere Klasse die ich in diesem Beispiel DataEnumerator taufe. Diese Klasse muss das Interface IEnumerator<T> implementieren und dies lässt sich folgendermaßen lösen:
private class DataEnumerator : IEnumerator<TSource>
{
    public GenericData<TSource> internalData;
    public int index = -1;
 
 
    public DataEnumerator(GenericData<TSource> data)
    {
        internalData = data;
    }
 
 
    public TSource Current
    {
        get 
        { 
            return internalData.elements[index]; 
        }
    }
 
 
    public void Dispose()
    {
        this.Reset();
    }
 
 
    object IEnumerator.Current
    {
        get { return this.Current; }
    }
 
 
    public bool MoveNext()
    {
        index++:
        if (index < internalData.elements.Length - 1)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
 
 
    public void Reset()
    {
        this.index = -1;
    }
}
Ist dies erledigt können wir die beiden Methoden der Klasse GenericData folgendermaßen implementieren:
public IEnumerator<TSource> GetEnumerator()
{
    return new DataEnumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}
So implementiert man IEnumerable<T> und man kann prima aus der Implementierung heraus lesen, wie eine foreach-Schleife arbeitet, wenn zu beispielsweise über eine Liste iteriert. Dieses Wissen kann man nun ausnutzen um IEnumerable<T> in nur 2 Zeilen zu implementieren. Hierfür muss einem jedoch das Schlüsselword yield nicht nur geläufig sein, sondern man muss auch verstehen was yield macht.
public class GenericData<TSource> : IEnumerable<TSource>
{
private TSource[] elements;
private int elementsCount = 0;public void Add(TSource item)[...]
public IEnumerator<TSource> GetEnumerator()
{
foreach (TSource element in elements)
{
yield return element;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
So Implementiert man IEnumerable<T> in 2 Zeilen Code. Nun sollte jedem auch klar werden wie yield arbeitet. yield gibt dem Compiler zu verstehen: Hier ist ein Iterationsblock.  Der Compiler übernimmt dann die Arbeit die wir zuvor in die Klasse DataEnumerator gesteckt haben und sorgt für das entsprechende Verhalten, das wir von Listen gewohnt sind. Mit yield lässt sich also ganz schnell und einfach ein iteratives Verhalten erzeugen.