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

Publikoval Michal Kočí dňa 21.2.2005 o 22:10 v kategórii .Net

Teraz príde asi tá najzaujímavejšia časť, teda tá, o ktorej sa toho veľa človek nedočíta. Sortovanie. Začnem asi od tých najjednoduchších implementačných záležitostí. Tými sú štyri vlastnosti. Vlastnosť IsSorted udáva, či je kolekcia zosortovaná. Teda, tá bude vracať privátnu premennú isSorted, ktorú nastavíme na true v momente zavolania metódy ApplySort a nastavíme ju na false v momente zavolania metódy RemoveSort. Vlastnosť SupportsSorting bude jednoducho vracať vždy true.

Vlastnosti SortDirection a SortProperty budú rovnako ako vyššie dve spomenuté vlastnosti iba na čítanie a budú takisto nastavované v metóde ApplySort.

private PropertyDescriptor sortProperty = null;
private ListSortDirection sortDirection = ListSortDirection.Ascending;
private bool isSorted = false;

public bool IsSorted
{
  get { return isSorted; }
}

public ListSortDirection SortDirection
{
  get { return sortDirection; }
}

public PropertyDescriptor SortProperty
{
  get { return sortProperty; }
}

public bool SupportsSorting
{
  get { return true; }
}

A teraz k samotnému princípu sortovania. Tým, že máme užívateľovi našej triedy poskytnúť aj metódu RemoveSort, ktorá na kolekcii zruší sortovanie nám vzniká jemný problém a to, ako by malo odsortovanie prebiehať. Riešení je isto viac, ale mnou použité je zrejme najjednoduchšie. Pri požiadavke na zosortovanie, t.j. v metóde ApplySort, si odzálohujeme nesortované pole. No a v metóde RemoveSort jednoducho toto odzálohované pole opäť vrátime. Tým nám vznikne ďaľší problém. Ak máme pole zosortované, t.j. vo vnorenej kolekcii InnerList máme kolekciu zosortovanú a niekde bokom máme odložený originál kolekcie, musíme istým spôsobom zabezpečiť, aby v prípade akýchkoľvek zmien prvkov boli tieto zmeny vykonané v oboch kolekciách. Táto na prvý pohľad zložitá požiadavka je finálne jednoduchá, riešením je ošetrovať tento stav v momente nastatia jednotlivých udalostí, ktoré nám vznikajú keď prvky kolekcie meníme. Riešenie uvidíte nižšie.

Takže poďme sa zamyslieť nad tým, ako by sme mali riešiť metódu ApplySort. Táto dostane ako dva vstupné parametre informáciu o smere sortovania (vzostupné alebo zostupné) a o vlastnosti triedy Customer, podľa ktorej máme kolekciu zosortovať. Na začiatku metódy musíme skontrolovať, či je kolekcia už zosortovaná. Ak ešte nie, musíme si pôvodnú nezoradenú kolekciu odzálohovať.

Samotné sortovanie si necháme zabezpečiť dočasným ArrayList-om. Ten vie totiž veľmi jednoducho sortovať objekty, ktoré implementujú rozhranie IComparable - napríklad String, DateTime, Decimal, Int32 a podobne. Teda snáď všetky jednoduché typy hodnotového charakteru. Ak teda niektorá vlastnosť našej triedy (v tomto prípade triedy Customer) nebude implementovať rozhranie IComparable, budem chcieť, aby sa táto vlastnosť zmenila na reťazec (metódou ToString) a sortovanie aby sa vykonalo na základe týchto reťazcov.

Teda, bolo by vhodné do ArrayListu dostať iba vlastnosti jednotlivých objektov typu Customer a podľa nich vykonať sortovanie. Tým by sme však stratili väzbu na samotný objekt typu Customer. A to nemôžeme. Preto sa mi pozdáva riešenie spomínanej šablóny pre CodeSmith. Podľa tej šablóny to aj vykonávam a to nasledovne. V rámci svojej kolekcie si pripravím vnorenú triedy, ktorú nazvem ListItem. Tá bude mať dve vlastnosti - Key a Item a obe sú typu Object. Vlastnosť Key bude reprezentovať tú vlastnosť triedy Customer, podľa ktorej chceme sortovať a vlastnosť Item bude obsahovať samotný objekt triedy Customer. Navyše, trieda ListItem bude implementovať rozhranie IComparable, ktorého jedinou úlohou je implementovať metódu CompareTo.

Metóda CompareTo triedy ListItem pracuje nasledovne. Ak Key implementuje IComparable, zavolá túto, čiže na ňu deleguje svoju úlohu. Ak Key neimplementuje IComparable, potom Key skonvertuje na String a porovná reťazce. ListItem vyzerá nasledovne:

private class ListItem : IComparable 
{
  public object Key;
  public object Item;

  public ListItem(object key, object item)
  {
    Key = key;
    Item = item;
  }

  int IComparable.CompareTo(object obj)
  {
    object target = ((ListItem)obj).Key;

    if(Key is IComparable)
    {
      return ((IComparable)Key).CompareTo(target);
    }
    else
    {
      if(Key.Equals(target))
        return 0;
      else
        return Key.ToString().CompareTo(target.ToString());
    }
  }

  public override string ToString()
  {
    return Key.ToString ();
  }
}

Tým pádom, ak náš pomocný ArrayList naplníme nie objektmi typu Customer, ale objektmi typu ListItem, tento ArrayList použije na sortovanie metódu CompareTo triedy ListItem, ktorá zase vnútorne na sortovanie použije metódy CompareTo triedy toho typu, podľa ktorého sa znažíme sortovať, prípadne typu String. Tým dosiahneme, že pole bude zosortované tak ako si predstavujeme a navyše, tým že ListItem obsahuje aj samotný objekt typu Customer (prístupný cez vlastnosť Item), nestratí sa nám ani väzba na zákazníka.

Toto sortovanie nám však zoradí kolekciu iba vzostupne. Ak chceme zoradiť kolekciu zostupne, jednoducho len po zoradení vezmeme prvky zo zosortovanej kolekcie od posledného k prvému. Inak ich vezmeme od prvého k poslednému. Takže postup sortovania sa dá dosiahnuť v nasledovných krokoch:

  • ak naša kolekcia nie je zosortovaná, tak ju odzálohujeme
  • pre každého zákazníka (Customer) vytvoríme objekt typu ListItem, ktorého vlastnosť Key bude obsahovať objekt toho typu, akého je samotná vlastnosť a ktorého vlastnosť Item bude obsahovať samotný (pôvodný) objekt typu Customer
  • zosortujeme kolekciu objektov typu ListItem
  • zmažeme vnútornú kolekciu (vlastnosť InnerList )
  • podľa typu sortovania (vzostupne / zostupne) prejdeme všetky prvky sortovanej kolekcie (od prvého k poslednému / od posledného k prvému) a vlastnosť Item, ktorá obsahuje zákazníka pridáme opäť do vnútornej kolekcie (InnerList )
  • nastavíme príznak, že je kolekcia zoradená
  • vyvoláme udalosť informujúcu o tom, že prvky kolekcie boli zresetované

A presne tento postup implementuje metóda ApplySort:

private ArrayList originalArray = null;

public void ApplySort(PropertyDescriptor property,
  ListSortDirection direction)
{
  sortProperty = property;
  sortDirection = direction;

  if(!isSorted)
  {
    originalArray = new ArrayList(InnerList);
  }

  ArrayList al = new ArrayList();
  foreach(Customer c in InnerList)
  {
    al.Add(new ListItem(sortProperty.GetValue(c), c));
  }

  al.Sort();
  InnerList.Clear();

  if(sortDirection == ListSortDirection.Ascending)
  {
    for(int i=0; i<al.Count; i++)
    {
      InnerList.Add(((ListItem) al[i]).Item);
    }
  }
  else
  {
    for(int i=al.Count-1; i>=0; i--)
    {
      InnerList.Add(((ListItem) al[i]).Item);
    }
  }

  isSorted = true;
  OnListChanged(new ListChangedEventArgs(
    ListChangedType.Reset, -1));
}

Naproti tomu je metóda RemoveSort už pomerne jednoduchá. V prvom kroku overíme, či je kolekcia zosortovaná. Ak nie, potom nemáme aké sortovanie by sme odstránili. Ak je, potom obnovíme zo zálohy nesortovanú kolekciu a túto zálohu zároveň odstránime. Nastavíme príznak, že pole nie je sortované a vyvoláme udalosť informujúcu o tom, že prvky kolekcie boli zresetované.

public void RemoveSort()
{
  if(!isSorted) return;

  InnerList.Clear();

  foreach(Customer c in originalArray)
  {
    InnerList.Add(c);
  }

  originalArray.Clear();
  originalArray = null;

  isSorted = false;
  OnListChanged(new ListChangedEventArgs(
    ListChangedType.Reset, -1));
}

Toľko ohľadom pre mňa veľmi dôležitej schopnosti zoradenia, nabudúce o udalostiach a ako nimi zabezpečiť akúsi synchronizáciu prvkom v sortovanej a nesortovanej kolekci..

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.