Pomysł na napisanie tego posta podsunął mi kolega z pracy. Na początek fragment klasy, która we wzorcowy sposób dostarcza własnej implementacji operatora równości:
Jest jednak inaczej. W momencie kompilacji kompilator nie wie co zostanie podstawione pod T. W związku z tym generowany jest kod sprawdzający równość referencyjną. Na poziomie IL oznacza to użycie instrukcji ceq. Jeśli zmienimy kod metody MyEquals tak jak poniżej kompilator będzie już mógł wybrać operator równości dostarczony przez nas zamiast bazowego.
public class Test { private int i; public Test(int i) { this.i = i; } public static bool operator==(Test a, Test b) { if (System.Object.ReferenceEquals(a, b)) return true; if (((object)a == null) || ((object)b == null)) return false; return a.i == b.i; } ... }Oczywiście potrzebna jest jeszcze implementacja operatora nierówności !=. Chciałbym też zwrócić uwagę, ponieważ sam zapomniałem o tym pisząc ten przykład :), że implementując operator równości (czy też nierówności) kiedy wykonujemy porównanie takie jak a == b, a == null czy b == null musimy zrzutować testowane obiekty do typu object. W innym wypadku nastąpi rekurencyjne wywołanie naszej implementacji operatora. Rzutowanie zapewni, że zostanie wywołana implementacja bazowa - operatory nie działają jak metody wirtualne. Inne rozwiązanie to użycie metody System.Object.ReferenceEquals. Teraz popatrzmy na kod poniżej i odpowiedzmy sobie na pytanie jaką wartość przyjmą zmienne lokalne res oraz res2:
public class Program { public static bool MyEqualsOdpowiedź brzmi oczywiście true oraz false. Piszę o tym również dla siebie ponieważ sam musiałem się zastanowić jaki będzie wynik. Pierwszy rezultat jest chyba oczywisty i nie wymaga żadnych tłumaczeń. Po prostu wywołany zostanie zaimplementowany przez nas operator. A co z wywołaniem metody generycznej MyEquals. Może się wydawać, że skoro jest to metoda generyczna to zostanie wywołany operator równości dla typu przekazanego jako argument typu generycznego (ang. generic type parameters). Argument typu generycznego to to co podstawiamy pod T czyli w tym przypadku Test.(T a, T b) where T : class { return a == b; } public static void Main(string[] args) { Test a = new Test(1); Test b = new Test(1); bool res = a == b; bool res2 = MyEquals(a, b); } }
Jest jednak inaczej. W momencie kompilacji kompilator nie wie co zostanie podstawione pod T. W związku z tym generowany jest kod sprawdzający równość referencyjną. Na poziomie IL oznacza to użycie instrukcji ceq. Jeśli zmienimy kod metody MyEquals tak jak poniżej kompilator będzie już mógł wybrać operator równości dostarczony przez nas zamiast bazowego.
public static bool MyEquals(T a, T b) where T : Test { return a == b; }
0 comments:
Post a Comment