Condividi tramite


Sovraccarico degli operatori - operatori predefiniti unari, aritmetici, di uguaglianza e di confronto

Un tipo definito dall'utente può eseguire l'overload di un operatore C# predefinito. Ovvero, un tipo può fornire l'implementazione personalizzata di un'operazione nel caso in cui uno o entrambi gli operandi siano di tale tipo. La sezione Operatori sovraccaricabili mostra gli operatori C# che possono essere sovraccaricati.

Usare la operator parola chiave per dichiarare un operatore. Una dichiarazione di operatore deve soddisfare le regole seguenti:

  • Include un public modificatore.
  • Un operatore unario ha un parametro di input. Un operatore binario ha due parametri di input. In ogni caso, almeno un parametro deve avere un tipo T o T? dove T è il tipo che contiene la dichiarazione dell'operatore.
  • Include il static modificatore, ad eccezione degli operatori di assegnazione composta, ad esempio +=.
  • Gli operatori di incremento (++) e decremento (--) possono essere implementati come metodi statici o di istanza. Gli operatori del metodo di istanza sono una nuova funzionalità introdotta in C# 14.

Nell'esempio seguente viene definita una struttura semplificata per rappresentare un numero razionale. La struttura sovraccarica alcuni degli operatori aritmetici:

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

È possibile estendere l'esempio precedente definendo una conversione implicita da int a Fraction. Quindi, gli operatori sovraccaricati supporterebbero argomenti di questi due tipi. Ciò significa che sarebbe possibile aggiungere un numero intero a una frazione e ottenere una frazione di conseguenza.

Si usa anche la operator parola chiave per definire una conversione di tipi personalizzata. Per altre informazioni, vedere Operatori di conversione definiti dall'utente.

Operatori sovraccaricabili

La tabella seguente illustra gli operatori che possono essere sovraccaricati:

Operatori Note
+x, -x, !x, ~x++, --, , truefalse Gli operatori true e false devono essere sovraccaricati insieme.
x + y, x - y, x * y, x / y, x % y
x & y, x | y, x ^ y
x << y, x >> y, x >>> y
x == y, x != y, x < y, x > y, x <= yx >= y Deve essere sottoposto a overload in coppie come indicato di seguito: == e !=, <> e <=e >=.
+=, -=, , *=, /=%=, &=\|=^=, <<=>>=>>>= Gli operatori di assegnazione composta possono essere sovraccaricati in C# 14 e versioni successive.

Un operatore di overload di assegnazione composta deve seguire queste regole:

  • Deve includere il public modificatore.
  • Non può includere il static modificatore.
  • Il tipo restituito deve essere void.
  • La dichiarazione deve includere un parametro, che rappresenta il lato destro dell'assegnazione composta.

A partire da C# 14, gli operatori di incremento (++) e decremento (--) possono essere sovraccaricati come membri dell'istanza. Gli operatori di istanza possono migliorare le prestazioni evitando la creazione di una nuova istanza. Un operatore di istanza deve seguire queste regole:

  • Deve includere il public modificatore.
  • Non può includere il static modificatore.
  • Il tipo restituito deve essere void.
  • Non può dichiarare parametri, anche se tali parametri hanno un valore predefinito.

Operatori non sovraccaricabili

La tabella seguente illustra gli operatori che non possono essere sovraccaricati:

Operatori Le alternative
x && y, x || y Sovraccaricare sia gli operatori true e false che gli operatori & o |. Per altre informazioni, vedere Operatori logici condizionali definiti dall'utente.
a[i], a?[i] Definire un indicizzatore.
(T)x Definire le conversioni di tipi personalizzate eseguite da un'espressione cast. Per altre informazioni, vedere Operatori di conversione definiti dall'utente.
^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, stackalloc, switch, typeofwith
Nessuno.

Prima di C# 14, gli operatori composti non potevano essere sovraccaricati. Sovraccaricare l'operatore binario corrispondente comporta implicitamente il sovraccarico dell'operatore di assegnazione composta corrispondente.

Risoluzione dell'overload degli operatori

Importante

Questa sezione si applica a C# 14 e versioni successive. Prima di C# 14, gli operatori di assegnazione composta definiti dall'utente e gli operatori di incremento e decremento dell'istanza non sono consentiti.

Se x è classificato come variabile in un'espressione di assegnazione composta, ad x «op»= yesempio , gli operatori di istanza sono preferiti rispetto a qualsiasi operatore statico per «op». Se un operatore overload «op»= non è dichiarato per il tipo di x o x non è classificato come variabile, vengono usati gli operatori statici.

Per l'operatore suffisso ++, se x non è classificato come variabile oppure se viene usata l'espressione , l'istanza x++ viene ignorata. In caso contrario, la preferenza viene assegnata all'istanza operator ++. Ad esempio:

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

Il motivo di questa regola è che y deve essere assegnato al valore di x che venga incrementato. Il compilatore non può determinare un'implementazione definita dall'utente in un tipo di riferimento.

Per l'operatore ++prefisso , se x è classificato come variabile in ++x, l'operatore di istanza è preferibile rispetto a un operatore unario statico.

Specificazione del linguaggio C#

Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:

Vedere anche