Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save eterekhin/25df8fae5649e6bbd41b28e98bb3113c to your computer and use it in GitHub Desktop.
Save eterekhin/25df8fae5649e6bbd41b28e98bb3113c to your computer and use it in GitHub Desktop.

Равенство - поля первого объекта попарно равны полям второго Тождество - первый объекта указывает на то же место в куче, что и второй объект == - это статический оператор, который может быть переопределен для каждого cочетания объектов, он не всегда вызывает Equals

1)Как реализован Object.Equals?

  public virtual Equals(object obj)
  {
    if(this == obj)
      return true;
    return false
  }

Просто ссылочное сравнение, если ссылки указываеют на одинаковые объекты в куче, то они равны, если нет - не равны

2) Как переопределяет Object.Equals ValueType.Equals?

Структуры переопределяют Equals на попарное сравнение значений всех полей, если какая-то пара полей не равна, то объекты не равны Сравнение происходит рефлексивно, поэтому имеет смысл переопределить этот метод

3) Почему Object.Equals, реализован неверно и как по-мнению Рихтера его нужно было реализовать?

Потому что он определяет тождественность объектов, а не равенство, также в классе Object определен метод ReferenceEquals(object o1,object o2), который делает тоже самое, что и метод Equals, по- мнению Рихтера Equals нужно организовать так:

  1. Проверяем что объект пришедший в метод не null, иначе возвращаем false
  2. Проверяем на равенство по ссылке, если они равны возвращаем true
  3. Проверяем, что объекты принадлежат к одинаковым типа (иначе возвращаем false)
  4. Далее попарно проверяем все поля объектов, если какие-то поля разные - возвращаем false (сравниваем с использованием Equals, для типов тех объектов)
  5. возвращаем результат вызова базового метода 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) Я бы не рекомендовал делать взаимные обращения при переопределении статических операторов

5) Вспомнить правила переопределения Equals

  1. x.Equals(x) = true - рефлексивность
  2. x.Equals(y) = y.Equals(x) - симметричность
  3. x.Equals(y) && y.Equals(z) == x.Equals(z) - транзитивность
  4. 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;
        }
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment