Я считаю, что получить что-то столь же простое, как проверка объектов на правильность, немного сложно с дизайном .NET.
Для конструкции
1) Реализация IEquatable<T>
. Это заметно улучшает производительность.
2) Так как теперь у вас есть собственный Equals
, переопределите GetHashCode
, чтобы соответствовать различным переопределениям проверки равенства object.Equals
.
3) Перегрузка операторов ==
и !=
не должна выполняться религиозным образом, поскольку компилятор предупредит, если вы непреднамеренно приравниваете структуру к другой с ==
или !=
, но это хорошо, чтобы это было в соответствии с Equals
методами.
public struct Entity : IEquatable<Entity>
{
public bool Equals(Entity other)
{
throw new NotImplementedException("Your equality check here...");
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity))
return false;
return Equals((Entity)obj);
}
public static bool operator ==(Entity e1, Entity e2)
{
return e1.Equals(e2);
}
public static bool operator !=(Entity e1, Entity e2)
{
return !(e1 == e2);
}
public override int GetHashCode()
{
throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
}
}
Для класса
От MS:
Большинство ссылочных типов не должны перегружать оператор равенства, даже если они переопределяют Equals.
Для меня ==
похоже на равенство значений, больше похоже на синтаксический сахар для метода Equals
. Написание a == b
гораздо более интуитивно понятно, чем написание a.Equals(b)
. Редко нам нужно проверять равенство ссылок. На абстрактных уровнях, имеющих дело с логическими представлениями физических объектов, это не то, что нам нужно проверять. Я думаю, что разные семантики для ==
и Equals
могут сбивать с толку. Я полагаю, что это должно было быть ==
для равенства значений и Equals
для ссылочного (или лучшего названия типа IsSameAs
) равенства в первую очередь. Я бы не хотел серьезно относиться к руководству по РС, не только потому, что оно для меня непривычно, но и потому, что перегрузка ==
не приносит никакого серьезного вреда. В отличие от того, чтобы не отменять неуниверсальные Equals
или GetHashCode
, который может откусить назад, потому что фреймворк не использует ==
где-либо, но только если мы сами его используем. Единственное реальное преимущество, которое я получу от , а не от перегрузки ==
и !=
, - это согласованность с дизайном всей структуры, над которой я не имею никакого контроля. И это действительно большая вещь, так что, к сожалению, я буду придерживаться этого .
С эталонной семантикой (изменяемые объекты)
1) Переопределить Equals
и GetHashCode
.
2) Реализация IEquatable<T>
не обязательна, но будет хорошо, если она у вас есть.
public class Entity : IEquatable<Entity>
{
public bool Equals(Entity other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(null, other))
return false;
//if your below implementation will involve objects of derived classes, then do a
//GetType == other.GetType comparison
throw new NotImplementedException("Your equality check here...");
}
public override bool Equals(object obj)
{
return Equals(obj as Entity);
}
public override int GetHashCode()
{
throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
}
}
С семантикой значения (неизменяемые объекты)
Это сложная часть. Может легко запутаться, если не позаботиться ..
1) Переопределить Equals
и GetHashCode
.
2) Перегрузка ==
и !=
для соответствия Equals
. Убедитесь, что он работает для нулей .
2) Реализация IEquatable<T>
не обязательна, но будет хорошо, если она у вас есть.
public class Entity : IEquatable<Entity>
{
public bool Equals(Entity other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(null, other))
return false;
//if your below implementation will involve objects of derived classes, then do a
//GetType == other.GetType comparison
throw new NotImplementedException("Your equality check here...");
}
public override bool Equals(object obj)
{
return Equals(obj as Entity);
}
public static bool operator ==(Entity e1, Entity e2)
{
if (ReferenceEquals(e1, null))
return ReferenceEquals(e2, null);
return e1.Equals(e2);
}
public static bool operator !=(Entity e1, Entity e2)
{
return !(e1 == e2);
}
public override int GetHashCode()
{
throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
}
}
Будьте особенно внимательны, чтобы увидеть, как должно получиться, если ваш класс может быть унаследован, в таких случаях вам придется определить, может ли объект базового класса быть равным объекту производного класса. В идеале, если для проверки на равенство не используются объекты производного класса, то экземпляр базового класса может быть равен экземпляру производного класса, и в таких случаях нет необходимости проверять равенство Type
в обобщенном Equals
базового класса. .
В общем, старайтесь не дублировать код. Я мог бы сделать общий абстрактный базовый класс (IEqualizable<T>
или около того) в качестве шаблона, чтобы упростить его повторное использование, но, к сожалению, в C #, что мешает мне наследовать дополнительные классы.