Likhetsoperatorer – testa om två objekt är lika med eller inte

Operatörerna == (likhet) och!=(ojämlikhet) kontrollerar om deras operander är lika med eller inte. Värdetyper är lika med när deras innehåll är lika med. Referenstyper är lika med när de två variablerna refererar till samma lagring.

C#-språkreferensen dokumenterar den senaste versionen av C#-språket. Den innehåller även inledande dokumentation för funktioner i offentliga förhandsversioner för den kommande språkversionen.

Dokumentationen identifierar alla funktioner som först introducerades i de tre senaste versionerna av språket eller i aktuella offentliga förhandsversioner.

Tips/Råd

Information om när en funktion först introducerades i C# finns i artikeln om språkversionshistoriken för C#.

Du kan använda mönstermatchningsoperatorn is som ett alternativ till ett == test när du testar mot ett konstant värde. Operatorn is använder standardjämlikhetssemantiken för alla värde- och referenstyper.

Likhetsoperator ==

Likhetsoperatorn == returnerar true om dess operander är lika, false annars.

Likhet mellan värdetyper

Operander för de inbyggda värdetyperna är lika med om deras värden är lika med:

int a = 1 + 2 + 3;
int b = 6;
Console.WriteLine(a == b);  // output: True

char c1 = 'a';
char c2 = 'A';
Console.WriteLine(c1 == c2);  // output: False
Console.WriteLine(c1 == char.ToLower(c2));  // output: True

Kommentar

För operatorerna ==, <, >, <=och >= om någon av operanderna inte är ett tal (Double.NaN eller Single.NaN) är falseresultatet av åtgärden . Det villkoret NaN innebär att värdet inte är större än, mindre än eller lika med något annat double (eller float) värde, inklusive NaN. Mer information och exempel finns i referensartikeln Double.NaN eller Single.NaN .

Två operander av samma uppräkningstyp är lika med om motsvarande värden för den underliggande integraltypen är lika.

Användardefinierade structtyper stöder inte operatorn == som standard. För att stödja operatorn == måste en användardefinierad struct överbelasta den.

C#- tupplar har inbyggt stöd för operatorerna == och != . Mer information finns i avsnittet Tuple equality i artikeln Tuple types (Tuppelns typer).

Likhet mellan referenstyper

Som standard är operander av referenstyp, exklusive poster, lika om de refererar till samma objekt:

public class ReferenceTypesEquality
{
    public class MyClass
    {
        private int id;

        public MyClass(int id) => this.id = id;
    }

    public static void Main()
    {
        var a = new MyClass(1);
        var b = new MyClass(1);
        var c = a;
        Console.WriteLine(a == b);  // output: False
        Console.WriteLine(a == c);  // output: True
    }
}

Som föregående exempel visar stöder användardefinierade referenstyper operatorn == som standard. En referenstyp kan dock överbelasta operatorn == . Om en referenstyp överbelastar operatorn == använder du Object.ReferenceEquals metoden för att kontrollera om två referenser av den typen refererar till samma objekt.

Likhet mellan posttyper

Posttyper stöder operatorerna == och != som som standard ger värdejämlikhetssemantik. Det innebär att två postoperor är lika när båda är null eller motsvarande värden för alla fält och automatiskt implementerade egenskaper är lika.

public class RecordTypesEquality
{
    public record Point(int X, int Y, string Name);
    public record TaggedNumber(int Number, List<string> Tags);

    public static void Main()
    {
        var p1 = new Point(2, 3, "A");
        var p2 = new Point(1, 3, "B");
        var p3 = new Point(2, 3, "A");

        Console.WriteLine(p1 == p2);  // output: False
        Console.WriteLine(p1 == p3);  // output: True

        var n1 = new TaggedNumber(2, new List<string>() { "A" });
        var n2 = new TaggedNumber(2, new List<string>() { "A" });
        Console.WriteLine(n1 == n2);  // output: False
    }
}

Som föregående exempel visar jämförs likheten mellan referenstypmedlemmar med hjälp av deras specifika likhetsimplementeringar.

Strängjämlikhet

Två strängoperor är lika med varandra när båda är null eller båda stränginstanserna har samma längd och har identiska tecken i varje teckenposition:

string s1 = "hello!";
string s2 = "HeLLo!";
Console.WriteLine(s1 == s2.ToLower());  // output: True

string s3 = "Hello!";
Console.WriteLine(s1 == s3);  // output: False

Jämförelse av strängjämlikhet är skiftlägeskänsliga ordningstalsjämförelser. Mer information om strängjämförelse finns i Så här jämför du strängar i C#.

Delegera likhet

Två delegerade operander av samma körningstyp är lika när båda är null eller deras anropslistor är lika långa och har samma poster i varje position:

Action a = () => Console.WriteLine("a");

Action b = a + a;
Action c = a + a;
Console.WriteLine(object.ReferenceEquals(b, c));  // output: False
Console.WriteLine(b == c);  // output: True

Viktigt!

Lika poster i en anropslista innehåller alla fasta parametrar i anropet, inklusive mottagaren. Mottagaren är instansen av ett objekt som representeras av this när posten anropas.

var o1 = new object();
var o2 = new object();
var d1 = o1.ToString;
var d2 = o2.ToString;
Console.WriteLine(object.ReferenceEquals(d1, d2));  // output: False
Console.WriteLine(d1 == d2);  // output: False (different receivers)

Mer information finns i avsnittet Delegera likhetsoperatorer i C#-språkspecifikationen.

Ombud som kommer från utvärdering av semantiskt identiska lambda-uttryck är inte lika med, vilket visas i följande exempel:

Action a = () => Console.WriteLine("a");
Action b = () => Console.WriteLine("a");

Console.WriteLine(a == b);  // output: False
Console.WriteLine(a + b == a + b);  // output: True
Console.WriteLine(b + a == a + b);  // output: False

Ojämlikhetsoperator !=

Ojämlikhetsoperatorn != returnerar true om dess operander inte är lika med, och false annars. För operanderna för de inbyggda typerna ger uttrycket x != y samma resultat som uttrycket !(x == y). Mer information om typjämlikhet finns i avsnittet Likhetsoperator .

I följande exempel visas hur du använder operatorn != :

int a = 1 + 1 + 2 + 3;
int b = 6;
Console.WriteLine(a != b);  // output: True

string s1 = "Hello";
string s2 = "Hello";
Console.WriteLine(s1 != s2);  // output: False

object o1 = 1;
object o2 = 1;
Console.WriteLine(o1 != o2);  // output: True

Likhet i klasshierarkier

Arkivhandlingar hanterar arv korrekt utan manuellt arbete. Den kompilatorgenererade likheten kontrollerar både körningstypen och alla deklarerade egenskaper, så att den automatiskt uppfyller kraven på symmetri och transitivitet. Föredra record framför en manuell oförseglade hierarki när värdejämlikhet är målet.

Viktigt!

Använd record när det är möjligt – kompilatorn genererar alla nödvändiga likhetsmedlemmar åt dig. Manuell implementering behövs bara när din typ måste härledas från en icke-postklass eller har andra begränsningar som förhindrar record.

Implementera likhet själv när en typ inte kan vara en record

Här är en minimal manuell implementering för en värdetyp som inte kan vara en post:

class Color : IEquatable<Color>
{
    public Color(int r, int g, int b)
    {
        R = r;
        G = g;
        B = b;
    }

    public int R { get; }
    public int G { get; }
    public int B { get; }

    public bool Equals(Color? other) =>
        other is not null && R == other.R && G == other.G && B == other.B;

    public override bool Equals(object? obj) => obj is Color other && Equals(other);
    public override int GetHashCode() => HashCode.Combine(R, G, B);
}

Implementeringen innehåller tre obligatoriska medlemmar: Equals(T?) som kärnjämförelse, override Equals(object?) för anrop på objektnivå och override GetHashCode() så fungerar hash-baserade samlingar korrekt. HashCode.Combine är en bibliotekshjälp som skapar en hash från samma värden som används av Equals. Implementering IEquatable<T> (överbelastningen Equals(T?) ) är valfritt men undviker boxning när anropare redan har betongtypen.

När du också definierar == och !=kräver språket dem som ett par. Varningar CS0660 och CS0661 påminner dig om att hålla alla fyra medlemmarna konsekventa.

Med de tre medlemmarna på plats Equals ovan återspeglar värdejämlikhet, men == testar fortfarande identiteten eftersom ingen == operator har deklarerats ännu:

var red1 = new Color(255, 0, 0);
var red2 = new Color(255, 0, 0);

Console.WriteLine(red1.Equals(red2)); // => True
Console.WriteLine(red1 == red2);      // => False  (no == overload; identity check)

En korrekt implementering måste också uppfylla ekvivalenskontraktet (anta x, yoch z är inte null):

  1. Reflexiv: x.Equals(x) returnerar true.
  2. Symmetrisk: x.Equals(y) returnerar samma värde som y.Equals(x).
  3. Transitiv: om x.Equals(y) och y.Equals(z) är båda truemåste vara x.Equals(z)true.
  4. Konsekvent: efterföljande anrop för att x.Equals(y) returnera samma värde så länge inget av objekten ändras.
  5. Null-beteende: x.Equals(null) returnerar false; x.Equals(y) får inte utlösas när det anropas på en icke-null x.

Värdejämlikhet i en oförseglad klasshierarki kräver mer försiktighet än i en förseglad klass för att uppfylla de symmetriska och transitiva reglerna. Risken är att IEquatable<T>.Equals(T? other) sändningen följer den deklarerade typen (typen som skrivits i variabeldeklarationen) för variabeln, inte dess körningstyp. Om Shape deklarerar en icke-virtualEquals(Shape? other), anropar Shape.Equalsen variabel som skrivs som den som Shape innehåller en Circle vid körning - tyst ignorerar Circle-specifika fält. Två Circle objekt med olika radier kan jämföras som lika när de nås via en Shape variabel.

Rätt mönster kräver två samarbetskrav: gör den typerade metoden virtual så att varje härledd Equals klass kan utöka jämförelsen och lägga till ett GetType() == other.GetType() skydd i basklassimplementeringen så att objekt av olika körningstyper aldrig betraktas som lika.

Grundläggande klassimplementering

// Shape is an unsealed base class. Making Equals virtual and guarding with GetType()
// ensures a derived instance is never equal to an instance of a different runtime type.
class Shape : IEquatable<Shape>
{
    public string Color { get; }
    public Shape(string color) => Color = color;

    public override bool Equals(object? obj) => Equals(obj as Shape);

    // virtual so derived classes can override and augment the comparison
    public virtual bool Equals(Shape? other) =>
        other is not null &&
        GetType() == other.GetType() &&   // reject different runtime types
        Color == other.Color;

    // GetType() is included because equality requires matching runtime types
    public override int GetHashCode() => HashCode.Combine(GetType(), Color);

    public static bool operator ==(Shape? l, Shape? r) => l?.Equals(r) ?? r is null;
    public static bool operator !=(Shape? l, Shape? r) => !(l == r);
}

Viktiga punkter:

  • virtual skrivet : varje härledd Equalsklass åsidosätter den här metoden för att utöka jämförelsen med sina egna fält.
  • GetType() guard: GetType() == other.GetType() förhindrar att en Circle är lika med en Shape med samma färg och förhindrar att objekt av olika härledda typer är lika med varandra.
  • GetHashCode innehåller GetType(): eftersom två objekt bara är lika med när deras körningstyper matchar GetHashCode måste du hash-typen och datafälten. Om du utelämnar GetType() detta orsakar det felaktiga beteendet i Dictionary<TKey,TValue> och HashSet<T>.
  • == ombud till Equals: håller operator- och metodjämlikhet konsekventa.

Implementering av härledd klass

En härledd klass som lägger till fält åsidosätter den typerade Equals, genererar till sin egen typ, anropar base.Equalsoch jämför sedan sina egna fält:

class Circle : Shape
{
    public double Radius { get; }
    public Circle(string color, double radius) : base(color) => Radius = radius;

    public override bool Equals(object? obj) => Equals(obj as Shape);

    // Calls base.Equals to verify Color and runtime type, then adds Radius
    public override bool Equals(Shape? other) =>
        other is Circle c && base.Equals(c) && Radius == c.Radius;

    public override int GetHashCode() => HashCode.Combine(GetType(), Color, Radius);
}

base.Equals(c) framtvingar vakten GetType() och kontrollerar de delade fälten. Gjutningen via other is Circle c misslyckas snabbt när argumentet är av Shape någon annan härledd typ.

Användning via en bastypsvariabel

Shape circle1 = new Circle("red", 5.0);
Shape circle2 = new Circle("red", 7.0);
Shape circle3 = new Circle("red", 5.0);
Shape shape1  = new Shape("red");

Console.WriteLine(circle1.Equals(circle2)); // => False  (Radius differs)
Console.WriteLine(circle1.Equals(circle3)); // => True
Console.WriteLine(circle1.Equals(shape1));  // => False  (different runtime types)

Förseglade klasser är enklare

Du kan inte underklassa en sealed klass, så kompileringstids- och körningstyperna är alltid överens. Du behöver inte vakten eller virtual sändningenGetType(). IEquatable<T> Mönstret som visas i Implementera likhet själv när en typ inte kan vara en post är korrekt och slutfört för en förseglad klass.

Överlagring av operator

Du kan överbelasta operatorerna == och != i en användardefinierad typ. Om du överbelastar en av dessa två operatorer måste du även överbelasta den andra operatorn.

Du kan inte uttryckligen överbelasta operatorerna == och != i en posttyp. Om du vill ändra beteendet för operatorerna == och != för posttypen Timplementerar IEquatable<T>.Equals du metoden med följande signatur:

public virtual bool Equals(T? other);

Språkspecifikation för C#

Mer information finns i avsnittet Relations- och typtestningsoperatorer i C#-språkspecifikationen.

Mer information om likhet mellan posttyper finns i avsnittet Likhetsmedlemmar i C#-språkspecifikationen.

Se även