Jaka jest różnica między IEquatable a po prostu przesłanianiem Object.Equals ()?

185

Chcę, aby moja Foodklasa mogła testować, ilekroć jest ona równa innej instancji Food. Później użyję go na liście i chcę użyć jego List.Contains()metody. Czy powinienem zaimplementować IEquatable<Food>czy po prostu zastąpić Object.Equals()? Z MSDN:

Ta metoda określa równość za pomocą domyślnego modułu porównującego równość zdefiniowanego przez obiektową implementację metody IEquatable.Equals dla T (typ wartości na liście).

Moje następne pytanie brzmi: z jakich funkcji / klas środowiska .NET korzystają Object.Equals()? Czy powinienem go używać?

pochłonęło elysium
źródło
3
Bardzo dobre wytłumaczenie tutaj blogs.msdn.com/b/jaredpar/archive/2009/01/15/…
nawfal
możliwy duplikat zrozumienia IEquatable
nawfal

Odpowiedzi:

214

Głównym powodem jest wydajność. Kiedy leki generyczne zostały wprowadzone w .NET 2.0 były one w stanie dodać kilka zgrabnych klas takich jak List<T>, Dictionary<K,V>, HashSet<T>, itd. Struktury te w ogromnym stopniu korzystają z GetHashCodei Equals. Ale dla typów wartości wymagało to boksowania. IEquatable<T>pozwala strukturze wdrożyć silnie typowaną Equalsmetodę, więc nie jest wymagane boksowanie. Dzięki temu znacznie lepsza wydajność przy użyciu typów wartości z kolekcjami ogólnymi.

Typy referencyjne nie odnoszą tak dużych korzyści, ale IEquatable<T>implementacja pozwala uniknąć rzutowania, z System.Objectktórego może mieć znaczenie, jeśli jest często wywoływany.

Jak zauważono na blogu Jareda Parsona , nadal musisz zaimplementować przesłonięcia obiektów.

Josh
źródło
Czy istnieje jakaś obsada między typami odniesienia? Zawsze myślałem, że rzutowania były po prostu „instrukcjami”, które podajesz kompilatorowi, gdy przypisujesz niektóre nieoczywiste rzutowania z jednego rodzaju obiektu na inny. Po skompilowaniu kod nie wiedziałby nawet, że była tam obsada.
pożarł elysium
7
Dotyczy to języków C ++, ale nie języków .NET, które wymuszają bezpieczeństwo typu. Istnieje rzutowanie środowiska wykonawczego, a jeśli rzutowanie się nie powiedzie, zostanie zgłoszony wyjątek. Istnieje więc niewielka kara w czasie wykonywania, którą trzeba zapłacić za casting. Kompilator może zoptymalizować upcasty na przykład Na przykład obiekt o = (obiekt) „string”; Ale downcasting - ciąg s = (ciąg) o; - musi się zdarzyć w czasie wykonywania.
Josh
1
Widzę. Czy przypadkiem masz jakieś miejsce, w którym mogę uzyskać tego rodzaju „głębsze” informacje o .NET? Dzięki!
pożarł elysium
7
Poleciłbym CLR przez C # Jeffa Richtera i C # w Depth Jona Skeeta. Jeśli chodzi o blogi, blogi Wintellect są dobre, blogi msdn itp.
Josh
Czy IEquatable<T>interfejs robi coś więcej niż przypomina programistom o dołączeniu public bool Equals(T other) członka do klasy lub struktury? Obecność lub brak interfejsu nie ma znaczenia w czasie wykonywania. Wydaje się, że przeciążenie Equalsjest wszystkim, co jest konieczne.
mikemay
48

Według MSDN :

W przypadku zastosowania IEquatable<T>, należy również zastąpić implementacje bazowe klasowych Object.Equals(Object)i GetHashCode tak, że ich zachowanie jest zgodne z tym z IEquatable<T>.Equals metody. Jeśli to zrobisz Object.Equals(Object), twoja przesłonięta implementacja jest również wywoływana w wywołaniach Equals(System.Object, System.Object)metody static w twojej klasie. Zapewnia to, że wszystkie wywołania Equalsmetody zwracają spójne wyniki.

Wygląda więc na to, że nie ma żadnej rzeczywistej różnicy funkcjonalnej między tymi dwoma, z wyjątkiem tego, że można je wywołać w zależności od sposobu użycia klasy. Z punktu widzenia wydajności lepiej jest używać wersji ogólnej, ponieważ nie wiąże się z nią kara za boksowanie / rozpakowywanie.

Z logicznego punktu widzenia lepiej jest również wdrożyć interfejs. Przesłonięcie obiektu tak naprawdę nikomu nie mówi, że twoja klasa jest rzeczywiście dająca się porównać. Zastąpienie może być po prostu klasą nic nie rób lub płytką implementacją. Korzystanie z interfejsu wyraźnie mówi: „Hej, ta rzecz jest ważna do sprawdzania równości!” To po prostu lepszy projekt.

CodexArcanum
źródło
9
Struktury powinny zdecydowanie zaimplementować iEquatable (ofOnnType), jeśli będą używane jako klucze w Słowniku lub podobnej kolekcji; zapewni znaczny wzrost wydajności. Klasy, których nie można odziedziczyć, otrzymają niewielki wzrost wydajności poprzez wdrożenie IEquatable (ichOwnType). Klasy dziedziczne powinny // nie // implementować IEquatable.
supercat
30

Rozszerzając to, co powiedział Josh, o praktyczny przykład. +1 do Josha - miałem już napisać to samo w mojej odpowiedzi.

public abstract class EntityBase : IEquatable<EntityBase>
{
    public EntityBase() { }

    #region IEquatable<EntityBase> Members

    public bool Equals(EntityBase other)
    {
        //Generic implementation of equality using reflection on derived class instance.
        return true;
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as EntityBase);
    }

    #endregion
}

public class Author : EntityBase
{
    public Author() { }
}

public class Book : EntityBase
{
    public Book() { }
}

W ten sposób mam do ponownego użycia metodę Equals (), która działa od razu dla wszystkich moich klas pochodnych.

to. __curious_geek
źródło
Jeszcze tylko jedno pytanie. Jaka jest zaleta używania „obj jako EntityBase” zamiast (EntityBase) obj? Tylko kwestia stylu, czy jest jakaś przewaga?
pożarł elysium
22
w przypadku „obj jako EntityBase” - jeśli obj nie jest typu EntityBase, przejdzie „null” i będzie kontynuowane bez błędu lub wyjątku, ale w przypadku „(EntityBase) obj”, z całą mocą spróbuje rzutować obj do EntityBase i jeśli obiekt nie jest typu EntityBase, zgłosi wyjątek InvalidCastException. I tak, „as” można zastosować tylko do typów referencyjnych.
to. __curious_geek
1
Link Josha do bloga Jareda Par sugeruje, że musisz również przesłonić kod GetHashCode. Czy tak nie jest?
Polubowne
3
Tak naprawdę nie dostaję dodatkowej wartości, jaką zapewnia twoja implementacja. Czy potrafisz wyjaśnić problem, który rozwiązuje twoja abstrakcyjna klasa podstawowa?
Mert Akcakaya,
1
@Amicable - tak, za każdym razem, gdy przesłonisz Object.Equals (Object), musisz również przesłonić GetHashCode, aby kontenery działały.
namford,
0

Jeśli zadzwonimy object.Equals, wymusza to kosztowne boksowanie według rodzajów wartości. Jest to niepożądane w scenariuszach wrażliwych na wydajność. Rozwiązaniem jest użyć IEquatable<T>.

public interface IEquatable<T>
{
  bool Equals (T other);
}

Chodzi o IEquatable<T>to, że daje ten sam wynik, object.Equalsale szybciej. Ograniczenia where T : IEquatable<T>należy używać z typami ogólnymi, takimi jak poniżej.

public class Test<T> where T : IEquatable<T>
{
  public bool IsEqual (T a, T b)
  {
    return a.Equals (b); // No boxing with generic T
  }
}

w przeciwnym razie wiąże się z slower object.Equals().

Seyedraouf Modarresi
źródło