Najlepsza metoda na zdefiniowanie IEnumeable

0

Witam, uczę się języka C#, stanąłem na kolekcjach i poznaje podstawowe interfejsy. Dla treningu postanowiłem zaimplementować wspomniany interfejs, oto mój kod:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class MyList<T> : IEnumerable<T>, IEnumerator<T>
    {
        private List<T> list;
        private int offset = -1;

        public MyList()
        {
            list = new List<T>();
        }

        private MyList(List<T> l)
        {
            list = l;
        }

        public void Add(T item)
        {
            list.Add(item);
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new MyList<T>(list);
        }

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

        public bool MoveNext()
        {
            if (offset + 1 >= list.Count) return false;
            else
            {
                offset++;
                return true;
            }
        }

        public T Current
        {
            get { return list[offset]; }
        }

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

        public void Reset()
        {
            offset = -1;
        }

        public void Dispose() { }

    }

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

            foreach (int i in ints)
            {
                Console.WriteLine(i);
            }

            Console.ReadLine();
        }
    }
}

 

Moje pytanie: Czy dobrze to zrobiłem? Czy tak się robi że klasa kolekcji implementuje zarówno interfejs IEnumerable i IEnumerator i w metodzie GetEnumerator() zwraca nowy obiekt danej klasy? Czy jest jakiś wzorzec jak to powinno się robić? Czy można zrobić to lepiej?

0
CSharper napisał(a):

i w metodzie GetEnumerator() zwraca nowy obiekt danej klasy?

nie, no raczej bez sensu
kolega (klasa) prosi o enumerator żeby szybko przelecieć po elementach zbioru, a Ty mu tworzysz nową listę, kopiujesz do niej elementy i zwracasz enumerator do tej nowej listy (bo to właśnie robisz pisząc new MyList<T>(list))

moim zdaniem IEnumerator ma implementować klasa która może zwrócić czegoś enumerator - w Twoim przypadku możesz zwrócić Enumerator listy, a cała klasa nie musi być iterowalna (IEnumerable)

0

a nie, przepraszam za zamieszanie - wydawało mi się że w konstruktorze tworzysz nową listę na podstawie starej; jednak nie tworzysz w tym przypadku nowej listy czyli wszystko jest w porządku

mimo wszystko klasa iteratora lepiej żeby była oddzielona od obiektu iterowalnego; inaczej wprowadza się zamieszanie i może kusić użycie samego obiektu listy jako iteratora co sprawi problem gdy będziesz chciał korzystać z niego w dwóch miejscach (pozycja tych iteratorów będzie współdzielona)

0

@gsdfgsdg mógłbym prosić o jakieś source pokazujące jak to powinno się robić?

3

Po pierwsze, twoja klasa nie powinna implementować IEnumerator, tylko samo IEnumerable.

Po drugie, jeśli twoja klasa jest tylko wrapperem na List<T>, to możesz pójść na łatwiznę:

        public IEnumerator<T> GetEnumerator()
        {
            return list.GetEnumerator();
        }

No ale chyba nie o to w ćwiczeniu chodziło, bo to za łatwe ;-)
Proponuję ci w ogóle zrezygnować z trzymania danych w List, bo opakowywanie listy trochę nie ma sensu. List<T> trzyma dane w tablicy T[], więc jeśli chcesz poćwiczyć tworzenie kontenera, też użyj tablicy...

Po trzecie, enumerator powinien być osobną klasą, która jako pola przechowuje offset i kontener (mylist):

    class MyEnumerator<T> : IEnumerator<T>
    {
        MyList<T> mylist;
        int offset;
        public MyEnumerator(MyList<T> amylist)
        {
            this.mylist = amylist;
            Reset();
        }

        public bool MoveNext()
        {
            if (offset+1 >= mylist.Count) return false;
            else
            {
                offset++;
                return true;
            }
        }

        public T Current
        {
            get { return mylist.At(offset); }
        }

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

        public void Reset()
        {
            offset = -1;
        }

        public void Dispose() { }
    }

    class MyList<T> : IEnumerable<T>
    {
        private List<T> list;

        public MyList()
        {
            list = new List<T>();
        }

        private MyList(List<T> l)
        {
            list = l;
        }

        public void Add(T item)
        {
            list.Add(item);
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new MyEnumerator<T>(this);
        }

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

        public T At(int index)
        {
            return list[index];
        }

        public int Count
        {
            get { return list.Count; }
        }
       
        public void Dispose() { }

    }

Nie przerabiałem List<T> na tablicę T[].
Musiałem dodać Count i metodę do odczytu danych z kontenera (nazwałem ją At(), można też zdefiniować jako operator).

Po czwarte, większość roboty z generowaniem enumeratora załatwia za nas słowo kluczowe yield:

    class MyList<T> : IEnumerable<T>
    {
        private List<T> list;

        public MyList()
        {
            list = new List<T>();
        }

        private MyList(List<T> l)
        {
            list = l;
        }

        public void Add(T item)
        {
            list.Add(item);
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (int i = 0; i < list.Count; i++)
                yield return list[i];
        }

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

Tadaaam! yield return „wypluwa” jedną wartość do foreacha, w ten sposób w ładnej pętli wypluwamy kolejne wartości. Wewnętrznie kompilator generuje za nas całą klasę enumeratora, z metodami MoveNext, Reset, i tym całym bajzlem.

0

Jak zawsze @Azarien dobrze poradzi i nakieruje początkującego. Szkoda, że nie zalogowani użytkownicy nie mogą dać "łapki", tak więc proszę kogoś innego żeby zrobił to za mnie :) Dziękuje za wytłumaczenie podstaw.

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