Равенство - поля первого объекта попарно равны полям второго Тождество - первый объекта указывает на то же место в куче, что и второй объект == - это статический оператор, который может быть переопределен для каждого cочетания объектов, он не всегда вызывает Equals
public virtual Equals(object obj)
{
if(this == obj)
return true;
return false
}
Просто ссылочное сравнение, если ссылки указываеют на одинаковые объекты в куче, то они равны, если нет - не равны
Структуры переопределяют Equals на попарное сравнение значений всех полей, если какая-то пара полей не равна, то объекты не равны Сравнение происходит рефлексивно, поэтому имеет смысл переопределить этот метод
Потому что он определяет тождественность объектов, а не равенство, также в классе Object определен метод ReferenceEquals(object o1,object o2), который делает тоже самое, что и метод Equals, по- мнению Рихтера Equals нужно организовать так:
- Проверяем что объект пришедший в метод не null, иначе возвращаем false
- Проверяем на равенство по ссылке, если они равны возвращаем true
- Проверяем, что объекты принадлежат к одинаковым типа (иначе возвращаем false)
- Далее попарно проверяем все поля объектов, если какие-то поля разные - возвращаем false (сравниваем с использованием Equals, для типов тех объектов)
- возвращаем результат вызова базового метода Equals, если объект участвует в иерархии наследования
4) Привести пример класса переопределяющего Equals и операторы (==, !=, >, <, >=, <=), IConverable<>, IConvertable, IEquatiale<>
public class Person : IComparable<Person>, IComparable, IEquatable<Person>
{
public string Name { get; set; }
public int Age { get; set; }
public int CompareTo(Person other)
{
if (Equals(other))
return 0;
// b-log
if (Age > other.Age)
return 1;
return -1;
//
}
public bool Equals(Person other)
{
if (ReferenceEquals(other, null))
return false;
return other.Name?.Equals(Name) == true && other.Age.Equals(Age);
}
public override bool Equals(object obj)
{
if (!(obj is Person person))
return false;
// забавный факт, но
// if(obj is Person == false)
// return false
// return Equals(person); - вызовет ошибку компиляции, переменная person непроиниализаирована
return Equals(person);
}
public override int GetHashCode()
{
if (Name != null)
return Name.GetHashCode() ^ Age;
return Age.GetHashCode();
}
public int CompareTo(object obj)
{
if (!(obj is Person person))
throw new ArgumentException();
return CompareTo(person);
}
public static bool operator ==(Person p1, Person p2)
{
return !ReferenceEquals(p1, null) && p1.Equals(p2);
}
public static bool operator !=(Person p1, Person p2)
{
return p1 == p2 == false;
}
public static bool operator >(Person p1, Person p2)
{
if (p1 == null)
throw new ArgumentException();
return p1.CompareTo(p2) == 1;
}
public static bool operator <(Person p1, Person p2)
{
if (p1 == null)
throw new ArgumentException();
return p1.CompareTo(p2) == -1;
}
public static bool operator >=(Person p1, Person p2)
{
if (p1 == null)
throw new ArgumentException();
return p1.CompareTo(p2) >= 0;
}
public static bool operator <=(Person p1, Person p2)
{
if (p1 == null)
throw new ArgumentException();
return p1.CompareTo(p2) <= 0;
}
}
Важный момент, в первой версии я неправильно переопределил ==, на самом деле в методе переопределении оператора нельзя использовать этот оператор(казалось бы очевидно), менее очевидно, что тут тоже будет SOE
public bool Equals(Person other)
{
if (other == null)
return false;
return other.Name.Equals(Name) && other.Age.Equals(Age);
}
public static bool operator ==(Person p1, Person p2)
{
return !(p1 == null) && p1.Equals(p2);
}
Во избежании этого, можно либо перед сравнением с null кастить к object, либо использовать ReferenceEquals(который также кастит к object) Я бы не рекомендовал делать взаимные обращения при переопределении статических операторов
- x.Equals(x) = true - рефлексивность
- x.Equals(y) = y.Equals(x) - симметричность
- x.Equals(y) && y.Equals(z) == x.Equals(z) - транзитивность
- Equals - должна быть детерминированной функцией
Рефлексивный поход по полям медленный, да и не все поля должны использоваться в определении Equals и GetHashCode(например, изменяемые не должны использоваться), поэтому можно предложить такое переопределение равенства объектов:
public interface IHasIdentityValues
{
IEnumerable<object> IdentityValues();
}
public abstract class Base<T> : IEquatable<T>, IHasIdentityValues where T : IHasIdentityValues
{
public abstract IEnumerable<object> IdentityValues();
public bool Equals(T other)
{
if (other == null)
return false;
return other.IdentityValues().SequenceEqual(IdentityValues());
}
public override int GetHashCode()
{
// todo
}
public override bool Equals(object obj)
{
if (!(obj is T t))
return false;
return Equals(t);
}
}
public class Pupil : Base<Pupil>
{
public string Name { get; set; }
public string SurName { get; set; }
public int Age { get; set; }
public override IEnumerable<object> IdentityValues()
{
yield return Name;
yield return SurName;
yield return Age;
}
}