Написание хорошего метода равных С#
Есть ли у кого-нибудь шаблон для написания приличного метода equals - я помню, что в Effective Java были проблемы с обработкой равных при работе с подклассами.
У меня нет книги со мной, и я не могу вспомнить, было ли это практическим советом. Итак, как вы пишете надежную реализацию метода равных равных?
Ответы
Ответ 1
Возможно, это предложение вне стены, но: во-первых, не следует переопределять Equals
. В принципе, принцип равенства не работает с подклассом, как вы упомянули. Однако почти везде в .NET API, который использует равенство (например, словари, хэш-множества), разрешается передавать IEqualityComparer<T>
. Создание другого объекта, ответственного за равенство, делает жизнь намного более гибкой: вы можете использовать разные объекты, чтобы определить, какие критерии использования.
Реализация IEqualityComparer<T>
намного проще - вам все равно нужно проверить недействительность, но вам не нужно беспокоиться о том, подходят ли типы или будет ли Equals
еще более переопределено.
Еще один подход к обеспечению нормальной работы Equals
заключается в том, чтобы избежать наследования полностью по большей части - я не могу вспомнить последний раз, когда в моем коде действительно имело смысл переопределить Equals
и разрешить производные классы. sealed
FTW:)
Ответ 2
Возможно, вы уже это сделали, но просмотрели ли вы статью MSDN при реализации Equals()
?
Внедрение метода Equals
Ответ 3
Свойства метода хороших равных:
- Симметрия. Для двух ссылок a и b, a.equals(b) тогда и только тогда, когда b.equals(a)
- Рефлексивность. Для всех непустых ссылок a.equals(a)
- Транзитивность: если a.equals(b) и b.equals(c), то a.equals(c)
Ответ 4
Если вам надоело писать много шаблонов для этого, вы можете попробовать использовать базовый класс, который реализует его для вас.
public abstract class ValueObject<T> : IEquatable<T>
where T : ValueObject<T>
{
protected abstract IEnumerable<object> Reflect();
public override bool Equals(Object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (obj.GetType() != GetType()) return false;
return Equals(obj as T);
}
public bool Equals(T other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Reflect().SequenceEqual(other.Reflect());
}
public override int GetHashCode()
{
return Reflect().Aggregate(36, (hashCode, value) => value == null ?
hashCode : hashCode ^ value.GetHashCode());
}
public override string ToString()
{
return "{ " + Reflect().Aggregate((l, r) => l + ", " + r) + " }";
}
}
Теперь, чтобы создать класс, подобный значению, просто скажите:
public class Person : ValueObject<Person>
{
public int Age { get; set; }
public string Name { get; set; }
protected override IEnumerable<object> Reflect()
{
return new object[] { Age, Name };
}
}
В Reflect
override вы возвращаете последовательность значений, которые должны способствовать равенству.
К сожалению, этот подход не может помочь в объявлении operator ==
, поскольку это должно быть специально объявлено на производном типе.
Ответ 5
Посмотрите Рекомендации по перегрузке Equals() и Operator == в MSDN.
Ответ 6
Обычно я делаю что-то вроде этого:
public struct EmailAddress : IEquatable<EmailAddress>
{
public override bool Equals(object obj)
{
return obj != null && obj.GetType() == typeof(EmailAddress) && Equals((EmailAddress)obj);
}
public bool Equals(EmailAddress other)
{
return this.LocalPart == other.LocalPart && this.Domain == other.Domain;
}
}
Ответ 7
"Хороший" метод равен метод, который сравнивает уникальную часть класса, оставляя вне части, которые не вносят вклад в уникальность. Итак, если у вас есть класс с уникальным идентификатором, вы можете просто использовать его для установления равенства, не оставляя никаких других свойств. Часто вам также потребуется некоторое значение времени.
Ответ 8
публичный класс Person { public string Имя {get; задавать; }
public int Age { get; set; }
public override bool Equals(object obj)
{
var objAsPerson = obj as Person;
if (obj == null)
{
return false;
}
if (this.Name != objAsPerson.Name)
{
return false;
}
if (this.Age != objAsPerson.Age)
{
return false;
}
return true;
}
}