Qué podemos hacer con los Delegados Genéricos. Fundamentos de LINQ III

Otro de los aspectos fundamentales de LINQ son los delegados genéricos introducidos en .NET Framework 2.0. En la mayoría de los métodos de extensión de la interfaz IEnumerable tenemos funciones en modo de predicados, selectores, selectores claves, etc. Todos ellos son, en su mayoría, representados por los delegados genéricos Action y Func.

En este artículo veremos como estos delegados genéricos encajan en lo que hemos explicado anteriormente cuando hablábamos de los métodos de extensión o de las características de la interfaz genérica IEnumerable.

Delegados genéricos: Func<> and Action<>

Básicamente son System.Func<> y System.Action<> y ambos funcionan de una forma muy simliar. La principal diferencia entre ellos es que Func retorna un valor del tipo genérico especificado, por ejemplo si Func<string , bool> entonces obtiene un argumento de tipo string y devuelve un valor del tipo bool, mientras que Action no devuelve nada, es decir que Action<string ,bool> obtendría dos argumentos de tipo string y bool respectivamente. Veamos un ejemplo de cada uno de ellos.

public static void DelegadosAnonimosFunc()
{
    Func<string , bool> esPar = (s => s.Count()%2 == 0);
    Console.WriteLine(esPar("LoEs")); //True
}
public static void DelegadosAnonimosAction()
{
    string cadena = "cadena cualquiera...";

    Action<string , bool> enMayuscula = (s, b) => cadena = (b == true ? s.ToUpper() : s.ToLower());

    enMayuscula(cadena, true);
    Console.WriteLine(cadena);//CADENA_CUALQUIERA...

    enMayuscula(cadena, false);
    Console.WriteLine(cadena);//cadena_cualquiera...
}

Hay que presta especial atención a la sobrecarga con pues cada uno de los tipos genéricos especificados implican un argumento y estos pueden variar en el número. En el ejemplo hemos utilizado un parámetro de entrada y salida para Func<> y dos de entrada para Action<>.

Veamos como encajan estos métodos en lo visto hasta ahora. Cojamos el ejemplo siguiente:

//creamos un origen de datos
IEnumerable<cliente> list = new List<cliente>
        {
            new Cliente {IdProvincia = 1, Nombre = “Cliente1”, 
                Tipo = 10, VolumenNegocio = 100.00m},   
            new Cliente {IdProvincia = 2, Nombre = “Cliente2”, 
                Tipo = 20, VolumenNegocio = 20.00m},   
            new Cliente {IdProvincia = 3, Nombre = “Cliente3”, 
                Tipo = 20, VolumenNegocio = 230.00m},   
            new Cliente {IdProvincia = 3, Nombre = “Cliente4”, 
                Tipo = 20, VolumenNegocio = 500.00m},   
            new Cliente {IdProvincia = 1, Nombre = “Cliente5”, 
                Tipo = 30, VolumenNegocio = 10.00m},   
            new Cliente {IdProvincia = 2, Nombre = “Cliente6”, 
                Tipo = 10, VolumenNegocio = 750.00m},   
            new Cliente {IdProvincia = 1, Nombre = “Cliente7”, 
                Tipo = 20, VolumenNegocio = 340.00m},   
            new Cliente {IdProvincia = 1, Nombre = “Cliente8”, 
                Tipo = 20, VolumenNegocio = 170.00m}   
        };
//obtenemos el nombre de los clientes de la provincia 1 
IEnumerable<Cliente> resultado = list.Where(s => s.IdProvincia == 1).Select(s=> s);

Básicamente hacemos una consulta utilizando los operadores de LINQ sobre un conjunto de objetos en memoria. En ella seleccionamos (Select) todos los objetos Cliente cuya Provincia (esto es propiedad getter Cliente.IdProvincia) es igual a 1 (Where). Ahora indaguemos en los métodos Select y Where exponiendo su definición.

public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source,
                                                        Func<TSource, bool> predicate);

    public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source,
                                                        Func<TSource, int, bool> predicate);

    public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source,
                                                                Func<TSource, TResult> selector);

    public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source,
                                                                Func<TSource, int, TResult> selector);
    
    //...
}

Fijémonos que se tratan de métodos de extensión sobre la interfaz genérica IEnumerable agrupados en la clase estática IEnumerable. Únicamente se han expuesto 4 métodos, o mejor dicho 2 métodos con 2 sobrecargas, correspondientes a los métodos Select y Where. Realmente existen muchos más pero los obviaremos por el momento.

En cuanto a los métodos Where en ambos casos recibe un argumento llamado predicate del tipo delegado genérico de Func. Cuando hablemos de los operadores veremos las agrupaciones correspondientes, pero sin entrar en demasiados detalles, el método Where, recibe un objeto del tipo Cliente y retorna un predicado de tipo bool, es decir, en el caso del ejemplo mostrado anteriormente el tipo de predicate seria Func<cliente , bool>.

En el método Select tres cuartas partes de lo mismo. La diferencia es que Select proyecta el resultado, es decir no es un predicado que deba cumplir una condición, y por tanto su firma será Func<Cliente , Cliente>.

Ahora que hemos visto la relación entre métodos de extensión, la interfaz IEnumerable y los delegados genéricos, en el proximo artículo hablaremos de las expresiones lambda, métodos anónimos y su uso como expresiones en delegados anónimos.

Portada de Genbeta