lunes, 8 de septiembre de 2014

Sobrecarga de Operadores en C#

Índice

0. Introducción
1. ¿En Qué Consiste Sobrecargar un Operador?
2. Introducción a la Sobrecarga de Operadores
3. Función de Operador
4. Consideraciones Especiales
4.1 Sobrecarga de operadores de igualdad y comparación
4.1.1 Emparejamiento
4.1.2 Métodos Equals y GetHashCode
4.1.3 Interfaces IComparable y IComparable<T>
4.2 Conversiones implícitas y explícitas
4.3 Sobrecarga de true y false
5. Ejemplos de Uso
5.1 Palabra
5.2 Número complejo
6. Artefactos
7. Conclusiones
8. Glosario
9. Literatura & Enlaces

0. Introducción

Otra de las operaciones interesantes que podemos implementar en un tipo de dato basado en estructura es la sobrecarga de operadores. Con la sobrecarga de operadores el programador puede crear su propia semántica para los operadores aritméticos estándar (e.g., +, -, *, /), los operadores compuestos (e.g., +=, -=, *=  /=), igualmente para los operadores relacionales y de igualdad, entre otros que nombraremos en el desarrollo de este artículo. Conoceremos y entenderemos acerca de las reglas de composición de la sobrecarga de operadores, las funciones de operadores (uso particular de la palabra clave operator), conversiones implícitas y explícitas, sobrecarga de valores lógicos (i.e., true y false).  La inclusión de ejemplos prácticos de esta operación interesante no será la excepción: crearemos nuestras propias propias clases con sus propias versiones sobrecargadas de los operadores estándar y compuestos.

1. ¿En Qué Consiste Sobrecargar un Operador?

Sabemos que un método miembro de una clase o estructura se puede sobrecargar básicamente por medio de diferentes firmas que incluyen combinaciones sin repetición de los tipos y el número de los parámetros. Esto para crear la siguiente analogía. Ahora, debemos considerar la inclusión en nuestra caja de herramientas el conocimiento y práctica de la sobrecarga de operadores. Sobrecargar un operador consiste en establecer una función semántica arbitraria de un operador nativo del lenguaje para un tipo de dato particular que deseemos implementar a partir de un modelo del mundo del problema.

Ahora, como advierten en [1], la posibilidad de sobrecargar un operador provee una sintaxis más natural para los tipos de datos definidos por el programador. Además, la sobrecarga de operadores es más apropiada para las estructuras (cfr. Structs) debido a su intrínseca relación con los tipos primitivos (e.g., char, int, long, double). De aquí podemos decir que cualquier oportunidad de definir un tipo numérico personalizado es un candidato ideal para la sobrecarga de operadores.

2. Introducción a la Sobrecarga de Operadores en C#

En el lenguaje de programación C# podemos sobrecargar los operadores que cuentan con la propiedad de 'sobrecargabilidad' (Overloadability) que se muestran en la Figura 1.
'Sobrecargabilidad' de operadores
Figura 1. 'Sobrecargabilidad' de operadores [5].


Además, es importante aclarar que de forma indirecta el hecho de sobrecargar los operadores binarios (e.g., +, -, *) incurre en la generación de las versiones compuestas de asignación (e.g., +=, -=, *=). De manera análoga, para los operadores condicionales (i.e.&&, y ||) sobrecargados, las operadores nivel de bits (cfr. Operadores Bitwise (Bit a Bit)) se sobrecargan de forma implícita.

En lo que respecta al proceso de sobrecarga en sí, es requisito indispensable el uso de las siguientes construcciones de C#:
  • static
  • operator
En el caso de operator [11] debe ser especificado en la misma firma del método, por ejemplo:

public static TipoRetorno operator +(Tipo, Tipo)

Descripción puntual:
  • static: Los operadores sobrecargados no requieren de una instancia del tipo para operar.
  • TipoRetorno: Un tipo de retorno arbitrario para el operador sobrecargado.
  • operator: Palabra requerida para la sobrecarga de un operador.
  • Tipo: Al menos uno de los operandos debe corresponder con el tipo en donde es declarado. (Ya veremos más adelante cómo cumplir con este requerimiento.)

3. Función de Operador

Para sobrecargar un operador es necesario seguir la sintaxis declarativa demostrada en la sección anterior, además de algunas reglas básicas [1]:
  • La firma de la función de operador debe especificar la palabra clave operator.
  • La firma de la función de operador debe estar marcada con las modificadores public y static.
  • Los parámetros corresponden con los operandos del operador a sobrecargar.
  • Para el caso del tipo de retorno de la función de operador, es el resultado de la operación sobrecargada del operador.
  • ad minimum uno de los operandos debe ser del tipo donde se declara la función de operador.
Por ejemplo, si queremos crear una estructura que representa un número complejo:

public struct NumeroComplejo
{
public int Real
{
 get;
 set;
}

public int Real
{
get;
set;
}

public NumeroComplejo(int real, int imaginario)
{
Real = real;
Imaginario = imaginario;
}

public static string operator +(NumeroComplejo c1, int o2)
{
return new NumeroComplejo( c1.Real + o2, c1.Imaginario);
}

// ...
}

En la función de operador que sobrecarga el operador + podemos realizar una operación como la siguiente desde código cliente:

NumeroComplejo nc1 = new NumeroComplejo (3, 7);
NumeroComplejo nc2 = nc1 + 13;

Y por extensión:

nc1 += 11;

4. Consideraciones Especiales

4.1 Sobrecarga de operadores de igualdad y de comparación

Para sobrecargar los operadores de igualdad y de comparación es importante seguir las siguientes reglas:

4.1.1 Emparejamiento

Para los operadores que cuentan con un emparejamiento lógico (== !=), (< >), y (<= >=), el compilador C# demanda que ambos sean definidos como funciones de operador al momento de sobrecargarse.

4.1.2 Métodos Equals y GetHashCode

Para los operadores ==, y != es lógicamente necesario (aunque no mandatorio) sobrescribir los métodos Equals y GetHashCode de la clase Object (cfr. El Tipo Object); esto permitirá establecer un comportamiento lógico a la hora de comparar el contenido de variables de la estructura o clase con operadores sobrescritos.

4.1.3 Interfaces IComparable y IComparable<T>

Para los operadores emparejados (< >) y (<= >=) sobrecargados, para comparaciones arbitrarias de valores se sugiera la implementación de la interfaz no-genérica IComparable [9] o genérica IComparable<T> [8]. (cfr. Interfaces)

4.2 Conversiones implícitas y explícitas

Para las conversiones implícitas y explícitas con tipos numéricos, C# cuenta con las palabras claves implicit y explicit, respectivamente, las cuales pueden ser especificadas en la firma de una función de operador.

Para ejemplificar este tipo de conversiones, asumamos que contamos con una estructura llamada NotaMusical (adaptado de [1]):

public struct NotaMusical
{
    // ...

    // Convierte a hercios
    public static implicit operator double (NotaMusical x)
    {
        return 440 * Math.Pow (2, (double) n.valor / 12);
    }

    // Convierte desde hercios al semitono más cercano:
    public static explicit operator double (double x)
    {
        return new NotaMusica(
            (int) (0.5 + 12 * (Math.Log (x/440)
                   / Math.Log(2)
            )
        );
    }

    // ...
}
Desde código cliente podemos tener los siguientes tipos de conversiones:

NotaMusical nm = (NotaMusical)554.37;   // Conversión explícita
double x = n;                           // Conversión implícita

Sin embargo, hay que tener en cuenta esta advertencia [1]:
Advertencia en el uso de conversiones implícitas y explícitas
Figura 2. Advertencia en el uso de conversiones implícitas y explícitas.

4.3 Sobrecarga de true y false

Este tipo de sobrecarga de operador unitarios true y false, como dicen en [1], es raramente utilizada. En la biblioteca base de clases de .NET Framework, la estructura SqlBoolean (System.Data.SqlTypes) [10] sobrecarga estos operadores para poder operar en estructuras de evaluación de flujo de control (i.e., if, do, while, for, &&, ||, ?:) .

5. Ejemplos de Uso

5.1 Palabra

En este primer ejemplo veremos cómo sobrecargar el operador de adición (+) para concatenar dos objetos de la clase Palabra, y, también el texto de una instancia Palabra con un número entero de 32 bits.

Empecemos por comentar acerca de las líneas 17-20: la función función de operador + básicamente concatena el contenido de la propiedad Texto de dos instancias (operandos) de la clase Palabra:

palabra1 + palabra2


Con la función de operador de las líneas 26-29 se concatena el valor de la propiedad Texto con un número entero de 32 bits (int):

palabra1 + 13

Compilación:


  1. csc /target:exe Palabra.cs

Ejecución assembly:


  1. .\Palabra.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly Palabra.exe
Figura 3. Ejecución assembly Palabra.exe.

5.2 Números Complejos

En este siguiente ejemplo veremos cómo podemos crear una clase para representar números complejos. La nueva clase con operadores sobrecargados tendrá la capacidad de adicionar, substraer, multiplicar números complejos.

En este ejemplo se sobrecargan los operadores aritméticos:
  • + (líneas 28-31)
  • - (líneas 36-39)
  • * (líneas 44-53)
  • / (líneas 58-64)
para la operaciones de suma, resta, producto, y cociente sobre números complejos.

Compilación:

  1. csc /target:exe NumeroComplejo.cs

Ejecución assembly

  1. .\NumeroComplejo.exe


> Prueba ejecución (local):
Ejecución assembly NumeroComplejo.exe
Figura 4. Ejecución assembly NumeroComplejo.exe.

6. Artefactos

A continuación enlaces de descarga de los artefactos producidos en el desarrollo de este artículo:

7. Conclusiones

Hemos adquirido conocimiento y práctica en el uso de la palabra clave operator para la sobrecarga de operadores (e.g., +, -, *, /) para crear comportamientos arbitrarios a un tipo basado en operaciones numéricas (por ejemplo). Además entendimos que existen reglas de implementación de sobrecarga de operadores: como el emparejamiento. A través de dos ejemplos quedó afianzado las explicaciones conceptuales y de uso de la palabra clave operator: concatenación de texto, y operaciones aritméticas sobre números complejos. En el próximo artículo empezaremos nuestra discusión acerca de los métodos de extensión.

8. Glosario

  • Clase
  • Estructura
  • Igualdad
  • Interfaz
  • Número complejo
  • Operador
  • Sobrecarga

9. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: OrtizOL - Experiencias Construcción Software (xCSw): Receta No. 1-21 en C#: Sobrecargar un Operador - http://ortizol.blogspot.com/2014/04/receta-no-1-21-en-c-sobrecargar-un.html
[3]: Structs en C# | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/04/structs-en-c.html
[4]: Visual C# 2010 Recipes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[5]: Overloadable Operators (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/8edha89s.aspx
[6]: Operadores Bitwise (Bit a Bit) en C# | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2013/07/operadores-bitwise-bit-bit-en-c.html
[7]: El Tipo object en C# | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/02/el-tipo-object-en-c.html
[8]: IComparable(T) Interface (System) - http://msdn.microsoft.com/en-us/library/4d7sx9hd(v=vs.110).aspx
[9]: IComparable Interface (System) - http://msdn.microsoft.com/en-us/library/system.icomparable(v=vs.110).aspx
[10]: SqlBoolean Structure (System.Data.SqlTypes) - http://msdn.microsoft.com/en-us/library/system.data.sqltypes.sqlboolean(v=vs.110).aspx
[11]: operator (C# Reference) - http://msdn.microsoft.com/en-us/library/s53ehcz3.aspx
[12]: Interfaces en C# - Parte 1 | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/05/interfaces-en-c-parte-1.html


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.