Implementácia sortovateľnej silne typovej kolekcie, časť 1

Publikoval Michal Kočí dňa 20.2.2005 o 18:03 v kategórii .Net

Asi snáď každý, mňa nevynímajúc, raz potrebuje pracovať s kolekciou dát. Občas sa síce stane, že táto kolekcia obsahuje objekty rôznych typov, poväčšine sa však jedná o kolekcie typov rovnakých. No a vtedy prichádza na rad rozhodnutie, či použiť nejakú v .Net Frameworku vopred pripravenú kolekciu (Array, ArrayList, HashTable, ...), alebo či si pripraviť nejakú vlastnú silne-typovú kolekciu (strongly typed collection).

V prospech netypových kolekcií hovorí najmä to, že ich môžete hneď začať používať, nemusíte nič pred ich použitím programovať. Napríklad máte triedu pre reprezentáciu zákazníka (Customer) a chcete si udržiavať kolekciu týchto zákazníkov. Rozhodnete sa, že zoznam zákazníkov bude dynamický a preto nepoužijete Array ale ArrayList. Váš kód potom môže vyzerať nasledovne:

ArrayList alZakaznici = new ArrayList();
alZakaznici.Add(new Customer("Janko Mrkvicka", 23));
alZakaznici.Add(new Customer("Peret Rychy", 26));

Ak potom chcete pristúpiť dajme tomu k zákazníkovi, ktorý je v kolekcii ako druhý, musíte si tento druhý prvok späť pretypovať na zákazníka:

Customer custDruhy = (Customer) alZakaznici[1];
custDruhy.Vek += 2;

No a tu je hneď vidno nevýhodu netypových kolekcií. Pred použitím objektu získaného z kolekcie si ho musíte pretypovať späť (v tomto prípade z Object na Customer ). Alebo budete predpokladať, že používate v kolekcii len a len objekty toho istého typu, alebo budete robiť kontroly, či sa tam ozaj nachádza správny typ. Celé to potom vedie jednak k zneprehľadneniu kódu (neustále pretypovanie) a druhak k možnej chybovosti (čo ak sa predsa len v kolekcii vyskytne prvok iného typu).

A to je presne ten moment, kedy je vhodné naprogramovať si silne-typovú kolekciu. Ovšem, pre silne-typovú kolekciu vraví ešte jeden fakt. Ak totiž použijete netypovú kolekciu a jej dáta budete chcieť nabindovať na DataGrid, nebude môcť užívateľ sortovať podľa jednotlivých prístupových členov (vlastností, properties). A toto bol presne dôvod, prečo som sa rozhodol začať používať silne typovú kolekciu, ktorá mi navyše poskytne aj možnosť sortovania dát v momente ich nabindovania na DataGrid.

Ako snáď každý lenivý programátor, prvotná myšlienka smerovala k tomu, či sa niečo použiteľné dá nájsť na webe. Popravde, nič použiteľné som nenašiel. Našiel som zopár článkov, ktoré sa dali použiť na vytvorenie predstavy o tom, čo je silne typová kolekcia aj ako ju naprogramovať, ovšem články mi nepridali nič nové, nič čo by som už nevedel. Navyše, v článkoch som nenašiel toľko potrebnú vec - sortovanie. Takže som sa vybral pomaličkým, ale istým krokom k tomu, že si takú kolekciu naprogramujem sám. Nutno podotknúť, že približne uprostred práce som narazil na template pre CodeSmith, ktorý generuje biznis aj dátovú vrstvu pre databázu a že práve tento templete mi pomohol pri implementácii rozumnej sortovacej metódy (s využitím pomocnej triedy ListItem ale to už trochu predbieham).

S čím treba začať. Začať treba s tým, že žačneme tvoriť triedu dediacu z abstraktnej triedy CollectionBase. Tým však stále nedosiahneme požadovanú schopnosť sortovania. Tú dosiahneme až v momente, keď naša trieda bude implementovať rozhranie IBindingList. Toto rozhranie nás totiž prinúti implementovať metódy ApplySort a RemoveSort, ktoré používa práve DataGrid v momente, keď užívateľ klikne myšou do hlavičky stĺpca s požiadavkou zosortovať si dáta.

Keď sa bližšie pozrieme na CollectionBase, zistíme že implementuje rozhrania IList, ICollection a IEnumerable. Tie isté tri rozhrania implementuje aj rozhranie IBindingList. Takže prvá pozitívna správa znie, že podstatnú časť základne, ktorú budeme musieť implementovať budeme zdielať. Aby v tom nebol hneď od začiatku neporiadok, rozdelil som si všetky metódy a vlastnosti na tie, ktoré implementovať budem a ktoré nie. Implementáciou niektorých by som totiž len pridal na komplexnosti celej triedy a vynaložené úsielie by sa mi s veľkou pravdepodobnosťou (blížiacou sa k 1) nevrátilo. Takže, neimplementovať som sa rozhodol nasledovné tri metódy a jednu vlastnosť:

  • void AddIndex(PropertyDescriptor property)
  • int Find(PropertyDescriptor property, object key)
  • void RemoveIndex(PropertyDescriptor property)
  • bool SupportsSearching

Ostatok, som sa znažil rozdeliť do pre mňa logických skupín a implementovať ich postupne, skupinu za skupinou. Prvá vziknutá skupina bola skupina skupina, ktorá mi logicky dávala celok na ovládanie kolekcie, teda prídavanie nových prvkov, odstraňovanie stávajúcich (jednotlivých prvkov ako aj všetkých naraz) a podobne:

  • int Add(Customer c)
  • object AddNew()
  • bool Contains(object value)
  • void Clear()
  • int IndexOf(object value)
  • void Insert(int index, object value)
  • bool IsFixedSize
  • bool IsReadOnly
  • void Remove(object value)
  • void RemoveAt(int index)
  • Customer this[int index]

Druhou skupinou, ktorá mi vyvstala je skupina jednej udalosti a zopár metód, ktoré budú túto udalosť vyvolávať a signalizovať tak odberateľovi správ, že sa v kolekcii došlo k nejakej zmene.

  • event ListChangedEventHandler ListChanged
  • void OnListChanged(ListChangedEventArgs ev)
  • void OnClearComplete()
  • void OnInsertComplete(int index, object value)
  • void OnRemoveComplete(int index, object value)
  • void OnSetComplete(int index, object oldValue, object newValue)
  • bool SupportsChangeNotification

Tri nezanedbateľné vlastnosti tvoria ďaľšiu skupinu, ktorá navonok signalizuje, či kolekcia povoluje pridávať, meniť a odstraňovať prvky:

  • bool AllowNew
  • bool AllowEdit
  • bool AllowRemove

V neposlednom rade vyvstala skupina podporujúca sortovanie, teda metódy ktoré sortujú a vracajú kolekciu do nezosortovaného tvaru, vlastnosti koté hovoria o tom, či kolekcia podporuje sortovanie a či je zosortovaní a podobne:

  • bool IsSorted
  • ListSortDirection SortDirection
  • PropertyDescriptor SortProperty
  • bool SupportsSorting
  • void ApplySort(PropertyDescriptor property, ListSortDirection direction)
  • void RemoveSort()

Poslednú skupinu tvoria už len posledné tri členy rozhrania ICollection:

  • bool IsSynchronized
  • void CopyTo(Array array, int index)
  • object SyncRoot

Pri prvom pohľade na docela veľký zoznam vo mne samozrejme začali rásť obavy, či sa mi čas strávený implementáciou vráti. Predpokladal som, že áno a teda som sa do samotnej implementácie aj pustil. Teraz, s mesačným odstupom (mesačný odstup v podobe používania, nie mesačnej implementácia; implementácia zabrala aj s testovaním len pár dní) môžem zodpovedne prehlásiť, že sa mi tento investovaný čas oplatil a vrátil a že túto triedu používam rád a docela často.

Prvým krokom teda bolo vytvorenie (implementácia) tejto triedy. Druhým krokom bolo, keďže som predpokladal jej širšie použitie (nie len v podobe kolekcie zákazníkov), vytvoriť si z nej šablónu pre CodeSmith. No a tretím krokom je samozrejme už len používanie výslednej triedy.

V tomto príspevku som len chcel načrtnúť, aké boli prípravy na tvorbu tejto triedy. Nie je až také zaujímavé (ale je podstatné to vedieť a byť na to pripravený), aké metódy a aké vlastnosti musíte implementovať. To sa samozrejme viete dočítať aj v MSDN. Čo je podstatné a zaujímavé je samotná implementácia, to jest skutočný kód, ktorý Vám do budúcnosti ušetrí množstvo času. Ten však príde až v ďaľšom príspevku...

Mohlo by ťa tiež zaujímať

Páčil sa ti príspevok?

Zdieľaj príspevok alebo si ho odlož na neskôr

Sleduj ma

Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.

Komentáre

K tomuto článku nie su pridané žiadne komentáre.

Pridať komentár

Máš niečo zaujímavé povedať k článku? Pridaj to k článku ako komentár. Spam, reklamu alebo inak nerelevantné komentáre okamžite mažem.