Udostępnij za pośrednictwem


Przeciążanie operatorów — predefiniowane jednoargumentowe operatory, operatory arytmetyczne, operatory równości i porównawcze operatory

Typ zdefiniowany przez użytkownika może przeciążać wstępnie zdefiniowany operator języka C#. Oznacza to, że typ może zapewnić niestandardową implementację operacji, jeśli jeden lub oba operandy są tego typu. Sekcja Przeciążenia operatorów pokazuje, które operatory języka C# mogą być przeciążone.

Użyj słowa kluczowego operator, aby zadeklarować operator. Deklaracja operatora musi spełniać następujące reguły:

  • Zawiera public modyfikator.
  • Operator jednoargumentowy ma jeden parametr wejściowy. Operator binarny ma dwa parametry wejściowe. W każdym przypadku co najmniej jeden parametr musi mieć typ T lub T? gdzie T jest typem zawierającym deklarację operatora.
  • Zawiera static modyfikator, z wyjątkiem operatorów przypisania skomplikowanego, takich jak +=.
  • Operatory inkrementacji (++) i dekrementacji (--) można zaimplementować jako metody statyczne lub metody instancji.

W poniższym przykładzie zdefiniowano uproszczoną strukturę reprezentującą liczbę racjonalną. Struktura przeciąża niektóre operatory arytmetyczne:

public struct Fraction
{
    private int numerator;
    private int denominator;

    public Fraction(int numerator, int denominator)
    {
        if (denominator == 0)
        {
            throw new ArgumentException("Denominator cannot be zero.", nameof(denominator));
        }
        this.numerator = numerator;
        this.denominator = denominator;
    }

    public static Fraction operator +(Fraction operand) => operand;
    public static Fraction operator -(Fraction operand) => new Fraction(-operand.numerator, operand.denominator);

    public static Fraction operator +(Fraction left, Fraction right)
        => new Fraction(left.numerator * right.denominator + right.numerator * left.denominator, left.denominator * right.denominator);

    public static Fraction operator -(Fraction left, Fraction right)
        => left + (-right);

    public static Fraction operator *(Fraction left, Fraction right)
        => new Fraction(left.numerator * right.numerator, left.denominator * right.denominator);

    public static Fraction operator /(Fraction left, Fraction right)
    {
        if (right.numerator == 0)
        {
            throw new DivideByZeroException();
        }
        return new Fraction(left.numerator * right.denominator, left.denominator * right.numerator);
    }

    // Define increment and decrement to add 1/den, rather than 1/1.
    public static Fraction operator ++(Fraction operand)
        => new Fraction(operand.numerator++, operand.denominator);

    public static Fraction operator --(Fraction operand) =>
        new Fraction(operand.numerator--, operand.denominator);

    public override string ToString() => $"{numerator} / {denominator}";

    // New operators allowed in C# 14:
    public void operator +=(Fraction operand) =>
        (numerator, denominator ) =
        (
            numerator * operand.denominator + operand.numerator * denominator,
            denominator * operand.denominator
        );

    public void operator -=(Fraction operand) =>
        (numerator, denominator) =
        (
            numerator * operand.denominator - operand.numerator * denominator,
            denominator * operand.denominator
        );

    public void operator *=(Fraction operand) =>
        (numerator, denominator) =
        (
            numerator * operand.numerator,
            denominator * operand.denominator
        );

    public void operator /=(Fraction operand)
    {
        if (operand.numerator == 0)
        {
            throw new DivideByZeroException();
        }
        (numerator, denominator) =
        (
            numerator * operand.denominator,
            denominator * operand.numerator
        );
    }

    public void operator ++() => numerator++;

    public void operator --() => numerator--;
}

public static class OperatorOverloading
{
    public static void Main()
    {
        var a = new Fraction(5, 4);
        var b = new Fraction(1, 2);
        Console.WriteLine(-a);   // output: -5 / 4
        Console.WriteLine(a + b);  // output: 14 / 8
        Console.WriteLine(a - b);  // output: 6 / 8
        Console.WriteLine(a * b);  // output: 5 / 8
        Console.WriteLine(a / b);  // output: 10 / 4
    }
}

Powyższy przykład można rozszerzyć , definiując niejawną konwersję z int na Fraction. Następnie przeciążone operatory obsługują argumenty tych dwóch typów. Oznacza to, że możliwe byłoby dodanie liczby całkowitej do ułamka i uzyskanie ułamka w wyniku.

Możesz również użyć słowa kluczowego operator do definiowania niestandardowej konwersji typu. Aby uzyskać więcej informacji, zobacz Operatory konwersji zdefiniowane przez użytkownika.

Operatory przeciążalne

W poniższej tabeli przedstawiono operatory, które mogą być przeciążone:

Operatorów Notatki
+x, -x, , !x, ~x++, , --, , truefalse Operatory true i false muszą być ze sobą przeciążone.
x + y, , x - y, x * y, x / y, , x % y
x & y, , x | y, , x ^ y
x << y, , x >> yx >>> y
x == y, , x != y, x < y, x > y, , x <= yx >= y Muszą być przeciążone w parach w następujący sposób: == i !=, < i >, <= i >=.
+=, -=, , , *=/=%=&=\|=^=<<=>>=>>>= Operatory przypisania złożonego mogą być przeciążone w języku C# 14 lub nowszym.

Przeciążony operator przypisania złożonego musi przestrzegać następujących reguł:

  • Musi zawierać public modyfikator.
  • Nie może zawierać static modyfikatora.
  • Zwracany typ musi mieć wartość void.
  • Deklaracja musi zawierać jeden parametr, który reprezentuje prawą stronę przypisania złożonego.

Począwszy od C# 14, operatory inkrementacji (++) i dekrementacji (--) mogą być przeciążone jako członkowie instancji. Operatorzy wystąpień mogą zwiększyć wydajność, unikając tworzenia nowego wystąpienia. Operator instancji musi przestrzegać następujących reguł:

  • Musi zawierać public modyfikator.
  • Nie może zawierać static modyfikatora.
  • Zwracany typ musi mieć wartość void.
  • Nie można zadeklarować żadnych parametrów, nawet jeśli te parametry mają wartość domyślną.

Nie przeciążalne operatory

W poniższej tabeli przedstawiono operatory, których nie można przeciążyć:

Operatorów Alternatywy
x && y, x || y Przeciąż zarówno operatory true i false, jak i operatory & lub |. Aby uzyskać więcej informacji, zobacz Operatory logiczne warunkowe zdefiniowane przez użytkownika.
a[i], a?[i] Zdefiniuj indeksator.
(T)x Zdefiniuj konwersje typów niestandardowych wykonywane przez wyrażenie rzutowane. Aby uzyskać więcej informacji, zobacz Operatory konwersji zdefiniowane przez użytkownika.
^x, x = y, , x.y, x?.yc ? t : f, , x ?? y, , ??= y
x..y, x->y, =>, f(x), as, await, checked, unchecked, default, delegate, is, nameof, new
sizeof, , stackalloc, switch, , typeofwith
Żaden.

Przed C# 14 nie można przeciążyć operatorów złożonych. Przeciążenie odpowiedniego operatora binarnego niejawnie przeciąża odpowiedni operator przypisania złożonego.

Rozpoznawanie przeciążenia operatora

Ważne

Ta sekcja dotyczy języka C# 14 i nowszych. Przed wprowadzeniem C# 14 operatory złożone przypisania definiowane przez użytkownika oraz operatory inkrementacji i dekrementacji obiektów nie były dozwolone.

Jeśli x jest sklasyfikowana jako zmienna w wyrażeniu przypisania złożonego, takim jak x «op»= y, operatory instancji są preferowane nad jakimikolwiek operatorami statycznymi dla «op». Jeśli dla typu «op»= nie jest zadeklarowany przeciążony operator lub jeśli xx nie jest klasyfikowany jako zmienna, są używane operatory statyczne.

Jeśli operator postfiksowy ++x nie jest klasyfikowany jako zmienna lub jeśli używane jest wyrażenie x++, to wystąpienie operator++ zostaje pominięte. W przeciwnym razie wystąpienie operator ++ będzie preferowane. Na przykład

x++; // Instance operator++ preferred.
y = x++; // instance operator++ isn't considered.

Powodem tej reguły jest przypisanie y do wartości xprzed jej przyrostem. Kompilator nie może określić tego w przypadku implementacji zdefiniowanej przez użytkownika w typie referencyjnym.

Dla operatora prefiksu ++, jeśli x jest klasyfikowany jako zmienna w ++x, operator instancji jest preferowany nad statycznym operatorem jednoargumentowym.

Specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz następujące sekcje specyfikacji języka C#:

Zobacz także