El blog del burgués

17 junio 2009

Delegados en C#

Filed under: C# — elburgues @ 9:46 PM
Tags:

Introducción

Aunque una función no es una variable, siempre tiene una localización física en memoria. Esa dirección es lo que se denomina “punto de entrada a la función” (la dirección que se usa cuando se invoca a la función). En C, esa dirección se puede asignar a un puntero y una vez que un puntero apunta a una función, se puede invocar a la función a través de ese puntero. También es posible en C pasar ese puntero como argumento a otras funciones. 

Los punteros a funciones han estado en C desde siempre. Bueno, pues los delegados se parecen a los punteros a funciones de la programación tradicional. Y digo “se parecen”, porque, como veremos a lo largo de esta entrada, en realidad, son algo más que eso. 

Planteo del problema

Echemos un vistazo a las siguientes dos funciones:

        public static double MediaRaiz(double[] puntos)
        {
            double total = 0.0;
            for (int i = 0; i < puntos.Length; i++)
            {
                total += Math.Sqrt(puntos[i]);
            }
            return total / puntos.Length;
        }
 
        public static double MediaExponencial(double[] puntos)
        {
            double total = 0.0;
            for (int i = 0; i < puntos.Length; i++)
            {
                total += Math.Exp(puntos[i]);
            }
            return total / puntos.Length;
        }

Viendo que hay cambios mínimos entre ambas ¿no se podría programar una función “Media” genérica de forma que acepte la función real que le suministremos?

Tendríamos que sustituir la llamada a la función concreta por la llamada a una función que “puede variar”. Esa función que “puede variar” no puede ser una función cualquiera. En nuestro caso, se trata de una función que acepte un parámetro de tipo real y retornar un valor real también.

Soluciones con OOP

1ª) Forma: mediante la herencia de clases:

    public abstract class Promediador
    {
        protected abstract double Funcion(double valor);
 
        public double Calcular(double[] puntos)
        {
            double total = 0.0;
            for (int i = 0; i < puntos.Length; i++)
            {
                total += Funcion(puntos[i]);
            }
            return total / puntos.Length;
        }
    }
 
    public class PromedioRaiz : Promediador
    {
        protected override double Funcion(double valor)
        {
            return Math.Sqrt(valor);
        }
    }
 
    public class Program
    {
        public static void Main()
        {
            double[] puntos = new double[] {0.0,1.0,2.0 };
            Promediador prom = new PromedioRaiz();
            Console.WriteLine(prom.Calcular(puntos));
        }
    }

Esto tiene algunos inconvenientes:

  • Necesitamos crear una clase para poder usar una función.
  • Cada vez que se quiera usar el algoritmo con una nueva función, hay que crear una clase derivada.
  • Cada llamada de evaluación es una llamada virtual y eso ralentiza el algoritmo.

 2ª) Forma: mediante la implementación de interfaces

    public interface IFuncion
    {
        double Funcion(double valor);
    }
 
    public class PromedioRaiz : IFuncion
    {
        public double Funcion(double valor)
        {
            return Math.Sqrt(valor);
        }
    }
 
    public class Program
    {
        public static double MediaRaiz(double[] puntos, IFuncion funcion)
        {
            double total = 0.0;
            for (int i = 0; i < puntos.Length; i++)
            {
                total += funcion.Funcion(puntos[i]);
            }
            return total / puntos.Length;
        }
 
        public static void Main()
        {
            double[] puntos = new double[] {0.0,1.0,2.0};
            IFuncion raizCuadrada = new PromedioRaiz();
            Console.WriteLine(MediaRaiz(puntos, raizCuadrada));
        }
    }

No ha cambiado mucho el panorama porque mientras que en la 1ª forma hay herencia de clases, en la 2ª forma hay implementación de interfaces.

Alternativa con los delegados

1º Declaremos un delegado:

Una declaración de delegado define un tipo que encapsula un método con un determinado conjunto de argumentos y un tipo devuelto por ese método. En nuestro caso, ya hemos visto que lo que nos hace falta es algo así:

public delegate double Funcion(double valor);

Se trata de una línea de definición de tipo, estamos definiendo un tipo Funcion. Con esa línea, le estamos diciendo al compilador que cree una clase llamada Funcion. Por tanto, los delegados están orientados a objetos.

Si necesitáramos un nuevo conjunto de tipos de argumentos o de valor devuelto, tendríamos que declarar un nuevo tipo delegado.

2º Instanciar y llamar al delegado:

Una vez declarado un tipo delegado, debe crearse un objeto delegado y asociarlo con un determinado método. Al igual que los demás objetos, un objeto delegado se crea mediante una expresión new. Sin embargo, cuando se crea un delegado, el argumento que se pasa a la expresión new es especial, se escribe como una llamada a un método, pero sin los argumentos:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Delegados
{
    public class Program
    {
        public delegate double Funcion(double valor);
 
        public static double Media(double[] puntos, Funcion funcion)
        {
            double total = 0.0;
            for (int i = 0; i < puntos.Length; i++)
            {
                total += funcion(puntos[i]);
            }
            return total / puntos.Length;
        }
 
        public static void Main()
        {
            double[] puntos = new double[] {0.0,1.0,2.0 };
            Funcion raizCuadrada = new Funcion(Math.Exp);
            Console.WriteLine(Media(puntos, raizCuadrada));
        }
    }
}

Hubiéramos podido también omitir esta línea:

Funcion raizCuadrada = new Funcion(Math.Exp);

Y haber hecho esto directamente:

Console.WriteLine(Media(puntos, new Funcion(Math.Exp)));

Observe que una vez que se crea el delegado, el método con el que está asociado no cambia nunca (los objetos delegados son inmutables). 

Con C# 2.0 se puede usar la siguiente sintaxis abreviada:

Funcion raizCuadrada = Math.Sqrt;

O haber hecho esto directamente:

Console.WriteLine(Media(puntos, Math.Sqrt));

Es la ausencia de paréntesis al final de Math.Sqrt la que indica que lo que queremos es una referencia a la función, en vez de su ejecución.

Conclusiones

En la siguiente imagen podemos ver que los delegados son clases que heredan de la clase System.MulticastDelegate que a su vez hereda de la clase System.Delegate: 

1

Las signaturas de las funciones System.Math.Sqrt y System.Math.Exp cumplen con la signatura que define nuestro delegado. Si no fuera así, al ejecutarse la aplicación, CLR lo detectaría y provocaría una excepción. Esta es una prueba de que los delegados son mucho más que punteros a funciones. Los delegados por tanto, proveen un mecanismo de seguridad de tipos y son seguros.

System.Delegate contiene un par de propiedades: Method y Target:

  • Method identifica la función a ser llamada y siempre tiene el mismo valor, por eso he dicho antes que los objetos delegados son inmutables.
  • Target identifica el objeto en dónde se encuentra la función a ser llamada. Si dicha función es estática, Target será null. O sea, que un delegado no necesita conocer la clase del objeto al que hace referencia. Esto hace que los delegados sean perfectos para una invocación “anónima”.

Esto que acabo de comentar es más de lo mismo acerca de la idea de que los delegados son algo más que punteros a funciones. En un lenguaje orientado a objetos puro, los métodos no existen separados de las clases, así que había que tomar una decisión: ¿debe el delegado guardar también la referencia al objeto en particular o no? C++ opta por la vía negativa, los lenguajes .NET hacen lo contrario.

Esta última idea es ilustrada con la siguiente imagen, en la que se representa nuestro ejemplo:

1

Seguiré hablando en futuras entradas de los delegados, de los tipos de delegados que hay y de lo útiles que pueden llegar a resultar. De momento, espero que esta entrada haya servido para aclarar los conceptos fundamentales acerca de lo que son realmente los delegados.

Anuncios

4 comentarios »

  1. Kestavlaaaaando!!!!

    Habla de furbo meón, iodeputah!!!!

    Comentario por Lobezno — 18 junio 2009 @ 10:37 AM | Responder

  2. Está hablando de delegados y realmente me sirvió! gracias!
    Kestavlaaando!! … hablando es .. no avlando para fútbol http://www.olé.com.ar

    Comentario por -- — 11 agosto 2009 @ 12:32 AM | Responder

  3. Muy buena explicación!! te felicito…tienes algun material adicional?

    Comentario por Eve — 23 agosto 2009 @ 6:28 PM | Responder

    • Gracias, me alegro que te haya gustado. Pues si, claro, tengo algunos libros de c#, alguno de ellos en pdf, si te interesa te puedo pasar alguno. Un saludo.

      Comentario por elburgues — 24 agosto 2009 @ 10:58 AM | Responder


RSS feed for comments on this post. TrackBack URI

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Crea un blog o un sitio web gratuitos con WordPress.com.

A %d blogueros les gusta esto: