Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Tips/Råd
Den här artikeln är en del av avsnittet Grunderna för utvecklare som redan känner till minst ett programmeringsspråk och lär sig C#. Om du inte har använt programmering tidigare börjar du med självstudierna Komma igång först.
Kommer du från ett annat språk? I Java testar == objekt och JavaScript === på objekt identitet, inte innehåll. C#-klasser fungerar på samma sätt som standard. I Python anropar ==__eq__ och jämför innehållet som standard, på samma sätt som C# records jämförs. C# -structs jämförs också efter värde när du anropar Equals.
C# särskiljer två typer av likhet. Värdeekvivalens innebär att två instanser är lika när de innehåller samma data. Referensjämlikhet innebär att två variabler bara är lika med när de pekar på samma objekt i minnet. Det här villkoret kallas även identitet. Värdetyper jämför vanligtvis data och referenstyper jämför vanligtvis identitet. Typförfattare kan ändra dessa standardvärden, men den mentala modellen förhindrar subtila buggar där två objekt som ser identiska ut inte anses vara lika, eller där en mutation genom en variabel tyst ändrar vad en annan variabel ser.
Värdetyper, referenstyper och standardvärden för likhet
Varje typ i C# är antingen en värdetyp eller en referenstyp. En värdetyp innehåller sina data direkt i variabeln. En referenstyp innehåller en referens till ett objekt. När du tilldelar en variabel av referenstyp till en annan variabel refererar båda variablerna till samma objekt. Mer information om värdetyper och referenstyper finns i Översikt över typsystem.
Standardjämlikhetsbeteendet följer vanligtvis typen av typ:
-
Inbyggda numeriska typer och uppräkningar är värdetyper. Två
intvariabler är lika med när deras numeriska värden matchar. -
Structs är värdetyper. Ett vanligt
structanvänder värdebaserad likhet när du anropar Equals. - Tupplar är värdetyper. Två tupler är lika när alla elements värden överensstämmer.
-
Klasser är referenstyper. En vanlig klass använder referensjämlikhet, så
==och Equals testar om två variabler pekar på samma objekt.
En klass använder referensjämlikhet. Två separata objekt med samma data är inte lika med, men två variabler som refererar till samma objekt är lika med:
var order1 = new Order(42, "Shoes");
var order2 = new Order(42, "Shoes");
Console.WriteLine(order1 == order2); // => False
Console.WriteLine(order1.Equals(order2)); // => False
Console.WriteLine(ReferenceEquals(order1, order2)); // => False
Order order3 = order1;
Console.WriteLine(order1 == order3); // => True
A struct visar värdejämlikhet via Equals. Två struct-instanser är lika när deras fält överensstämmer:
var pt1 = new Point(3, 4);
var pt2 = new Point(3, 4);
Console.WriteLine(pt1.Equals(pt2)); // => True
Vanliga structs får ingen fördefinierad == operator. Att skriva p1 == p2 på en vanlig struct kompileras endast om structen deklarerar sin egen operator ==. Om du behöver operatorjämförelser för en struct definierar == du och != som ett par och håller dem konsekventa med Equals och GetHashCode.
Tupplar är också värdetyper. Två tupplar är lika när varje elements värde är samma. Elementnamn i en namngiven tupel är bara till hjälp vid kompilering och tas inte med i jämförelsen. Endast positioner och värden spelar roll:
var t1 = (Name: "Grace", Role: "Engineer");
var t2 = (Name: "Grace", Role: "Engineer");
Console.WriteLine(t1 == t2); // => True
Mer information om tuppelns syntax och dekonstruktion finns i Tupplar och dekonstruktion.
Använd Object.ReferenceEquals för att testa identiteten direkt
ReferenceEquals testar alltid identiteten oavsett hur en typ åsidosätter Equals eller överbelastar ==. Använd den som en identitetsdiagnostik när du behöver bekräfta om två variabler pekar på exakt samma objekt:
var doc1 = new Document("Report");
var doc2 = new Document("Report");
var doc3 = doc1;
Console.WriteLine(ReferenceEquals(doc1, doc2)); // => False
Console.WriteLine(ReferenceEquals(doc1, doc3)); // => True
En vanlig användning är i en Equals-överskuggning för att hoppa över den fullständiga jämförelsen: när båda argumenten refererar till samma objekt är de alltid lika utan att kontrollera de enskilda fälten.
Note
När variabler skrivs som ett gränssnitt== kontrollerar du om gränssnittsvariablerna refererar till samma objekt. Ett anrop till Equals kör fortfarande implementeringen av det underliggande objektet.
Note
ReferenceEquals returnerar false alltid vid jämförelse av värdetyper, även om båda argumenten innehåller samma värden. Det här beteendet beror på att varje värdetypsargument rutas separat i ett separat heap-objekt när det skickas till ReferenceEquals.
Typer kan definiera olika likhetssemantik
Typer kan definiera likhetssemantik som skiljer sig från standardbeteendet. Den vanligaste orsaken är att implementera värdejämlikhet. Om du skapar en typ som representerar data, till exempel ett bankkonto, en produkt i lagret eller en användare i ett system, bör du överväga instanser med samma värden som lika med. Välj posttyper för att implementera värdejämlikhet och kompilatorn genererar alla nödvändiga likhetsmedlemmar åt dig.
Note
Strängar är klasser, men == och Equals jämför stränginnehåll, inte identitet.
-
==: likhetsoperatorn. De flesta typer använder den här operatorn som det primära sättet att kontrollera likhet. Dess beteende beror på om typen har en inbyggd eller användardefinierad==operator. -
!=: ojämlikhetsoperatorn. När en typ definierar en användardefinierad==operator måste den också definiera!=. - Equals: en virtuell metod som ärvs av varje typ. Du kan åsidosätta den för att ändra likhetssemantik för en typ.
- GetHashCode: en virtuell metod som används av hash-baserade samlingar. När två värden är lika måste deras hash-koder också vara lika med.
- ReferenceEquals: en statisk metod som alltid testar identitet.
Använda poster för värdejämlikhet
Använd modifieraren record för att ge typen datafokuserad värdejämlikhet när typen kan vara en record. Kompilatorn genererar Equals, GetHashCode och ==/!= medlemmar som jämför värdet för varje deklarerad egenskap.
A record class är fortfarande en referenstyp, men den jämför värden i stället för identitet:
var person1 = new Person("Ada", "Lovelace");
var person2 = new Person("Ada", "Lovelace");
Console.WriteLine(person1 == person2); // => True
Console.WriteLine(person1.Equals(person2)); // => True
Console.WriteLine(ReferenceEquals(person1, person2)); // => False
ReferenceEquals bekräftar att person1 och person2 är olika objekt i minnet, medan == och Equals returnerar True eftersom den kompilatorgenererade likheten jämför egenskapsvärden.
Samma kompilatorgenerering gäller för record struct typer:
var dim1 = new Dimension(1920, 1080);
var dim2 = new Dimension(1920, 1080);
Console.WriteLine(dim1 == dim2); // => True
Console.WriteLine(dim1.Equals(dim2)); // => True
Recordtyper genererar hela uppsättningen för likhet för den egna typen. Både record class och record struct typer åsidosätter Equals och GetHashCode. De genererar också == och operatorer för !=, samt en typanpassad Equals-metod för recordtypen. Till skillnad från en vanlig struct stöder en record struct därför automatiskt == och !=. Mer information om recordtyper och deras semantik för likhet finns i Records.
Poster med medlemmar av referenstyp
Rekordjämlikhet använder medlemmarnas egna jämställdhetssemantik. Varje egenskap eller fält jämförs med sin egen Equals metod. För de flesta skalära värden, till exempel int, string eller DateTime, jämför det här tillvägagångssättet värdena för postens medlemmar. Det subtila uppstår med vanliga föränderliga samlingar som List<T> eller T[]: dessa typer jämförs efter referens, så två record-instanser som innehåller olika listobjekt med samma innehåll anses inte vara lika enligt den syntetiserade record-likheten.
var playlist1 = new Playlist("Chill", new List<string> { "Song A", "Song B" });
var playlist2 = new Playlist("Chill", new List<string> { "Song A", "Song B" });
Console.WriteLine(playlist1.Equals(playlist2)); // => False (different List instances)
Console.WriteLine(playlist1.Tracks.SequenceEqual(playlist2.Tracks)); // => True
playlist1 och playlist2 är separata List<string> instanser. Även om innehållet är identiskt returnerar Equals, false.
När du behöver att recordlikhet ska återspegla samlingens innehåll har du några alternativ:
-
Implementera
IEquatable<T>i posten och åsidosättEqualsför att använda Enumerable.SequenceEqual för elementen i samlingen. -
Använd en samlingstyp med värdejämlikhet , till exempel en anpassad
IEqualityComparer<T>eller en typ vars egnaEqualsjämför element. - Utforma med identitet i åtanke: om recorden representerar en entitet snarare än ett rent värde kan referenslikhet för dess samlingsmedlemmar vara avsiktlig.
Important
Manuell implementering av likhet är sällsynt idag i C#. Records hanterar automatiskt det vanliga fallet med värdelikhet. Om du behöver implementera likvärdighet manuellt, till exempel eftersom din typ måste ärva från en basklass som inte är en post, se Implementera likvärdighet själv när en typ inte kan vara en post i språkreferensen.
Se även
- Översikt över typsystem
- Klasser
- Strukturer
- Register
- Tupplar och dekonstruktion.
- Likhetsoperatorer (språkreferens).
- Likhet i klasshierarkier – avancerad vägledning om polymorf likhet.
- Aritmetiska operatorer, jämförelseoperatorer, logiska operatorer och tilldelningsoperatorer – likhetsoperatorns undersökning tillsammans med aritmetiska, logiska operatorer och tilldelningsoperatorer.