[C#] Implementacja GetEnumerator dla kolekcji generycznych

0

Stworzyłem klasę implementującą po IList<MojaKlasa>.

Aby zaimplementować GetEnumerator chciałem intuicyjnie użyć kodu:

public IEnumerator<MojaKlasa> GetEnumerator()
        {
            return list.GetEnumerator();//  list jest typu List<MojaKlasa>
        }

Niestety kompilator wyrzucił błąd o braku impletacji.
Na forachn wyczytałe, że aby zaimplementować ową metodę, muszę użyć kodu:

 IEnumerator<MojaKlasa> IEnumerable<MojaKlasa>.GetEnumerator()
        {
            return list.GetEnumerator();
        }

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

Dlaczego wymagana jest taka implementacja?
Dlaczego Metoda IEnumerator<Module> IEnumerable<Module>.GetEnumerator()?
Będę wdzięczny, jeżeli ktoś poświęci trochę czasu aby mnie uświadomić.

Pozdrawiam
Ciekawski

0

Kolejna kwestia.

public class Atributes
    {
        private int id;
        private string name;

        public int Id
        {
            get { return id; }
            set { id = value; }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public Atributes(int id, string name)
        {
            this.id = id;
            this.name = name;
        }
    }

    public class ClassCollection
    {
        private List<Atributes> atribs;
        public Atributes this[int index]
        {
            get { return atribs[index]; }
            set { atribs[index] = value; }
        }
        public void Add(Atributes atr)
        {
            atribs.Add(atr);
        }
        public int Count
        {
            get { return atribs.Count; }
        }
        public void Add(int id, string name)
        {
            Atributes myAtr = new Atributes(id, name);
            atribs.Add(myAtr);
        }
    }

    public class Module
    {
        private ClassCollection coll;
        public ClassCollection Classes
        {
            get { return coll; }
            set { coll = value; }
        }
        public Module(string name)
        {
            this.name = name;
        }
        private string name;
        public string ModuleName
        {
            get { return name; }
        }
    }

    public class ModuleCollection : IList<Module>
    {
        private List<Module> list;
        public Module this[int index]
        {
            get { return list[index]; }
            set { list[index] = value; }
        }
        public ModuleCollection()
        {
            list = new List<Module>();
        }
        public void Add(Module module)
        {
            list.Add(module);
        }
        public int Count
        {
            get { return list.Count; }
        }

        //Implementing Ilist
        public bool Contains(Module myValue)
        {
            return list.Contains(myValue);
        }
        IEnumerator<Module> IEnumerable<Module>.GetEnumerator()
        {
            return list.GetEnumerator();
        }

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

        public int IndexOf(Module obj)
        {
            return list.IndexOf(obj);
        }
        public void Insert(int index, Module obj)
        {
            list.Insert(index, obj);
        }
        public bool IsFixedSize
        {
            get { return true;}
        }
        public bool IsReadOnly
        {
            get { return false; }
        }
        public void RemoveAt(int index)
        {
            list.RemoveAt(index);
        }
        public bool Remove(Module obj)
        {
            return list.Remove(obj);
        }
        public bool IsSynchronized
        {
            get{return false; }
        }
        public object SyncRoot
        {
            get{ return this;}
        }
        public void CopyTo(Module[] array, int index)
        {
            list.CopyTo(array, index);
        }
        public void Clear()
        {
            list.Clear();
        }

Zaimplementowałem IList, aby móc używać właściwości DataSource dla comboboxa i obiektów kolekcji.

 mc = new ModuleCollection();
            mc.Add(new Module("ala"));
            mc.Add(new Module("kot"));
            comboBox1.DataSource = mc;
            comboBox1.DisplayMember = ???<code>

W jaki sposób przystosować klasę ModuleCollection, aby jej obiekty (dowolna włąściwość) mogły byc pokazywane w kontrolce ComboBox za pomocą jej składowej. Np ModuleName.
A więc co wstawić za znaki zapytania, aby kontrolka pokazywała ModuleName dla obiektów należących do kolekcji ModuleCollection?
0
ciekawski napisał(a)

Na forachn wyczytałe, że aby zaimplementować ową metodę, muszę użyć kodu:

 IEnumerator<MojaKlasa> IEnumerable<MojaKlasa>.GetEnumerator()
        {
            return list.GetEnumerator();
        }

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

Dlaczego wymagana jest taka implementacja?

Dlatego że są dwie klasy: IEnumerable oraz IEnumerable<T>. Ta druga dziedziczy po pierwszej, więc jeśli my dziedziczymy po IEnumerable<T>, to pośrednio dziedziczymy po IEnumerable.
Oba interfejsy posiadają metodę GetEnumerator(). Jest to jednak nie ta sama metoda: jedna zwraca IEnumerator, a druga IEnumerator<T>. A pokryć musimy obie.
Więc musimy mieć w klasie dwie metody o tej samej nazwie, z tymi samymi (żadnymi) parametrami, a różniące się tylko zwracanym typem. Na coś takiego kompilator nie pozwala: metody muszą się różnić albo nazwą, albo parametrami. Sam typ zwracany nie wystarcza.
Rozróżnienie ich poprzez dodanie nazwy interfejsu rozwiązuje problem.

public class Atributes

To można zapisać dużo prościej:

public class Attributes
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Attributes(int id, string name)
        {
            this.Id = id;
            this.Name = name;
        }
    }

podobnie możesz

            atribs.Add(new Attributes(id, name));

bez tworzenia tymczasowej zmiennej.

Po co jest u ciebie ClassCollection? Chyba zbyt się rozdrabniasz, to jest zbędny poziom IMHO. Już Module powinno zawierać listę Attributes.

PS. koloryzację C# na tym forum uzyskasz <‌code=csharp‌>w ten sposób<‌/code‌>.

0

Dzięki za info.
Hmm, wymagany jest taki poziom rozdrobnienia, ponieważ potrzebuję uzyskać kolekcje coś mniej więcej jak dataColumn, DataColiumnCollection, DataRow i DataRowCollection. Nie użyje ich w programie, ponieważ nie chcę dostarczać zbędnych składowych do głownego programu (kiedyś miałem DataTable&DataRow'y). Klasa ModulesCollection zawiera kolekcje modułów, które z kolei zawierają kolekcję obiektów klasy Atributes.

Dzięki za kolorowanie składni, przyda się. :)
A dlaczego nie można zadeklarować metody GetEnumerator() z modyfikatorem dostępu public?
Np w przypadku implementowania Intrerfejsu IList, wymagane jest już implementowanie jednej metody i można użyć public.

A jak rozwiązać problem z DataSource i DisplayMember?

1

A dlaczego nie można zadeklarować metody GetEnumerator() z modyfikatorem dostępu public?

Nie tylko można ale i trzeba. Inaczej interfejs nie uzna jej za "swoją" i zgłosi wyjątek. Jeśli się domyślam o co chodzi, to żeby użyć tej metody musisz napisać

MojaEnumerableKlasa klasa = new MojaEnumerableKlasa();
var cośtam = (IEnumerable())klasa.GetEnumerator();

gdyż metoda jest w pewnym sensie implementowana jako część interfejsu a nie klasy.

0
ciekawski napisał(a)

A dlaczego nie można zadeklarować metody GetEnumerator() z modyfikatorem dostępu public?

Uznaj to za osobliwość C#, bo nie ma na to logicznego wytłumaczenia. Ot, dziwactwo takie, wynikające z tego, że metody interfejsów są publiczne, ale nie wolno ich oznaczyć jako publiczne. Pretensje do twórców ;-)

MSM napisał(a)

Nie tylko można ale i trzeba.

Nie masz racji.

0

Wyjasnienie jest bardzo logiczne. Metody interfejsow sa publiczne i tylko publiczne. Zatem pisanie public byloby powtarzaniem oczywistosci, a do tego wprowadzaloby niektorych blad, bo public nie jest jedynym modyfikatorem dostepu. Wtedy bylyby pytania dlaczego public, a nie protected, private, itp. Jednoznaczny jest brak modyfikatora, jesli mozliwosc jest tylko jedna. Mniej jednoznaczne jest okreslenie, ze modyfikator ma wystapic, ale tylko jeden z czterech mozliwych.

1

@Azarien - Nie zrozumiałem autora (edit: a właściwie nie doczytałem że chodzi o deklarowanie a nie implementacje, mea culpa). Chodziło mi w przybliżeniu o to co johny napisał - czyli implementowanej metodzie z interfejsu nie można przypisać innego modyfikatora niż public

0
johny_bravo napisał(a)

Wyjasnienie jest bardzo logiczne. Metody interfejsow sa publiczne i tylko publiczne. Zatem pisanie public byloby powtarzaniem oczywistosci, a do tego wprowadzaloby niektorych blad, bo public nie jest jedynym modyfikatorem dostepu. Wtedy bylyby pytania dlaczego public, a nie protected, private, itp. Jednoznaczny jest brak modyfikatora, jesli mozliwosc jest tylko jedna. Mniej jednoznaczne jest okreslenie, ze modyfikator ma wystapic, ale tylko jeden z czterech mozliwych.

Hmm, rozumiem.
Przy implementacji innych metod z modyfikatorem dostępu public kompilator nie zgłasza spzreciwu. Czym to jest spowodowane? Specyficzną cechą, że GetEnumerator musi być zaimplementowany w 2 wersjach? generycznej i niegenerycznej?

0
MSM napisał(a)

@Azarien - Nie zrozumiałem autora (edit: a właściwie nie doczytałem że chodzi o deklarowanie a nie implementacje, mea culpa). Chodziło mi w przybliżeniu o to co johny napisał - czyli implementowanej metodzie z interfejsu nie można przypisać innego modyfikatora niż public

Implementując interfejs IList definuję implementowane metody, więc nie chodziło mi o deklarację.

0
elviss napisał(a)

Specyficzną cechą, że GetEnumerator musi być zaimplementowany w 2 wersjach? generycznej i niegenerycznej?

Nie musi. Po prostu IEnumerator<T> implementuje IEnumerator, przez co zawiera dwie metody o tej samej nazwie, więc jedna z nich musi być zaimplementowana "explicity".
Najlepiej zlecić wygenerowanie metod interfejsu przez Visual Studio, wtedy nie trzeba się zastanawiać, tylko uzupełnić kod. :)

0
Azarien napisał(a)
ciekawski napisał(a)

A dlaczego nie można zadeklarować metody GetEnumerator() z modyfikatorem dostępu public?

Uznaj to za osobliwość C#, bo nie ma na to logicznego wytłumaczenia. Ot, dziwactwo takie, wynikające z tego, że metody interfejsów są publiczne, ale nie wolno ich oznaczyć jako publiczne. Pretensje do twórców ;-)

MSM napisał(a)

Nie tylko można ale i trzeba.

Nie masz racji.

1

Właśnie o to chodzi :] Żeby interfejs uznał że jakaś metoda należy do niego musi być publiczna - bo co to za interfejs, skoro inne aplikacje nie mogą wywołać jego składowych. Dzięki za tego screena ;)

0

ok, dzięki.
Nawołując do innego wątku poruszonego w tym temacie, dodam pewną myśl.

Aby móc przypisywać do właściwości DataSource kontrolki zaimplementowałem przez moją klasę reprezentującą kolekcję interfejs IListSource.

Wszystko działa, ale nie wiem co zrobić, kiedy dana lista ulegnie zmianie. Wg testów zawartośc comboboxa nie została odświeżona. Jak to zmienic?

public IList GetList()
{
            BindingList<Module> ble = new BindingList<Module>();

            foreach (Module mi in myList)
            {
                ble.Add(mi);
            }
            return ble;
}

 private void button1_Click(object sender, EventArgs e)
{
            col.Add(new Module("NewText", 1));
}
0

Wyczytałem, że można się pobawić w eventy, ale nie wiem jak to zastosować do owego przykladu.

0

pewnie cos zle robisz - brzmi glupio :D wiem, pokaz wiecej kodu to ci powiemy

czy jest to aplikacja web czy win forms?
co to za zmienna col i jak jest zadeklarowana?
jak podpinasz dane do comboboxa?

0
 public partial class Form1 : Form
    {
        Class1 c2;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            c2 = new Class1();
            c2.Add(new Unit("Apples", 22));
            c2.Add(new Unit("Grapes", 45));
            c2.Add(new Unit("Oranges", 36));
           
            comboBox1.DataSource = c2;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            c2.Add(new Unit("bluberries", 14));
        }
    }

public class Class1:IListSource
    {
        private List<Unit> myList;

        public Class1()
        {
            myList = new List<Unit>();
        }

        public void Add(Unit obj)
        {
            myList.Add(obj);
        }

        public bool ContainsListCollection
        {
            get { return false; }
        }

        public IList GetList()
        {
            return myList.ToArray<Unit>();
        }
   
    }
 
public class Unit
    {
        public Unit(string text, int digit)
        {
            this.text = text;
            this.digit = digit;
        }
        private string text;
        private int digit;
        public string Text
        {
            get{return Text;}
        }
        public int Digit
        {
            get{return digit;}
        }
        public override string ToString()
        {
            return text;
        }
    }

Kiedy po wciśnięciu klawisza dodaję nowy element do mojej listy, zmiany nie są odwzorowane na kontrolce.

0

Zacznijmy od tego, że

Aby móc przypisywać do właściwości DataSource kontrolki zaimplementowałem przez moją klasę reprezentującą kolekcję interfejs IListSource

jest mylące. Obiekt podawany do .DataSource nie musi implementowac IListSource. Na dobrą sprawę, mało co musi implementować. Zwykle posiadanie jakichś property albo wystawianie interfejsu IList jest wystarczające do prostego przerzucenia danych - ostatecznie wiekszosc kontrolek poza .DataSource ma również .DataMember, a gdzie to nie wystarcza to można posłać obiekt BindingSource albo BindingList<TItem>. Sprawa sie komplikuje gdy faktycznie chcesz napisac swoj kontener z jakiegos powodu... i w związku z tym:

trzy słowa o idei IListSource, którego probujesz użyć:
Kontrolka posiadająca "jej.DataSource = twojClass1", w momencie wykrycia ze tenże Twoj obiekt implementuje IListSource przestaje go postrzegac jako sensu stricte DataSource i zaczyna sadzic ze jest on raczej tak jakby 'źródłem źródła danych'. Toteż wywola .GetList tylko raz, to co od niego dostenie zapamięta i odtąd będzie używać owego otrzymanego obiektu listy, przynjamniej dopoki nie zmienisz jej .DataSource kiedy to oczywiscie znowu przeladuej wszystko. I na tym koniec interakcji przez IListSource. Źródło wygenerowało właściwa kolekcje danych i wszyscy są happy. (Możliwe, że właśnie taki efekt chciałes uzyskać, ale sądzę że raczej chciales miec klase reprezentujaca kolekcje i chcialbys żeby obiekty tej klasy funkcjonowaly jako zrodla danych.) Problem polega na tym, ze liczy się, jakie interfejsy implementuje ta OSTATECZNA lista. To ona ma za zadanie powiadamiać, że uległa zmianie. Czasami nazywa się to implementowaniem 'IListChanged', acz to trochę nie tak:) U ciebie jest to IList, czyli totalnie głupia lista. Jesli chcialbys teraz sytuacje naprawic, to ta drobna lista zwracana musialaby byc wymieniona na cos bardziej skomplikowanego, np. na BindingList<Unit> -- i to pewnie natychmiast zadziala i grid bedzie sie odswiezal jak tylko dodasz nowe elementy.

Ale proszę, zanim zaczniesz dalej kombinowac, zapoznaj się z całym artykułem (który defacto czasem ciężko znaleźć, a jest BARDZO pożyteczny):
Wyjaśnienie interfejsów używanych przy DataBindingu w .Net
sądzę, że pomoże on Ci złapać, czego oczekuje się od Twoich klas które maja byc ładnymi źrodłami danych.

wyciąg ze środka, na potwierdzenie:

  • IListSource interface
    A class that implements the IListSource interface enables list-based binding on non-list objects. The GetList method of IListSource is used to return a bindable list from an object that does not inherit from IList. IListSource is used by the DataSet class.

Co w skrócie mówi, że zamieniłeś sobie problem bycia bindowalną listą, na problem dostarczenia bindowalnej listy, czyli samo IListSource na razie mało Ci dało :) [Acz coś dało, bo teraz możesz użyć BindingList<>!]

a osobiście polecam interfejs: IBindingList który jest jednym z najlepszych sposobów (=rozumiany przez najwieszą liczbę kontrolek/komponentów) na powiedzenie innym, że jest się listą która może się zmieniać. Zwróc też uwagę, że do informowania o zmianach we własnościach elementu listy jest INotifyPropertyChanged i to element listy powinien go implementować, nie lista. Lista się nie zmieła - oszczedzasz na przeladowaniu calej listy, ktore odbylo by sie gdyby o tym powiadamiac przez ListChanged.

Jak nazwa wskazuje, wspomniana klasa S.W.F.BindingList<TItem> jest wlasnie domyslna implementacja IBindingList.

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