Подтвердить что ты не робот

Почему анонимные типы Equals-реализация сравнивает поля?

Как и в вопросе, мне просто интересно, почему разработчики языка решили реализовать Equals на анонимных типах, которые ведут себя как тип значения. Разве это не вводит в заблуждение?

    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public static void ProofThatAnonymousTypesEqualsComparesBackingFields()
    {
        var personOne = new { Name = "Paweł", Age = 18 };
        var personTwo = new { Name = "Paweł", Age = 18 };

        Console.WriteLine(personOne == personTwo); // false
        Console.WriteLine(personOne.Equals(personTwo)); // true
        Console.WriteLine(Object.ReferenceEquals(personOne, personTwo)); // false

        var personaOne = new Person { Name = "Paweł", Age = 11 };
        var personaTwo = new Person { Name = "Paweł", Age = 11 };
        Console.WriteLine(personaOne == personaTwo); // false
        Console.WriteLine(personaOne.Equals(personaTwo)); // false
        Console.WriteLine(Object.ReferenceEquals(personaOne, personaTwo)); // false
    }

На первый взгляд все напечатанные логические значения должны быть ложными. Но строки с вызовами Equals возвращают разные значения, когда используется тип Person, и используется анонимный тип.

4b9b3361

Ответ 1

Анонимные экземпляры типов являются неизменяемыми значениями данных без поведения или идентичности. Это не имеет смысла для сравнения - сравнить их. В этом контексте я считаю, что вполне разумно создавать для них сравнения структурного равенства.

Если вы хотите переключить поведение сравнения на что-то пользовательское (сравнение ссылок или нечувствительность к регистру), вы можете использовать Resharper для преобразования анонимного типа в именованный класс. Resharper также может генерировать члены равенства.

Существует также очень практичная причина: анонимные типы удобно использовать в качестве хеш-ключей в объединениях LINQ и группировках. По этой причине они требуют семантически правильных реализаций Equals и GetHashCode.

Ответ 2

Зачем нужно спросить у дизайнеров языка...

Но я нашел это в статье Эрика Липлетса о Анонимные типы Унифицировать внутри сборки, часть вторая

Анонимный тип дает вам удобное место для хранения небольшого неизменяемый набор пар имя/значение, но это дает вам больше. Это также дает вам реализацию Equals, GetHashCode и, большинство для этой дискуссии, ToString. (*)

Где причина в примечании:

(*) Мы даем вам Equals и GetHashCode, чтобы вы могли использовать экземпляры анонимных типов в запросах LINQ в качестве ключей для выполнения присоединяется. LINQ to Objects реализует объединения, используя хэш-таблицу для и поэтому нам нужны правильные Equals и GetHashCode.

Ответ 3

Официальный ответ из спецификации языка С# (можно получить здесь):

Методы Equals и GetHashcode для анонимных типов переопределяют методы, унаследованные от объекта, и определяются в терминах Equals и GetHashcode свойств, так что два экземпляра одного и того же анонимного типа равны тогда и только тогда, когда все их свойства равны.

(Мой акцент)

Другие ответы объясняют, почему это делается.

Ответ 4

Потому что это дает нам что-то полезное. Рассмотрим следующее:

var countSameName = from p in PersonInfoStore
  group p.Id by new {p.FirstName, p.SecondName} into grp
  select new{grp.Key.FirstName, grp.Key.SecondName, grp.Count()};

Работает потому, что реализация Equals() и GetHashCode() для анонимных типов работает на основе равноправного поля.

  • Это означает, что приведенное выше будет ближе к одному и тому же запросу при запуске с PersonInfoStore, который не является привязкой к объектам. (Все-таки не то же самое, он будет соответствовать тому, что будет делать XML-источник, но не то, что приведет к сбоям большинства баз данных).
  • Это означает, что нам не нужно определять IEqualityComparer для каждого вызова GroupBy, что сделало бы группу очень трудной задачей с анонимными объектами - возможно, но непросто определить IEqualityComparer для анонимных объектов - и далеки от самый естественный смысл.
  • Прежде всего, это не вызывает проблем с большинством случаев.

Третий вопрос стоит изучить.

Когда мы определяем тип значения, мы, естественно, хотим концепцию равенства, основанную на значении. Хотя у нас может быть иная идея о равенстве, основанном на ценности, чем значение по умолчанию, например, при сопоставлении заданного поля без учета регистра, значение по умолчанию является естественным (если оно плохо работает и не работает в одном случае *). (Кроме того, в этом случае ссылочное равенство бессмысленно).

Когда мы определяем ссылочный тип, мы можем или не хотим концепцию равенства на основе ценности. Значение по умолчанию дает нам ссылочное равенство, но мы можем легко изменить это. Если мы его изменим, мы можем изменить его только на Equals и GetHashCode или для них, а также ==.

Когда мы определяем анонимный тип, о, подождите, мы не определили его, что означает анонимное! Большинство сценариев, в которых мы заботимся о ссылочном равенстве, больше не существует. Если мы собираемся держать объект вокруг достаточно долго, чтобы позже подумать, если он будет таким же, как и другой, мы, вероятно, не имеем дело с анонимным объектом. Часто случаются случаи, когда мы заботимся о равенстве на основе ценности. Очень часто с Linq (GroupBy, как мы видели выше, но также Distinct, Union, GroupJoin, Intersect, SequenceEqual, ToDictionary и ToLookup) и часто с другими видами использования (it не так, как мы не делали то, что Linq делает для нас с перечислениями в 2.0 и до некоторой степени до этого, любое кодирование в 2.0 могло бы написать половину методов в Enumerable сами).

В целом, мы многое получаем от того, как равенство работает с анонимными классами.

В том, что кто-то действительно хочет ссылочного равенства, == с использованием эталонного равенства означает, что у них все еще есть это, поэтому мы ничего не теряем. Это путь.

* Реализация по умолчанию Equals() и GetHashCode() по умолчанию имеет оптимизацию, которая позволяет использовать бинарное соответствие в случаях, когда это безопасно. К сожалению, есть ошибка, из-за которой иногда ошибочно идентифицировать некоторые случаи как безопасные для этого более быстрого подхода, когда они отсутствуют (или, по крайней мере, это использовалось, возможно, оно было исправлено). Обычный случай - если в структуре есть поле decimal, тогда он рассмотрит некоторые экземпляры с эквивалентными полями как неравные.