programing

익명 유형에 대한 Equals 구현이 필드를 비교하는 이유는 무엇입니까?

nasanasas 2021. 1. 6. 08:25
반응형

익명 유형에 대한 Equals 구현이 필드를 비교하는 이유는 무엇입니까?


언어의 디자이너가 Equals값 유형 과 유사하게 익명 유형에 Equals를 구현하기로 결정한 이유가 궁금 합니다. 오해의 소지가 있지 않습니까?

public 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유형이 사용되고 익명 유형이 사용 되면 다른 값을 반환합니다 .


익명 유형 인스턴스는 동작이나 ID가없는 변경 불가능한 데이터 값입니다. 그것들을 참조-비교하는 것은 의미가 없습니다. 그런 맥락에서 구조적 동등성 비교를 생성하는 것이 전적으로 합리적이라고 생각합니다.

비교 동작을 사용자 지정 (참조 비교 또는 대소 문자 구분 안 함)으로 전환하려면 Resharper를 사용하여 익명 유형을 명명 된 클래스로 변환 할 수 있습니다. Resharper는 평등 구성원을 생성 할 수도 있습니다.

이렇게해야하는 매우 실용적인 이유도 있습니다. 익명 형식은 LINQ 조인 및 그룹화에서 해시 키로 사용하기 편리합니다. 이러한 이유로 그들은 의미 상 정확 Equals하고 GetHashCode구현이 필요합니다 .


언어 디자이너에게 물어봐야하는 이유는 ...

하지만이 사실은 Eric Lippert의 Anonymous Types Unify within an Assembly, Part Two에서 발견했습니다.

익명 유형은 변경 불가능한 작은 이름 / 값 쌍 세트를 저장할 수있는 편리한 위치를 제공하지만 그 이상을 제공합니다. 또한 Equals, GetHashCode 및이 토론과 가장 밀접한 ToString의 구현을 제공합니다. (*)

메모의 이유 부분 :

(*) LINQ 쿼리에서 익명 형식의 인스턴스를 조인을 수행 할 키로 사용할 수 있도록 Equals 및 GetHashCode를 제공합니다. LINQ to Objects는 성능상의 이유로 해시 테이블을 사용하여 조인을 구현하므로 Equals 및 GetHashCode를 올바르게 구현해야합니다.


C # 언어 사양의 공식 답변 ( 여기에서 구할 수 있음 ) :

익명 형식의 Equals 및 GetHashcode 메서드는 개체에서 상속 된 메서드를 재정의하고 속성의 Equals 및 GetHashcode 측면에서 정의되므로 모든 속성이 동일한 경우에만 동일한 익명 형식의 두 인스턴스 가 동일 합니다.

(내 강조)

다른 답변은 이것이 수행되는 이유를 설명합니다.

VB.Net 에서 구현이 다르다는 점은 주목할 가치 가 있습니다 .

키 속성이없는 익명 형식의 인스턴스는 자신과 동일합니다.

익명 형식 개체를 만들 때 키 속성을 명시 적으로 지정해야합니다. 기본값은 키 없음이며 C # 사용자에게는 매우 혼란 스러울 수 있습니다!

이러한 개체는 VB에서 동일하지 않지만 C #과 동등한 코드에 있습니다.

Dim prod1 = New With {.Name = "paperclips", .Price = 1.29}
Dim prod2 = New With {.Name = "paperclips", .Price = 1.29}

다음 개체는 "같음"으로 평가됩니다.

Dim prod3 = New With {Key .Name = "paperclips", .Price = 1.29}
Dim prod4 = New With {Key .Name = "paperclips", .Price = 2.00}

Because it gives us something that's useful. Consider the following:

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()};

The works because the implementation of Equals() and GetHashCode() for anonymous types works on the basis of field-by-field equality.

  1. This means the above will be closer to the same query when run against at PersonInfoStore that isn't linq-to-objects. (Still not the same, it'll match what an XML source will do, but not what most databases' collations would result in).
  2. It means we don't have to define an IEqualityComparer for every call to GroupBy which would make group by really hard with anonymous objects - it's possible but not easy to define an IEqualityComparer for anonymous objects - and far from the most natural meaning.
  3. Above all, it doesn't cause problems with most cases.

The third point is worth examining.

When we define a value type, we naturally want a value-based concept of equality. While we may have a different idea of that value-based equality than the default, such as matching a given field case-insensitively, the default is naturally sensible (if poor in performance and buggy in one case*). (Also, reference equality is meaningless in this case).

When we define a reference type, we may or may not want a value-based concept of equality. The default gives us reference equality, but we can easily change that. If we do change it, we can change it for just Equals and GetHashCode or for them and also ==.

When we define an anonymous type, oh wait, we didn't define it, that's what anonymous means! Most of the scenarios in which we care about reference equality aren't there any more. If we're going to be holding an object around for long enough to later wonder if it's the same as another one, we're probably not dealing with an anonymous object. The cases where we care about value-based equality come up a lot. Very often with Linq (GroupBy as we saw above, but also Distinct, Union, GroupJoin, Intersect, SequenceEqual, ToDictionary and ToLookup) and often with other uses (it's not like we weren't doing the things Linq does for us with enumerables in 2.0 and to some extent before then, anyone coding in 2.0 would have written half the methods in Enumerable themselves).

In all, we gain a lot from the way equality works with anonymous classes.

In the off-chance that someone really wants reference equality, == using reference equality means they still have that, so we don't lose anything. It's the way to go.

*The default implementation of Equals() and GetHashCode() has an optimisation that let's it use a binary match in cases where it's safe to do so. Unfortunately there's a bug that makes it sometimes mis-identify some cases as safe for this faster approach when they aren't (or at least it used to, maybe it was fixed). A common case is if you have a decimal field, in a struct, then it'll consider some instances with equivalent fields as unequal.

ReferenceURL : https://stackoverflow.com/questions/12123512/why-does-the-equals-implementation-for-anonymous-types-compare-fields

반응형