Poznáváme C# a Microsoft.NET 27. díl – třídy kolekcí III.

V tomto dílu seriálu, který je opět spojen se třídami kolekcí, se podíváme na možnost definice vlastních porovnávacích metod, které ve výsledku ovlivní i způsoby řazení v kolekcích.

Definice vlastního porovnání instance

Zajisté se někdy dostanete do situace, kdy bude potřeba porovnat dvě instance tříd. Můžeme to například provést implementací nějaké vlastní instanční metody, která nám to zajistí. Lepší volba ovšem je nechat naší třídu, která má mít námi určený způsob porovnávání, implementovat rozhraní IComparable, které se nachází v námi již známém jmenném prostoru System.Collections.

To nám s sebou přinese mnohé výhody o kterých se dozvíme dále. Rozhraní IComparable nám jednoduše předepisuje naimplementovat metodu CompareTo, která přijímá parametr typu object a vrací celé číslo (int). Pokud je výsledek této metody menší než nula, znamená to, že je naše instance „menší“ než porovnávaný objekt, který byl předán jako parametr. V opačném případě je naše instance „větší“.

Metoda by měla vracet nulu pouze v případě, že jsou si instance rovny. Následující zdrojový kód obsahuje definici třídy Osoba, která implementuje rozhraní IComparable a tím pádem i zmiňovanou metodu.

/// <summary>
/// Trida predstavujici osobu, s definici
/// vlastniho porovnavani instanci
/// </summary>
public class Osoba : IComparable
{
  private string jmeno;
  private string prijmeni;
   
  public Osoba(string Jmeno, string Prijmeni)
  {
    this.jmeno = Jmeno;
    this.prijmeni = Prijmeni;
  }

   

  #region IComparable Members

  public int CompareTo(object obj)
  {
    if (!(obj is Osoba))
      throw new ArgumentException("Predany objekt neni typu osoba");
    Osoba lOsoba = (Osoba) obj;
    return String.Compare(this.prijmeni,lOsoba.prijmeni);
  }

  #endregion

  public override string ToString()
  {
    return jmeno + " " + prijmeni;
  }

}

Jak můžete vidět, tak tato implementace metody, porovnává na základě příjmení daných instancí osob. Pokud se jí pokusíme ve formě parametru předat instanci, která není typu osoba, k porovnání nedojde a bude vyhozena výjimka. K porovnání příjmení jsem použil statickou metodu Compare třídy System.String.

Onu, o pár řádek výše, zmiňovanou výhodu použití implementace rozhraní IComparable k definici porovnávání, představuje to, že naše implementace metody CompareTo, bude použita při řazení v nějaké kolekci. Třída ArrayList obsahuje instanční metodu Sort, která zařídí seřazení obsažených prvků. A jelikož naše třída Osoba má definováno porovnávání na základě příjmení budou prvky ArrayListu po zavolání metody Sort seřazeny podle příjmení.

/// <summary>
/// Priklad, ukazujici nasledek implementace rozhrani
/// IComparable na tride Osoba.
/// </summary>
public class IComparablePriklad
{
  public static void Priklad()
  {
    Osoba lMichal = new Osoba("Michal","Hynek");
    Osoba lMartin = new Osoba("Martin","Pesek");
    Osoba lTomas = new Osoba("Tomas","Berger");
    ArrayList lOsoby = new ArrayList();
    lOsoby.Add(lMichal);
    lOsoby.Add(lMartin);
    lOsoby.Add(lTomas);
    lOsoby.Sort();
    VypisKolekci.VypisHodnoty(lOsoby);
  }
}

Výstup bude následující:

Tomas Berger, Michal Hynek, Martin Pesek,

Definice více možných způsobů porovnání

Možná Vás napadlo, že zmíněný způsob implementace porovnávání instancí našich tříd je sice fajn, ale až do té doby, kdy budeme chtít použít více než jeden způsob porovnávání. Předchozí způsob je vhodný pouze pro definici výchozího způsobu porovnávání, protože rozhraní lze implementovat pouze jednou. Jak ale tedy zařídit, aby bylo možné konkrétní způsob řazení zvolit?

Odpověď na tuto otázku za nás vyřešili návrháři knihoven .NET frameworku a to v podobě použití rozhraní IComparer. Toto rozhraní totiž předepisuje metodu Compare, přijímající dva parametry typu object, které mají být mezi sebou porovnány. Tato metoda by měla vracet celočíselnou hodnotu, která je menší než nula pokud je první předaný objekt menší než druhý a analogicky větší než nula v případě, že je první objekt větší.

Nula by měla být vráceno, když jsou si objekty rovny. Takže za záměrem umožnit porovnávání a tedy i následné řazení v kolekcích jak podle jména tak podle příjmení osob přidáme do třídy Osoba definice dvou vnitřních tříd implementující rozhraní IComparer.

public class Osoba : IComparable
{
  private string jmeno;
  private string prijmeni;
   
  public Osoba(string Jmeno, string Prijmeni)
  {
    this.jmeno = Jmeno;
    this.prijmeni = Prijmeni;
  }

  public static System.Collections.IComparer RazeniPodleJmena
  {
    get
    {
      return new PorovnaniPodleJmena();
    }
  }

  public static System.Collections.IComparer RazeniPodlePrijmeni
  {
    get
    {
      return new PorovnaniPodlePrijmeni();
    }
  }

  #region  Definice tridy PorovnaniPodleJmena
  private class PorovnaniPodleJmena : System.Collections.IComparer
  {
    #region IComparer Members

    public int Compare(object obj1, object obj2)
    {
      Osoba lOsoba1 = (Osoba) obj1;
      Osoba lOsoba2 = (Osoba) obj2;
      return String.Compare(lOsoba1.jmeno,lOsoba2.jmeno);
    }

    #endregion
  }
  #endregion

  #region Definice tridy PorovnaniPodlePrijmeni
  private class PorovnaniPodlePrijmeni : System.Collections.IComparer
  {
    #region IComparer Members

    public int Compare(object obj1, object obj2)
    {
      Osoba lOsoba1 = (Osoba) obj1;
      Osoba lOsoba2 = (Osoba) obj2;
      return String.Compare(lOsoba1.prijmeni,lOsoba2.prijmeni);
    }

    #endregion

  }
  #endregion

  #region IComparable Members

  public int CompareTo(object obj)
  {
    if (!(obj is Osoba))
      throw new ArgumentException("Predany objekt neni typu osoba");
    Osoba lOsoba = (Osoba) obj;
    return String.Compare(this.prijmeni,lOsoba.prijmeni);
  }

  #endregion
  public override string ToString()
  {
    return jmeno + " " + prijmeni;
  }

}

Do naší třídy tedy přibyly dvě vnitřní třídy. První definuje porovnání podle jména a druhá podle příjmení. To jakým způsobem budou objekty porovnávány respektive řazeny určíme například u ArrayListu tak, že využijeme přetížení metody Sort. Některé její verze totiž očekávají instanci třídy implementující rozhraní IComparer.

Mimo dvou vnitřních tříd přibyly do třídy Osoba i dvě nové statické vlastnosti (RazeniPodleJmena, RazeniPodlePrijmeni), které vracejí instance oněch vnitřních tříd a to hlavně proto, aby bylo určování způsobu porovnávání o něco příjemnější (jinak bychom vždy museli psát v kódu new pro vytváření instancí vnitřních tříd).

/// <summary>
/// Priklad pouziti razeni v ArrayListu podle
/// parametru typu IComparer
/// </summary>
public class IComparerPriklad
{
  public static void Priklad()
  {
    Osoba lMichal = new Osoba("Michal","Hynek");
    Osoba lMartin = new Osoba("Martin","Pesek");
    Osoba lTomas = new Osoba("Tomas","Berger");
    ArrayList lOsoby = new ArrayList();
    lOsoby.Add(lMichal);
    lOsoby.Add(lMartin);
    lOsoby.Add(lTomas);
    //serazeni osob podle jmena
    lOsoby.Sort(Osoba.RazeniPodleJmena);
    VypisKolekci.VypisHodnoty(lOsoby);
    //serazeni osob podle prijmeni
    lOsoby.Sort(Osoba.RazeniPodlePrijmeni);
    VypisKolekci.VypisHodnoty(lOsoby);
  }
}

Výstup po spuštění tohoto příkladu by měl vypadat takto:

Martin Pesek, Michal Hynek, Tomas Berger,
Tomas Berger, Michal Hynek, Martin Pesek,

Zdrojové kódy příkladů naleznete zde.

Diskuze (1) Další článek: Dvě slibné akvizice společnosti Sun

Témata článku: , , , , , , , , , , ,