\

Školení Návrhové vzory, OOP a UML


 Thursday, April 29, 2004
Pár poznámek k implementaci metody Equals

V poslední době mě docela překvapilo, kolik špatných implementací metody Equals jsem nalezl na webu. Jak jistě víte, metodu Equals mají ve svém rozhraní všechny třídy v .NET Frameworku, protože je deklarována v základní a pro všechny třídy povinné bázové třídě Object. Metoda Equals zjišťuje, zda jsou dvě instance shodné. Ve třídě Object naleznete dvě metody Equals. Instanční virtuální metoda Equals přijímá jeden argument typu Object a její výchozí implementace porovnává referenční identitu dvou instancí. To znamená, že metoda vrátí true pouze tehdy, když porovnává proměnné odkazující na stejné místo v paměti. U hodnotových typů je metoda Equals přepsána tak, že porovnává pomocí reflexe všechny členy instance.

public virtual bool Equals(Object obj);

 Pomocné statické metodě Equals se předávají dva argumenty typu object. Metoda nejdříve zkontroluje, jestli je nějaký argument null. Pokud ano, tak metoda vrátí false, jinak zavolá instanční metodu Equals na objektu objA a předá ji objekt v argumentu objB.

public static bool Equals( object objA, object objB);

Ověření, že dvě proměnné ukazují na stejnou instanci, není ale vždy dostačující. V aplikaci potřebujeme často zjistit, zda dvě proměnné ukazující na jinou  instanci stejného typu  nejsou logicky shodné. Konkrétně, objednávku s číslem 3 můžeme v systému vytvořit ve dvou nezávislých kopiích, ale když porovnáváme proměnné odkazující na tyto nezávislé instance, tak chceme, aby byly instance považovány za shodné. Právě proto musíme správně přepsat metodu Equals a doplnit vlastní porovnávací logiku.
Jak postupovat? Představme si, že chceme přepsat metodu Equals v objektu Order.

public override bool Equals(object obj)
{

}

V těle metody zjistíme, jestli argument obj není null a jestli neni jiného typu, než je typ objektu, na kterém je metoda Equals volána. Jestliže je argument null nebo má odlišný typ, tak ihned vrátíme false, protože nemůžeme porovnávat zcela odlišné nebo neinicializované objekty. Po těchto nutných kontrolách můžeme argument obj bezpečně přetypovat na typ Order a výsledek uložit do proměnné order2.

if (obj == null)
  return false;

if (obj.GetType != this.GetType())
  return false;

Order order2 = obj as Order;

Když dědíme z jiné třídy než Object, měli bychom zavolat přepsanou metodu Equals bázové třídy. Jestliže metoda Equals bázové třídy vrátí false, musíme z potomka vrátit také false.

Nyní musíme porovnat všechny relevantní soukromé i veřejné členy třídy. Bez ohledu na to, zda porovnáváme hodnotový nebo referenční typ, delegujeme odpovědnost za porovnání na pomocnou statickou metodu Equals třídy Object. Když zjistíme, že někteří členové nejsou shodní, musíme vrátit false. Jestliže se všichni členové shodují, můžeme z metody Equals vrátit true, protože jsme právě ověřili, že obě instance jsou identické.

if (!Object.Equals(this.ReferenceProperty1, order2.ReferenceProperty1))
  return false;

if (!Object.Equals(this.ValueProperty2, order2.ValueProperty2))
  return false;

return true;

V objektech, které mají jednoznačný identifikátor (Id), metoda Equals často porovná jen identifikátor Id. Porovnání není zcela přesné, protože objekty mohou mít různé hodnoty atributů, ale pro business aplikace je toto porovnání většinou dostačující. U hodnotových typů (struktur) by měla být vytvořena metoda Equals, která příjimá v argumentu přímo hodnotový typ, aby se předešlo boxingu při volání zděděné metody Equals, jež, jak jsme řekli, akceptuje Object. Správně napsaná metoda Equals musí dodržovat následující čtyři pravidla.

1) Musí být reflexivní - porovnám-li instanci A s instancí A, metoda musí vždy vrátit true.

2) Musí být symetrická - jestliže volání metody A.Equals(B) vrátí true, tak B.Equals(A) musí také vrátit true.

3) Musí být tranzitivní - jestliže volání metody A.Equals(B) vrátí true a B.Equals(C) také vrátí true, musí A.Equals(C) vrátit true.

4) Musí být konzistentní - když volání metody A.Equals(B) vrátí true a nedojde ke změně objektu A ani B, tak nové volání A.Equals(B) musí opět vrátit true.

Když máme přepsanou metodu Equals, je výhodné přetížit operátoy rovná se (==) a nerovná se (!=). Implementace je opravdu jednoduchá. Využijeme statickou metodu Object.Equals, která po kontrolách popsaných výše zavolá námi přepsanou instanční metodu Equals, v níž je již obsažena všechna potřebná logika. V operátoru "nerovná se" ještě pochopitelně výsledek metody Equals negujeme.

public static bool operator != (Order order1, Order order2)
{
  return !object.Equals(order1, order2);
}

public static bool operator == (Order order1, Order order2)
{
  return object.Equals(order1, order2);
}



Thursday, April 29, 2004 7:58:00 PM (Central Europe Standard Time, UTC+01:00)       
Comments [5]  .NET Framework


Tuesday, July 19, 2005 11:02:10 AM (Central Europe Standard Time, UTC+01:00)
Zdravim,
jen byl mel maly dotaz. Pokud u hodnotovych typu vytvorim metodu Equals, ktera neprebira parametr typu objekt, ale dany hodnotovy typ, nevznikne mi pretizena metoda Equals misto prepsane? A nemuzou pak vzniknout problemy, pokud jednou ...
Tuesday, July 19, 2005 11:02:10 AM (Central Europe Standard Time, UTC+01:00)
Take zdravim,
ano, u hodnotovych typu se prida nova metoda Equals s typovym argumentem. Pouvodni metoda Equals s argumentem typu object se prepise tak, ze predany argument pretypuje na dany hodnotovy typ dle postupu popsaneho ve spotu a pote zavola ...
Tuesday, July 19, 2005 11:02:10 AM (Central Europe Standard Time, UTC+01:00)
Takto bych to take resil, jen jsem se chtel ujistit, ze to tak skutecne je :)
Tuesday, July 19, 2005 11:02:10 AM (Central Europe Standard Time, UTC+01:00)
Sice jste se pustil do kritiky "spatnych implementaci metody Equals()" ale kod mi v clanku chybi :-).

Ja bych jen dodal ze:

5) A.Equals(null) je false

coz Vas kod urcite splnuje. Zde ja zajimava inkonzistence v dokumentaci. Rekneme, ze ...
Tuesday, July 19, 2005 11:02:10 AM (Central Europe Standard Time, UTC+01:00)
kod, ktery je roztrousen po spotu, je ze "ziveho" kodu, pouze jsem jej z didaktickych duvodu osekal a clent tridy prejmenoval. Mohl bych sem dat cely kod + asociovane tridy, ale nevim, jestli zrovna tohle je pro spot vhodne. Klidne ...
Comments are closed.