domingo, 23 de febrero de 2014

Sobrecarga y Resolución Llamada Métodos en C#

Tabla de Contenido

0. Introducción
1. Sobrecarga de Métodos
2. Resolución Llamada a Métodos Sobrecargados
2.1 Casos Simples
2.2 Múltiples Parámetros
2.3 Herencia y Sobrecarga
3. Conclusiones
4. Glosario
5. Enlaces & Literatura

0. Introducción

Las tareas del compilador son infinitas y una de ellas va a hacer tratada en este artículo: resolución de llamada a métodos sobrecargados. Con resolución me refiero a la determinación de la selección del miembro función más apto para una invocación desde código cliente o interno (privado). Veremos algunas reglas que utiliza el compilador del lenguaje de programación C# para determinar el método sobrecargado a partir de los tipos y número de parámetros. También se introducirá la regla de llamada basada en la jerarquía de herencia.

1. Sobrecarga de Métodos

En el artículo Métodos en C# comprendimos que los métodos son un tipo de función para la resuabilidad de código. Además de aportar los medios para la estructuración de código fuente en unidades bien definidas (es decir, que se ajuste al principio de diseño cohesión). También hicimos la analogía entre un método y un proceso: donde para empezar a funcionar se requiere de una entrada (datos), un procesamiento (implementación), y una salida (o resultado de la ejecución de las instrucciones que comprenden la implementación).

En la sección 4 del mismo artículo hablamos de manera muy generalizada acerca del concepto de sobrecarga:
La sobrecarga de un método consiste en crear firmas diferentes para métodos que tienen el mismo nombre.
En esencia ahí tenemos una definición para un método sobrecargado. La sobrecarga, además, permite reafirmar el principio cohesión, es decir, que los métodos realicen sólo aquella tarea para lo cual fueron concebidos. No deberíamos hacer que un método abarque cientos de líneas, dado que ese es un indicio de sobre-dimensionamiento de la capacidad o tareas a llevar a cabo. Ahí es donde nos deberíamos replantear la implementación y aplicar otro principio: divide y vencerás [5].

Ahora veamos un ejemplo simple de un método sobrecargado:

int Sumar (int a, int b) {...}
double Sumar (double a, double b) {...}

El primero corresponde con la suma de dos números enteros de 32 bits (int), el segundo se encarga de sumar dos números de coma flotante de doble precisión: double. Gracias a la sobrecarga podemos tener definidos varios métodos con el mismo nombre (mantener la consistencia de nuestro tipo de dato. Imagínense uds. una calculadora que tuviera un botón de suma por cada tipo de dato soportado por la arquitectura interna de la misma. Ver Figura 1).
Calculadora con muchos botones.
Figura 1. Calculadora con muchos botones.

Resultaría muy tedioso en volumen y uso de la calculadora al contener tantos botones, precisamente hablando de las versiones por cada tipo de dato:
  • Suma
    • +(i): Suma enteros (int)
    • +(f): Suma punto flotante de 32 bits (float)
    • +(d): Suma punto flotante de 64 bits (double)
Y así mismo para otras tres operaciones de la aritmética: resta, producto, y cociente.

La programación interna de la calculadora debe determinar los tipos de datos a sumar y usar las rutinas apropiadas para la evaluación y generación de resultados. De manera análoga, el compilador de C# se encarga de escoger la versión del método más idóneo y óptima para llevar a cabo el cálculo de la suma (o resta, producto, o cociente).

Por otro lado, si tuviéramos algo como:

float Sumar (double a, double b) {...}
double Sumar (double a, double b) {...}

El compilador no completará el compilado debido a que las firmas de estos dos métodos es idéntica: los métodos no se diferencian por el tipo de dato de retorno: float y double en este caso:

error CS0111: A member `Test.Sumar(double, double)' is already defined. Rename this member or use different parameter types

Lo mismo ocurrirá con dos métodos que se definan de esta manera:

void ConcatenarGrupoCadenasCaracteres (string[] cadenas)
void ConcatenarGrupoCadenasCaracteres (params string[] cadenas)

El parámetro de ConcatenarGrupoCadenasCaracteres es un arreglo de objetos string, para la segunda versión es un arreglo de objetos string pero con longitud variable (params). Desde luego estas firmas son equivalentes, y el compilador generará el error CS0111 [6].

2. Resolución Llamada a Métodos Sobrecargados

Ahora que ya conocemos el concepto de método sobrecargado y algunas de sus complejidades en la definición de versiones de métodos con el mismo nombre, es hora de que pasemos a conocer cómo logra el compilador determinar cuál versión de un método sobrecargado llamar.

2.1 Casos simples

Este primer caso se demuestra las situaciones donde el compilador de C# no hace mayor esfuerzo para determinar cuál versión de método sobrecargado ejecutar:

Archivo CasoSimple.cs:

Los dos métodos a pesar de poseer el mismo nombre -Metodo- son heterogéneos por el tipo de dato de sus argumentos. Esta es una tarea muy fácil para el compilador de C#.

► Prueba de ejecución.

Otro ejemplo que podríamos analizar es el de hacer que los parámetros pertenezcan al mismo grupo de tipos de datos. Podríamos hacer el ejemplo con la categoría de numéricos:

Archivo CasoSimple2Sobrecarga.cs:



El método Metodo tiene dos versiones, una que recibe un entero de 32 bits (int) como parámetro, y otra que recibe un double (32 bits).

Aquí el compilador invoca el método con la firma Metodo (int x)  debido a que no debe realizar ninguna conversión de la literal 13. Si hubiéramos escrito 13.0 se hubiera invocado Metodo (double y).

► Prueba de ejecución.

2.2 Múltiples parámetros

Para los casos en que tengamos varios métodos sobrecargados con múltiples parámetros, el compilador escogerá aquel método que cumpla con el mínimo de conversiones posibles entre tipos de datos, y que desde luego éstos sean compatibles en una jerarquía de herencia.

Miremos el siguiente ejemplo:

Al compilador nuevamente se le facilita la tarea de selección de método; dado que los tipos de datos de los argumentos 5, y 10 son números enteros, y la firma apropiada es: Metodo (int x, int y).

► Prueba de ejecución.

Un ejemplo más:

Archivo MultiplesParametros2Sobrecarga.cs:



Los dos métodos declarados son ambiguos y generan error en tiempo de compilación. Este es el error generado:
error CS0121: The call is ambiguous between the following methods or properties: `Articulos.Ch03.MultiplesParametros2Sobrecarga.Metodo(int, double)' and `Articulos.Ch03.MultiplesParametros2Sobrecarga.Metodo(double, int)'
Al consultar en [8], el error radica en la conversión implícita de los argumentos a los tipos de los parámetros. Nos dan estas soluciones para corregir el problema:
  • Specify the method parameters in such a way that implicit conversion does not take place.
  • Remove all overloads for the method.
  • Cast to proper type before calling method.

La última práctica a la que se debería recurrir es la segunda, es decir, la de remoción de los métodos sobrecargados, pues quizás estemos modificando el diseño de nuestra clase a los requerimientos del lenguaje. Mi sugerencia es iterar entre la primera y tercera opción que son más naturales, pero al no encontrar el modo, recurrir a la segunda.

► Prueba de ejecución.

2.3 Sobrecarga y Herencia

Sobre la herencia y métodos sobrecargados hay que tener en cuenta que el compilador tiene en cuenta las llamadas sobre el objeto objetivo, es decir, empieza por buscar sobre las definiciones en la subclase, de no encontrarla, pasa a la clase base, en caso contrario pasa a la superclase de ésta última, &c.

Archivo SobrecargaHerencia.cs:



El compilador de C# primero busca en la clase Hijo, al encontrar una firma apropiada a la llamada desde el código cliente en la clase ClasePrueba, se dirige a la clase Padre y es allí donde encuentra la firma de método apropiada con el tipo del parámetro.

► Prueba de ejecución.

Conclusiones

Comprendimos el uso de métodos sobrecargados para hacer más consistente la implementación de métodos con el mismo nombre pero con diferente tipo de datos y número de parámetros. Se ejemplificó con el diseño hardware de una calculadora el sobre-uso de diferentes botones para representar una semántica distinta (operaciones aritméticas). Al final vimos el impacto que tiene sobre el compilador de uso de métodos sobrecargados, pues éste debe evaluar y detectar las firmas más apropiadas que se ajusten a las invocaciones.

Glosario

  • Aplicable
  • Compilador
  • Función
  • Método
  • Miembro
  • Programación
  • Tiempo de compilación

Enlaces & Literatura

[1]: C# 5000.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: 7.4.2 Overload resolution (C#) - http://msdn.microsoft.com/en-us/library/aa691336%28v=vs.71%29.aspx
[3]: C# in Depth: Overloading - http://csharpindepth.com/Articles/General/Overloading.aspx
[4]: Patterns in Practice: Cohesion And Coupling - http://msdn.microsoft.com/en-us/magazine/cc947917.aspx
[5]: Divide and conquer algorithm, the free encyclopedia - https://en.wikipedia.org/wiki/Divide_and_conquer_algorithm
[6]: Compiler Error CS0111 - http://msdn.microsoft.com/en-us/library/ddza5cw4%28v=vs.90%29.aspx
[7]: Compiler Error CS0121 - http://msdn.microsoft.com/en-us/library/ky5f1hz8%28v=vs.90%29.aspx


J

No hay comentarios:

Publicar un comentario

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