Freigeben über


Operatorüberladung – vordefinierte unäre, arithmetische, Gleichheits- und Vergleichsoperatoren

Ein benutzerdefinierter Typ kann einen vordefinierten C#-Operator überladen. Das heißt, ein Typ kann die benutzerdefinierte Implementierung eines Vorgangs bereitstellen, falls ein oder beide Operanden diesen Typ aufweisen. Im Abschnitt überladene Operatoren wird gezeigt, welche C#-Operatoren überladen werden können.

Verwenden Sie das operator Schlüsselwort, um einen Operator zu deklarieren. Eine Operatordeklaration muss die folgenden Regeln erfüllen:

  • Es enthält einen public-Modifikator.
  • Ein unärer Operator weist einen Eingabeparameter auf. Ein binärer Operator verfügt über zwei Eingabeparameter. Mindestens ein Parameter muss jeweils den Typ T oder T? haben, wobei T der Typ ist, der die Operatordeklaration enthält.
  • Er enthält den static Modifizierer, mit Ausnahme der zusammengesetzten Zuordnungsoperatoren, z. B. +=.
  • Die Operatoren inkrementieren (++) und dekrementieren (--) können als statische oder Instanzmethoden implementiert werden.

Im folgenden Beispiel wird eine vereinfachte Struktur definiert, die eine rationale Zahl darstellt. Die Struktur überlastet einige der arithmetischen Operatoren:

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
    }
}

Sie könnten das vorherige Beispiel erweitern, indem Sie eine implizite Konvertierung von int nach Fraction definieren. Anschließend würden überladene Operatoren Argumente dieser beiden Typen unterstützen. Das heißt, es wäre möglich, eine ganze Zahl zu einem Bruch hinzuzufügen und einen Bruch als Ergebnis zu erhalten.

Sie verwenden auch das operator Schlüsselwort, um eine benutzerdefinierte Typkonvertierung zu definieren. Weitere Informationen finden Sie unter Benutzerdefinierte Konvertierungsoperatoren.

Überladbare Operatoren

In der folgenden Tabelle sind die Operatoren aufgeführt, die überladen werden können:

Betriebspersonal Hinweise
+x, , -x!x, ~x, ++, --, , truefalse Die Operatoren true und false müssen zusammen überladen werden.
x + y, x - y, x * y, x / y, x % y
x & y, x | y, x ^ y
x << y, x >> yx >>> y
x == y, , x != yx < y, x > y, , x <= yx >= y Muss in Paaren wie folgt überladen werden: == und !=, < und >, <= und >=.
+=, -=, *=, /=, %=, &=, \|=, ^=, <<=, >>=, >>>= Die zusammengesetzten Zuweisungsoperatoren können in C# 14 und höher überladen werden.

Ein überladener Operator für eine zusammengesetzte Zuweisung muss diese Regeln befolgen:

  • Er muss den public Modifizierer enthalten.
  • Er kann den static Modifizierer nicht enthalten.
  • Der Rückgabetyp muss sein void.
  • Die Deklaration muss einen Parameter enthalten, der die rechte Seite der Verbundzuordnung darstellt.

Seit C# 14 können die Inkrement- (++) und Dekrementoperatoren (--) als Instanzmember überladen werden. Instanzoperatoren können die Leistung verbessern, indem sie die Erstellung einer neuen Instanz vermeiden. Ein Instanzoperator muss den folgenden Regeln entsprechen:

  • Er muss den public Modifizierer enthalten.
  • Er kann den static Modifizierer nicht enthalten.
  • Der Rückgabetyp muss sein void.
  • Sie kann keine Parameter deklarieren, auch wenn diese Parameter über einen Standardwert verfügen.

Nicht überladbare Operatoren

Die folgende Tabelle zeigt die Operatoren, die nicht überladen werden können:

Betriebspersonal Alternativen
x && y, x || y Überladen Sie sowohl die true- und false-Operatoren als auch die &- oder |-Operatoren. Weitere Informationen finden Sie unter benutzerdefinierte bedingte logische Operatoren.
a[i], a?[i] Definieren Sie einen Indexer.
(T)x Definieren Sie angepasste Konversionen, die von einem Cast-Ausdruck ausgeführt werden. Weitere Informationen finden Sie unter Benutzerdefinierte Konvertierungsoperatoren.
^x, x = y, x.y, x?.y, c ? t : f, x ?? y, ??= y
x..y, x->y, =>, f(x), as, await, checked, unchecked, default, delegate, is, nameof, new
sizeof, , stackallocswitch, , typeofwith
Keiner.

Vor C# 14 können die zusammengesetzten Operatoren nicht überladen werden. Durch das Überladen des entsprechenden binären Operators wird implizit der entsprechende Verbundzuordnungsoperator überladen.

Überladungsauflösung von Operatoren

Von Bedeutung

Dieser Abschnitt gilt für C# 14 und höher. Vor C# 14 sind benutzerdefinierte Verbundzuordnungsoperatoren und Instanzinkrementierer und Dekrementoperatoren nicht zulässig.

Wenn x in einem zusammengesetzten Zuordnungsausdruck wie x «op»= y als Variable klassifiziert wird, werden Instanzoperatoren gegenüber statischen Operatoren bevorzugt für «op». Wenn ein überladener «op»= Operator nicht für den Typ von x erklärt ist oder x nicht als Variable klassifiziert ist, werden die statischen Operatoren verwendet.

Wenn der Postfix-Operator ++x nicht als Variable klassifiziert wird oder der Ausdruck x++ verwendet wird, wird die Instanz operator++ ignoriert. Andernfalls erhält die Instanz operator ++ die Vorzug. Beispiel:

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

Der Grund für diese Regel ist, dass y dem Wert x vor dem Inkrementieren zugewiesen werden soll. Der Compiler kann dies für eine benutzerdefinierte Implementierung in einem Referenztyp nicht bestimmen.

Wenn der Präfixoperator ++ für x als Variable in ++x klassifiziert wird, wird der Instanzoperator gegenüber einem statischen unären Operator bevorzugt.

C#-Sprachspezifikation

Weitere Informationen finden Sie in den folgenden Abschnitten der C#-Sprachspezifikation:

Siehe auch