Waarom vergelijkt de Equals-implementatie voor anonieme typen velden?

Ik vraag me af waarom ontwerpers van de taal besloten om Equals op anonieme typen te implementeren, net zoals Equalsop waardetypen. Is het niet misleidend?

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
}

Op het eerste gezicht zouden alle afgedrukte booleaanse waarden onwaar moeten zijn. Maar regels met Equals-aanroepen retourneren verschillende waarden wanneer het type Personwordt gebruikt en het anonieme type wordt gebruikt.


Antwoord 1, autoriteit 100%

Instances van het anonieme type zijn onveranderlijke gegevenswaarden zonder gedrag of identiteit. Het heeft weinig zin om ernaar te verwijzen en ze te vergelijken. In die context denk ik dat het heel redelijk is om structurele gelijkheidsvergelijkingen voor hen te genereren.

Als u het vergelijkingsgedrag wilt wijzigen in iets aangepasts (referentievergelijking of hoofdletterongevoeligheid), kunt u Resharper gebruiken om het anonieme type om te zetten in een benoemde klasse. Resharper kan ook gelijkheidsleden genereren.

Er is ook een zeer praktische reden om dit te doen: Anonieme typen zijn handig om te gebruiken als hash-sleutels in LINQ-joins en -groeperingen. Om die reden hebben ze semantisch correcte implementaties Equalsen GetHashCodenodig.


Antwoord 2, autoriteit 51%

Voor het waarom zou je de taalontwerpers moeten vragen…

Maar ik vond dit in het artikel van Eric Lippert over Anonieme typen verenigen zich binnen een assemblage, deel twee

Een anoniem type geeft je een handige plek om een ​​kleine op te bergen
onveranderlijke set naam/waarde-paren, maar het geeft je meer dan dat. Het
geeft je ook een implementatie van Equals, GetHashCode en, de meeste
relevant voor deze discussie, ToString. (*)

Waar het waarom-gedeelte in de notitie komt:

(*) We geven je Equals en GetHashCode zodat je instanties kunt gebruiken
van anonieme typen in LINQ-query’s als sleutels om op uit te voeren
doet mee. LINQ to Objects implementeert joins met behulp van een hashtabel voor:
prestatieredenen, en daarom hebben we correcte implementaties nodig van
Is gelijk aan en GetHashCode.


Antwoord 3, autoriteit 29%

Het officiële antwoord van de C#-taalspecificatie (hier) :

De methoden Equals en GetHashcode op anonieme typen overschrijven de methoden die zijn geërfd van het object en worden gedefinieerd in termen van de Equals en GetHashcode van de eigenschappen, zodat twee instanties van hetzelfde anonieme type gelijk zijn als en slechts als al hun eigenschappen zijn gelijk.

(Mijn nadruk)

De andere antwoorden leggen uit waarom dit wordt gedaan.

Het is vermeldenswaard dat in VB.Netde implementatie is anders:

Een instantie van een anoniem type zonder sleuteleigenschappen is alleen gelijk aan zichzelf.

De sleuteleigenschappen moeten expliciet worden aangegeven bij het maken van een anoniem type object. De standaard is: geen sleutel, wat erg verwarrend kan zijn voor C#-gebruikers!

Deze objecten zijn niet gelijk in VB, maar zouden in C#-equivalente code staan:

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

Deze objecten worden geëvalueerd als “gelijk”:

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

Antwoord 4, autoriteit 12%

Omdat het ons iets nuttigs geeft. Overweeg het volgende:

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

Het werkt omdat de implementatie van Equals()en GetHashCode()voor anonieme typen werkt op basis van gelijkheid per veld.

  1. Dit betekent dat het bovenstaande dichter bij dezelfde query komt wanneer het wordt uitgevoerd tegen PersonInfoStoredat niet linq-to-objects is. (Nog steeds niet hetzelfde, het komt overeen met wat een XML-bron zal doen, maar niet met wat de meeste databases zouden opleveren).
  2. Het betekent dat we geen IEqualityComparerhoeven te definiëren voor elke aanroep naar GroupBywat group by echt moeilijk zou maken met anonieme objecten – het is mogelijk maar niet gemakkelijk om definieer een IEqualityComparer voor anonieme objecten – en verre van de meest natuurlijke betekenis.
  3. Bovenal veroorzaakt het in de meeste gevallen geen problemen.

Het derde punt is het onderzoeken waard.

Als we een waardetype definiëren, willen we natuurlijk een op waarden gebaseerd concept van gelijkheid. Hoewel we misschien een ander idee hebben van die op waarden gebaseerde gelijkheid dan de standaard, zoals het ongevoelig voor hoofdletters matchen van een bepaald veld, is de standaard natuurlijk verstandig (indien slecht in prestatie en in één geval bugs*). (Verwijzingsgelijkheid is in dit geval ook zinloos).

Als we een referentietype definiëren, willen we misschien wel of niet een op waarden gebaseerd concept van gelijkheid. De standaard geeft ons referentiegelijkheid, maar we kunnen dat gemakkelijk veranderen. Als we het veranderen, kunnen we het alleen voor Equalsen GetHashCodewijzigen of voor hen en ook ==.

Als we een anoniem type definiëren, oh wacht, we hebben het niet gedefinieerd, dat is wat anoniem betekent! De meeste scenario’s waarin we om referentiegelijkheid geven, zijn er niet meer. Als we een object lang genoeg vasthouden om ons later af te vragen of het hetzelfde is als een ander, hebben we waarschijnlijk niet te maken met een anoniem object. De gevallen waarin we om op waarden gebaseerde gelijkheid geven, komen veel naar voren. Heel vaak met Linq (GroupByzoals we hierboven zagen, maar ook Distinct, Union, GroupJoin, Intersect, SequenceEqual, ToDictionaryen ToLookup) en vaak met andere toepassingen (het is niet alsof we de dingen niet deden Linq doet voor ons met enumerables in 2.0 en tot op zekere hoogte daarvoor zou iedereen die codeert in 2.0 de helft van de methoden in Enumerablezelf hebben geschreven).

Al met al hebben we veel profijt van de manier waarop gelijkheid werkt met anonieme klassen.

In het geval dat iemand echt referentie-gelijkheid wil, ==het gebruik van referentie-gelijkheid betekent dat ze dat nog steeds hebben, dus we verliezen niets. Het is de juiste keuze.

*De standaardimplementatie van Equals()en GetHashCode()heeft een optimalisatie waardoor het een binaire overeenkomst laat gebruiken in gevallen waar het veilig is om dit te doen. Helaas is er een bug waardoor het soms sommige gevallen verkeerd identificeert als veilig voor deze snellere aanpak terwijl ze dat niet zijn (of in ieder geval vroeger, misschien is het opgelost). Een veelvoorkomend geval is dat als je een decimal-veld in een struct hebt, sommige instanties met equivalente velden als ongelijk worden beschouwd.

Other episodes