Operadores sobrecargados.

El concepto de operador sobrecargado se refiere a un operador que es capaz de desarrollar su función en varios contextos diferentes sin necesidad de otras operaciones adicionales. Por ejemplo, la suma 'a + b' en la práctica para nosotros supondrá operaciones comunes diferentes dependiendo de que estemos trabajando en el campo de los números reales o en el campo de los números complejos. Si dotamos al operador '+' para que, además de sumar números reales, permita también sumar números complejos, dependiendo esto de los tipos de operandos, entonces diremos que el operador '+' está sobrecargado.

Las posibilidades de sobrecarga de operadores están remumidas en la siguiente tabla:

OPERADORES POSIBILIDAD DE SOBRECARGA
+, -, !, ~, ++, --, true, false Operadores unarios que sí se pueden sobrecargar.
+, -, *, /, %, &, |, ^, <<, >> Operadores binarios que sí se pueden sobrecargar.
==, !=, <, >, <=, >= Los operadores de comparación se pueden sobrecargar, pero por parejas; es decir, si se sobrecarga '==', también se puede sobrecargar '!='.
&&, || Los operadores lógicos condicionales no se pueden sobrecargar, pero se evalúan mediante '&' y '|', los cuáles sí se pueden sobrecargar.
[] El operador de indexación de matrices no se puede sobrecargar, pero se pueden definir indizadores.
() El operador de conversión explícita de tipos no se puede sobrecargar, pero se pueden definir nuevos operadores de conversión: 'explicit' e 'implicit'.
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= Los operadores de asignación no se pueden sobrecargar, pero '+=', por ejemplo, se puede evaluar con '+', que sí se puede sobrecargar.
=, ., ?:, ->, new, is, sizeof, typeof Estos operadores no se pueden sobrecargar.

Ya sabemos que un operador unario se aplica sobre un solo operando y que un operador binario se aplica sobre dos operandos. Pues bien, según esto, cuando se sobrecargue un operador unario utilizando un método 'static', éste debe tomar un parámetro, y dos cuando se sobrecargue un operador binario:

public class OperatorsClass
{
    // Atributos.
    public static OperatorsClass operator -(OperatorsClass x)
    {
        // ...
    }
    public static OperatorsClass operator -(OperatorsClass x, OperatorsClass y)
    {
        // ...
    }
}
public class Program
{
    public static void Main()
    {
        // a, b y c con objetos de la clase OperatorsClass.
        OperatorClass a = new OperatorClass(), b = new OperatorClass(), c = new OperatorClass();
        b = -c;
        c = a - b;
    }
}

La sentencia 'b = -c' implícitamente invoca al método 'operator -' con un parámetro, y 'c = a - b' al método 'operator -' con dos parámetros.

En este ejemplo hemos utilizado una clase para definir los objetos que intervienen en las operaciones programadas a través de la sobrecarga de operadores. Pero podríamos pasar una estructura en lugar de una clase, si después de un análisis previo de la aplicación decidimos que es más eficiente. Por ejemplo:

public struct OperatorsClass
{
    // Atributos.
    public static OperatorsClass operator -(OperatorsClass x)
    {
        // ...
    }
    public static OperatorsClass operator -(OperatorsClass x, OperatorsClass y)
    {
        // ...
    }
}
public class Program
{
    public static void Main()
    {
        OperatorClass a = new OperatorClass(), b, c = new OperatorClass();
        b = -c; // Menos unario.
        c = a - b; // Menos binario.
    }
}

Parece un poco complicado ¿verdad? No te preocupes. Vamos con un ejemplo más funcional para que lo entiendas. Para realizar una adición entre dos vectores, puedes crear un método llamado 'AddVector' que tome como parámetros los componentes 'X' e 'Y' del vector:

public class Vector
{
    public double X
    {
        get;
        set;
    }
    public double Y
    {
        get;
        set;
    }
    public Vector AddVector(Vector v)
    {
        return new Vector() { X = this.X + v.X, Y = this.Y + v.Y }
    }
}

De esta manera, para sumar dos vectores esa suficiente utilizar una instrucción de este tipo:

Vector vectorA = new Vector(1, 2);
Vector vectorB = new Vector(3, 4);
Vector vectorC = vectorA.AddVector(vectorB);

La sobrecarga de operadores va a permitir que la utilización del objeto sea más intuitiva, con una notación como la siguiente:

Vector vectorC = vectorA + vectorB;

La sobrecarga del operador de adición para la estructura 'Vector' dará:

public static Vector operator +(Vector v1, Vector v2)
{
    return new Vector() { X = v1.X + v2.X, Y = v1.Y + v2.Y }
}

Ahora el cuerpo del método no es diferente del cuerpo del método 'AddVector' definido anteriormente, pero su uso sí que es completamente difetente:

Vector vectorD = vectorA + vectorB + vectorC;

A continuación, y para rematar el tema, te muestro un código fuente para la sobrecarga de los operadores '==' y '!=' para la estructura 'Vector':

public static bool operator ==(Vector v1, Vector v2)
{
    return v1.X == v2.X && v1.Y == v2.Y;
}
public static bool operator !=(Vector v1, Vector v2)
{
    return !(v1 == v2);
}

La sobrecarga del operador '==' hace una comparación de las variables 'X' e 'Y' entre ellas para determinar si los dos objetos son idénticos. La sobrecarga del operador '!=' devuelve el valor inverso del resultado de la sobrecarga del operador '==' entre los dos argumentos 'v1' y 'v2'. El ejemplo de uso de estas dos sobrecargas sería el siguiente:

Vector vectorA = new Vector(1, 2);
Vector vectorB = new Vector(1, 2);
ConsoleWriteLine("vectorA==vectorB: {0}", vectorA == vectorB); // vectorA==vectorB: True
ConsoleWriteLine("vectorA!=vectorB: {0}", vectorA != vectorB); // vectorA!=vectorB: False