0Pricing
C# Academy · Lekcja

Przeciążanie operatorów porównania

Implementuj operatory ==, != oraz operatory porządku.

Przeciążanie operatorów porównania to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.

Operatory porównania i równość

Po przeciążeniu operatora == należy również przeciążyć !=, a także przesłonić Equals i GetHashCode, aby wszystkie formy równości pozostały spójne. C# wymusza parę tych operatorów na etapie kompilacji.

Przeciążanie == i !=

Kompilator wymaga, aby operatory == i != były zdefiniowane razem. Każdy z nich zwraca wartość bool opisującą, czy operandy są uznawane za równe.

using System;

public struct Point
{
    public int X, Y;
    public Point(int x, int y) { X = x; Y = y; }

    public static bool operator ==(Point a, Point b) => a.X == b.X && a.Y == b.Y;
    public static bool operator !=(Point a, Point b) => !(a == b);

    public override bool Equals(object obj) => obj is Point p && this == p;
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Point(1, 2) == new Point(1, 2));
        Console.WriteLine(new Point(1, 2) != new Point(3, 4));
    }
}

Zawsze należy też przesłonić Equals

Operatory są rozstrzygane w czasie kompilacji na podstawie typu statycznego, natomiast Equals jest metodą wirtualną używaną przez kolekcje. Należy przesłonić Equals, aby słowniki i List.Contains były zgodne z operatorem ==.

using System;

public struct Point
{
    public int X, Y;
    public Point(int x, int y) { X = x; Y = y; }
    public static bool operator ==(Point a, Point b) => a.X == b.X && a.Y == b.Y;
    public static bool operator !=(Point a, Point b) => !(a == b);
    public override bool Equals(object obj) => obj is Point p && this == p;
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    public static void Main()
    {
        object a = new Point(1, 2);
        object b = new Point(1, 2);
        Console.WriteLine(a.Equals(b));
    }
}

GetHashCode musi być zgodny z równością

Jeśli dwie wartości są równe, muszą zwracać ten sam kod skrótu. W przeciwnym razie kolekcje oparte na kodach skrótu, takie jak Dictionary i HashSet, będą działać niepoprawnie.

using System;
using System.Collections.Generic;

public struct Point
{
    public int X, Y;
    public Point(int x, int y) { X = x; Y = y; }
    public static bool operator ==(Point a, Point b) => a.X == b.X && a.Y == b.Y;
    public static bool operator !=(Point a, Point b) => !(a == b);
    public override bool Equals(object obj) => obj is Point p && this == p;
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    public static void Main()
    {
        var set = new HashSet<Point> { new Point(1, 2) };
        Console.WriteLine(set.Contains(new Point(1, 2)));
    }
}

Operatory porządku < i >

Można również przeciążać operatory relacyjne. Podobnie jak w przypadku równości, operatory < i > muszą być zdefiniowane razem, podobnie jak <= i >=.

using System;

public struct Weight
{
    public int Grams;
    public Weight(int g) { Grams = g; }

    public static bool operator <(Weight a, Weight b) => a.Grams < b.Grams;
    public static bool operator >(Weight a, Weight b) => a.Grams > b.Grams;

    public override string ToString() => Grams + "g";
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Weight(100) < new Weight(200));
        Console.WriteLine(new Weight(300) > new Weight(200));
    }
}

Równość typów referencyjnych

W przypadku klas równość wartości oznacza porównywanie pól, a nie referencji. Z null należy postępować ostrożnie, aby uniknąć zgłoszenia wyjątku wewnątrz operatora.

using System;

public class Person
{
    public string Name;
    public Person(string name) { Name = name; }

    public static bool operator ==(Person a, Person b)
    {
        if (ReferenceEquals(a, b)) return true;
        if (a is null || b is null) return false;
        return a.Name == b.Name;
    }
    public static bool operator !=(Person a, Person b) => !(a == b);
    public override bool Equals(object obj) => this == (obj as Person);
    public override int GetHashCode() => Name?.GetHashCode() ?? 0;
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Person("Ann") == new Person("Ann"));
    }
}

Spójność we wszystkich formach

Wszystkie sposoby sprawdzania równości powinny dawać ten sam wynik: ==, Equals i kod skrótu. Należy zaimplementować == za pomocą Equals lub odwrotnie, aby istniało jedno źródło prawdy.

using System;

public struct Id
{
    public int Value;
    public Id(int value) { Value = value; }
    public bool Equals(Id other) => Value == other.Value;
    public override bool Equals(object obj) => obj is Id other && Equals(other);
    public override int GetHashCode() => Value;
    public static bool operator ==(Id a, Id b) => a.Equals(b);
    public static bool operator !=(Id a, Id b) => !a.Equals(b);
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Id(7) == new Id(7));
        Console.WriteLine(new Id(7).Equals(new Id(8)));
    }
}

Operatory a Equals w kolekcjach

Wyszukiwanie na liście korzysta z Equals, a nie z ==, ponieważ działa na referencjach typu object. Jeśli przeciążą Państwo tylko == i zapomną o Equals, wyszukiwanie użyje tożsamości referencji i może się nie powieść.

using System;
using System.Collections.Generic;

public struct Tag
{
    public string Name;
    public Tag(string name) { Name = name; }
    public override bool Equals(object obj) => obj is Tag t && t.Name == Name;
    public override int GetHashCode() => Name?.GetHashCode() ?? 0;
    public static bool operator ==(Tag a, Tag b) => a.Equals(b);
    public static bool operator !=(Tag a, Tag b) => !a.Equals(b);
}

public class Program
{
    public static void Main()
    {
        var list = new List<Tag> { new Tag("red"), new Tag("blue") };
        Console.WriteLine(list.Contains(new Tag("blue")));
    }
}

Poprawne usuwanie ostrzeżeń kompilatora

Przesłanianie Equals i GetHashCode wraz z operatorami to nie tylko kwestia dobrych obyczajów; kompilator ostrzega, jeśli przeciążą Państwo == bez przesłonięcia Equals. Wykonanie obu czynności usuwa ostrzeżenie i zapobiega błędom.

using System;

public struct Cell
{
    public int Row, Col;
    public Cell(int r, int c) { Row = r; Col = c; }
    public override bool Equals(object obj) => obj is Cell c && c.Row == Row && c.Col == Col;
    public override int GetHashCode() => HashCode.Combine(Row, Col);
    public static bool operator ==(Cell a, Cell b) => a.Equals(b);
    public static bool operator !=(Cell a, Cell b) => !a.Equals(b);
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Cell(0, 0) == new Cell(0, 0));
    }
}

Połączenie wszystkiego

Dobrze zaprojektowany typ z obsługą równości definiuje == i !=, przesłania Equals i GetHashCode oraz kieruje je wszystkie przez jedno porównanie. To złoty standard dla typów reprezentujących wartości.

using System;
using System.Collections.Generic;

public struct Color
{
    public int R, G, B;
    public Color(int r, int g, int b) { R = r; G = g; B = b; }
    public override bool Equals(object obj) => obj is Color c && c.R == R && c.G == G && c.B == B;
    public override int GetHashCode() => HashCode.Combine(R, G, B);
    public static bool operator ==(Color a, Color b) => a.Equals(b);
    public static bool operator !=(Color a, Color b) => !a.Equals(b);
}

public class Program
{
    public static void Main()
    {
        var dict = new Dictionary<Color, string> { [new Color(255, 0, 0)] = "red" };
        Console.WriteLine(dict[new Color(255, 0, 0)]);
    }
}

Proszę spróbować samodzielnie

Proszę zaimplementować w pełni spójną strukturę z obsługą równości i sprawdzić jednocześnie jej działanie jako klucza słownika oraz z operatorem ==.

using System;
using System.Collections.Generic;

public struct GridPos
{
    public int Row, Col;
    public GridPos(int r, int c) { Row = r; Col = c; }
    public override bool Equals(object obj) => obj is GridPos p && p.Row == Row && p.Col == Col;
    public override int GetHashCode() => HashCode.Combine(Row, Col);
    public static bool operator ==(GridPos a, GridPos b) => a.Equals(b);
    public static bool operator !=(GridPos a, GridPos b) => !a.Equals(b);
}

public class Program
{
    public static void Main()
    {
        var grid = new Dictionary<GridPos, string> { [new GridPos(1, 1)] = "player" };
        Console.WriteLine(new GridPos(1, 1) == new GridPos(1, 1));
        Console.WriteLine(grid[new GridPos(1, 1)]);
    }
}

Szybkie sprawdzenie

Proszę przypomnieć sobie zasady przeciążania równości.

Podsumowanie

Przeciążanie operatorów porównania wymaga zachowania spójności.

  • Operatory == i != muszą być zdefiniowane razem; podobnie </>.
  • Należy przesłonić Equals i GetHashCode, aby były zgodne z ==.
  • Równe wartości muszą mieć ten sam kod skrótu.
  • Należy kierować wszystkie operacje sprawdzania równości przez jedną metodę porównującą, aby uniknąć rozbieżności.

Często zadawane pytania

Czy lekcja „Przeciążanie operatorów porównania” jest bezpłatna?

Tak — pełny tekst „Przeciążanie operatorów porównania” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Przeciążanie operatorów porównania”?

Implementuj operatory ==, != oraz operatory porządku. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C# Academy?

Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Przeciążanie operatorów porównania”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?

Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Przeciążanie operatorów arytmetycznych
  2. Przeciążanie operatorów porównania
  3. Konwersje definiowane przez użytkownika
  4. Dobre praktyki przeciążania operatorów
← Powrót do C# Academy