Implementacja for each własnej kolejcji

0

Korzystając z kodu podanego na wykładzie chciałem zaimplementować własną kolekcję która posiadałaby pętlę for each. Poniżej zamieściłem kod w zasadzie przepisany z wykładu:

using System.Collections;

class Element<T>
{
    public T val;
    public Element<T> next;
}

class ListEnum<T> : IEnumerator
{
    Element<T> lista;
    public ListEnum(Element<T> lista)
    {
        this.lista = lista;
    }

    public bool MoveNext()
    {
        if (this.current == null) this.current = this.lista;
        else this.current = this.current.next;
        return this.current != null;
    }

    public object Current
    {
        get
        {
            return current.val;
        }
    }
    public void Reset()
    {
        this.current = this.lista;
    }
}

class Lista<T> : IEnumerable
{
    Element<T> lista;
   
    public IEnumerator GetEnumerator()
    {
        return new ListEnum<T>(lista);
    }
}

Oczywiście jest to tylko wycinek kodu, ale już tutaj zwraca mi w sumie 7 błedów następującej treści:

Error 1
'ListEnum<T>' does not contain a definition for 'current' and no extension method 'current' accepting a first argument of type 'ListEnum<T>' could be found (are you missing a using directive or an assembly reference?)

No ok, nie mam zdefiniowanego pola current, ale z tego co wyczytałem to jest to właściwość tego interfejsu - dlaczego jej nie widać? Czy ktoś może wie jak to poprawić aby wszystko zadziałało? Ja już szczerze mówiąc mam dość szukania błędów w slajdach wykładowcy, więc byłbym wdzięczny gdyby ktoś umiał pomóc.

0

ale z tego co wyczytałem to jest to właściwość tego interfejsu - dlaczego jej nie widać?
interfejs to tylko "sugestia", co powinieneś zaimplementować w klasie. sam nic nie wnosi, jest pusty.

2

Korzystając z kodu podanego na wykładzie chciałem zaimplementować własną kolekcję która posiadałaby pętlę for each. Poniżej zamieściłem kod w zasadzie przepisany z wykładu:

Nie masz nigdzie pola current - jeśli go zdefiniujesz i przy tworzeniu enumeratora będziesz go inicjalizował na początek (głowę) listy, to na oko kod powinien działać.

A tak swoją drogą to strasznie starożytny ten kod.
Pomijając już że nazwy pól publicznych się konwencjonalnie zaczyna dużymi literami (val, next) (można też w ogóle nie używać pól publicznych tylko właściwości), mieszasz polskie i angielskie nazwy itp.

  1. Enumerable<T>, Enumerator<T>, od C# 2.0 - korzystasz z typów generycznych, ale iteratory masz stare. Jest to trochę zamotane bo z powodu kompatybilności wstecznej trzeba dwa interfejsy implementować, ale można tak:
class ListEnum<T> : IEnumerator<T> {
    Element<T> lista, current;
    public ListEnum(Element<T> lista) {
        this.lista = lista;
    }

    public bool MoveNext() {
        if (this.current == null)
            this.current = this.lista;
        else
            this.current = this.current.next;
        return this.current != null;
    }

    object IEnumerator.Current {
        get { return Current; }
    }
    public void Reset() {
        this.current = this.lista;
    }

    public T Current {
        get { return current.val; }
    }

    public void Dispose() { }
}

class Lista<T> : IEnumerable<T> {
    Element<T> lista;

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

    public IEnumerator<T> GetEnumerator() {
        return new ListEnum<T>(lista);
    }
}

Ale od C# 3.0 można to zrobić lepiej w 99% przypadków w taki sposób:

class Lista<T> : IEnumerable<T> {
    Element<T> lista;

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

    public IEnumerator<T> GetEnumerator() {
        Element<T> current = lista;
        while (current != null) {
            yield return current.val;
            current = current.next;
        }
    }
}

Cała implementacja foreach (tzn. IEnumerable), pokaż wykładowcy a pewnie będzie zachwycony ;)

0
msm napisał(a):

Ale od C# 3.0 można to zrobić lepiej w 99% przypadków w taki sposób:

class Lista<T> : IEnumerable<T> {
    Element<T> lista;

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

    public IEnumerator<T> GetEnumerator() {
        Element<T> current = lista;
        while (current != null) {
            yield return current.val;
            current = current.next;
        }
    }
}

Fajna sprawa, ogólnie zadziałało. Tylko, że niestety, kod jest dla mnie jednak niezrozumiały a dokumentacja na te temat bardzo średnia - mógłbym więc poprosić o wytłumaczenie tych kilku linijek?

0

mógłbym więc poprosić o wytłumaczenie tych kilku linijek?

uwaga, kłamię, ale to dla dobra dydaktycznego:

yield return nie wychodzi z funkcji, ale "wypluwa" jeden element do wynikowej kolekcji, którą enumeruje foreach.

Czyli za każdym przebiegiem pętli

        while (current != null) {
            yield return current.val;
            current = current.next;
        }

do foreacha wydostaje się bieżący element current.val.

...a przynajmniej tak to należy rozumieć.
Bo pod maską to trochę inaczej jest zbudowane: kompilator przekształca ten kod automatycznie na klasę enumeratora, z tymi wszystkimi Current i MoveNext.

1 użytkowników online, w tym zalogowanych: 0, gości: 1