Compartir a través de


Sobrecarga de operadores: operadores unarios predefinidos, aritméticos, de igualdad y de comparación

Un tipo definido por el usuario puede sobrecargar un operador predefinido de C#. Es decir, un tipo puede proporcionar la implementación personalizada de una operación en caso de que uno o ambos operandos sean de ese tipo. La sección Operadores sobrecargables muestra qué operadores de C# se pueden sobrecargar.

Use la operator palabra clave para declarar un operador. Una declaración de operador debe cumplir las siguientes reglas:

  • Incluye un modificador public.
  • Un operador unario tiene un parámetro de entrada. Un operador binario tiene dos parámetros de entrada. En cada caso, al menos un parámetro debe tener el tipo T o T? donde T es el tipo que contiene la declaración del operador.
  • Incluye el static modificador, excepto para los operadores de asignación compuestos, como +=.
  • Los operadores de incremento (++) y decremento (--) se pueden implementar como métodos estáticos o de instancia.

En el ejemplo siguiente se define una estructura simplificada para representar un número racional. La estructura sobrecarga algunos de los operadores aritméticos:

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

Puede ampliar el ejemplo anterior definiendo una conversión implícita de int a Fraction. A continuación, los operadores sobrecargados admitirían argumentos de esos dos tipos. Es decir, sería posible agregar un entero a una fracción y obtener una fracción como resultado.

También se usa la operator palabra clave para definir una conversión de tipos personalizada. Para obtener más información, vea Operadores de conversión definidos por el usuario.

Operadores sobrecargables

En la tabla siguiente se muestran los operadores que se pueden sobrecargar:

Operadores Notas
+x, -x, !x, ~x, ++, --, , truefalse Los operadores true y false deben sobrecargarse juntos.
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 <= y, x >= y Debe sobrecargarse en pares de la siguiente manera: == y !=, < y >, <= y >=.
+=, -=, *=, /=, %=, &=, \|=, ^=, <<=, , >>=>>>= Los operadores de asignación compuesta se pueden sobrecargar en C# 14 y versiones posteriores.

Un operador sobrecargado de asignación compuesta debe seguir estas reglas:

  • Debe incluir el public modificador .
  • No puede incluir el static modificador .
  • El tipo de valor devuelto debe ser void.
  • La declaración debe incluir un parámetro, que representa el lado derecho de la asignación compuesta.

A partir de C# 14, los operadores de incremento (++) y decremento (--) se pueden sobrecargar como miembros de instancia. Los operadores de instancia pueden mejorar el rendimiento evitando la creación de una nueva instancia. Un operador de instancia debe seguir estas reglas:

  • Debe incluir el public modificador .
  • No puede incluir el static modificador .
  • El tipo de valor devuelto debe ser void.
  • No puede declarar ningún parámetro, incluso si esos parámetros tienen un valor predeterminado.

Operadores no sobrecargables

En la tabla siguiente se muestran los operadores que no se pueden sobrecargar:

Operadores Alternativas
x && y, x || y Sobrecargue tanto los operadores true como los false, y los operadores & o |. Para obtener más información, consulte Operadores lógicos condicionales definidos por el usuario.
a[i], a?[i] Defina un indexador.
(T)x Defina las conversiones personalizadas de tipo realizadas mediante una expresión de conversión. Para obtener más información, vea Operadores de conversión definidos por el usuario.
^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, , typeof, with
Ninguno.

Antes de C# 14, no era posible sobrecargar los operadores compuestos. Sobrecargar el operador binario correspondiente sobrecarga implícitamente el operador de asignación compuesta correspondiente.

Resolución de sobrecarga de operadores

Importante

Esta sección se aplica a C# 14 y versiones posteriores. Antes de C# 14, no se permiten operadores de asignación compuesta definidos por el usuario ni operadores de incremento y decremento de instancia.

Si x se clasifica como una variable en una expresión de asignación compuesta como x «op»= y, los operadores de instancia se prefieren sobre cualquier operador estático para «op». Si un operador sobrecargado «op»= no se declara para el tipo de x o x no se clasifica como una variable, se usan los operadores estáticos.

Para el operador ++postfix , si x no se clasifica como una variable o se usa la expresión x++ , se omite la instancia operator++ . De lo contrario, se da preferencia a la instancia operator ++. Por ejemplo

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

El motivo de esta regla es que y se debe asignar al valor de xantes de que se incremente. El compilador no puede determinar que esto es válido para una implementación definida por el usuario en un tipo de referencia.

Para el operador ++de prefijo , si x se clasifica como una variable en ++x, se prefiere el operador de instancia sobre un operador unario estático.

Especificación del lenguaje C#

Para más información, vea las secciones siguientes de la Especificación del lenguaje C#:

Consulte también