2010-12-12 7 views
10

Каков ваш подход при написании проверок равенства для structs и classes, которые вы создаете?C# проверка равенства

1) Требует ли «полное» равенство проверки того, что большая часть шаблонного кода (например, override Equals, override GetHashCode, общий Equals, operator==, operator!=)?

2) Вы конкретно указываете, что ваши классы моделируют интерфейс IEquatable<T>?

3) Правильно ли я понимаю, что нет никакого фактического способа автоматического применения Equals переопределения, когда я призываю что-то вроде a == b и я всегда должен реализовать оба Equals и operator== членов?

ответ

20

вы правы, это много кода котла-плиты, и вы должны реализовать все по отдельности.

Я бы рекомендовать:

  • Если вы собираетесь осуществить равенство значений на всех, переопределить GetHashCode и Equals(object) - создание перегрузок для == и реализации IEquatable<T>, не делая, что может привести к очень неожиданному поведению
  • я всегда буду осуществлять IEquatable<T>, если вы переопределение Equals(object) и GetHashCode
  • Я только перегрузить оператор == более редко
  • Реализация равенства правильно для негерметичных классов сложно, и все еще может привести к неожиданным/нежелательным результатам. Если вам нужно равенство для типов в иерархии, реализуйте IEqualityComparer<T>, выразив интересующее вас сравнение.
  • Равенство для изменяемых типов обычно плохое, поскольку два объекта могут быть равными, а затем неравными позже ...если объект мутирован (в случае равноправия) после того, как он был использован как ключ в хеш-таблице, вы больше не сможете его найти.
  • Некоторые из котельной плиты несколько отличаются для структур ... но, как и Марк, я очень редко пишу свои собственные структуры.

Вот пример реализации:

using System; 

public sealed class Foo : IEquatable<Foo> 
{ 
    private readonly string name; 
    public string Name { get { return name; } } 

    private readonly int value; 
    public int Value { get { return value; } } 

    public Foo(string name, int value) 
    { 
     this.name = name; 
     this.value = value; 
    } 

    public override bool Equals(object other) 
    { 
     return Equals(other as Foo); 
    } 

    public override int GetHashCode() 
    { 
     int hash = 17; 
     hash = hash * 31 + (name == null ? 0 : name.GetHashCode()); 
     hash = hash * 31 + value; 
     return hash; 
    } 

    public bool Equals(Foo other) 
    { 
     if ((object) other == null) 
     { 
      return false; 
     } 
     return name == other.name && value == other.value; 
    } 

    public static bool operator ==(Foo left, Foo right) 
    { 
     return object.Equals(left, right); 
    } 

    public static bool operator !=(Foo left, Foo right) 
    { 
     return !(left == right); 
    } 
} 

И да, это чертовски много шаблонного, очень мало чего меняется между реализациями :(

Реализация == является немного менее эффективен, чем может быть, поскольку он будет звонить до Equals(object), который должен выполнить проверку динамического типа ... но альтернативой является еще больше котельной пластины, например:

public static bool operator ==(Foo left, Foo right) 
{ 
    if ((object) left == (object) right) 
    { 
     return true; 
    } 

    // "right" being null is covered in left.Equals(right) 
    if ((object) left == null) 
    { 
     return false; 
    } 
    return left.Equals(right); 
} 
+1

^^ Вашего каждого сообщения это учебная глава C# .. :) – Dienekes

+0

2 второстепенных предложения для второго кодового блока: 1) Разве вы не должны перемещать '(object) left == (object) right' из' == 'в общий' Equals'? Это дает скорость (конечно, это зависит, но в худшем случае) проверка ссылочного равенства даже для общего метода «Равно»? 2) вам не нужна вторая нулевая проверка '(object) right == null' в' == ', поскольку вы по существу делаете это в родовом' Equals'. См. Мой пост .. – nawfal

+0

@nawfal: Я не думаю, что в общем случае «Equals» есть много смысла - все равно будет быстро в тех случаях, когда оно * верно, а для случаев, когда оно * не * true, это добавляет дополнительную проверку без каких-либо преимуществ. Что касается нулевой части - это потребует проверки динамического типа. Да, вы можете поспорить обоим - но я доволен тем, что я написал два года назад ... –

1

Вам просто нужно реализовать operator == для a == b.
Как мне нравятся мои данные в словарях, иногда я переопределяю GetHashCode.
Далее я реализую Equals (в качестве стандартного стандарта ... это потому, что при использовании дженериков нет ограничений для равенства) и укажите реализацию IEquatable. Поскольку я собираюсь сделать это, я мог бы также указать мои == и! = Реализации равным. :)

6

Я редко делаю что-то особенное для занятий; для большинства обычных объектов ссылочное равенство работает нормально.

Я еще реже пишу struct; но поскольку структуры представляют значения, то обычно целесообразно обеспечить равенство и т. д. Это обычно включает все; Равно, ==,! = И IEquatable<T> (так как это позволяет избежать бокс в сценариях использования EqualityComparer<T>.Default.

шаблонный, обычно не слишком проблематично, но IIRC инструменты, такие как ReSharper может помочь здесь.

Да, желательно сохранить Equals и == в синхронизации, и это должно быть сделано явно.

Смежные вопросы