0% encontró este documento útil (0 votos)
31 vistas28 páginas

Uso de expresiones lambda en C#

1) Se define una clase Curso que almacena estudiantes. Se incluye un método que imprime estudiantes basado en una función de comparación anónima. 2) En Main, se crea un objeto Curso con 5 estudiantes y se imprime usando diferentes funciones lambda para filtrar por nota. 3) Las funciones lambda permiten imprimir solo estudiantes con nota >=7, con nota=2, o todos los estudiantes.

Cargado por

Remigio
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
31 vistas28 páginas

Uso de expresiones lambda en C#

1) Se define una clase Curso que almacena estudiantes. Se incluye un método que imprime estudiantes basado en una función de comparación anónima. 2) En Main, se crea un objeto Curso con 5 estudiantes y se imprime usando diferentes funciones lambda para filtrar por nota. 3) Las funciones lambda permiten imprimir solo estudiantes con nota >=7, con nota=2, o todos los estudiantes.

Cargado por

Remigio
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd

Expresiones lambda

Una expresión lambda es una función anónima que normalmente se la utiliza


para enviarla como parámetro a un método para ser evaluada en el mismo.

Problema

Definir un método estático llamado Operar. Llegan como parámetro dos enteros
y la referencia a una expresión lambda (lo debe recibir un delegado). En el bloque
del método llamar a la función que llega como parámetro y enviar los dos
primeros parámetros.
Desde la función Main llamar a Operar y enviar distintas expresiones lambdas
que permitan sumar, restar y elevar el primer valor al segundo .
using System;
using [Link];
using [Link];
using [Link];
using [Link];

namespace Lambda1
{
delegate int Operacion(int x1, int x2);

class Program
{

public static int Operar(int x1, int x2,


Operacion delegado)
{
return delegado(x1, x2);
}

static void Main(string[] args)


{
var suma = [Link](10, 3, (x,
y) => { return x + y; } );
[Link]("La suma de 10 y 3
es {0}", suma);
var resta = [Link](10, 3,
(x, y) => { return x - y; });
[Link]("La resta de 10 y
3 es {0}", resta);
var elevadoOctava =
[Link](2, 8, (x, y) => {
var valor = x;
for (int f = 1; f < y; f++)
valor = valor * x;
return valor;
});
[Link]("2 elevado a la 8
es {0}", elevadoOctava);
[Link]();
}
}
}
Declaramos un delegado llamado Operación cuya firma es que recibe dos
enteros y retorna un entero:
delegate int Operacion(int x1, int x2);
En la clase Program definimos un método estático llamado Operar que recibe
dos enteros, un delegado de tipo Operación y retorna un entero:
public static int Operar(int x1, int x2, Operacion delegado)
{
return delegado(x1, x2);
}
Dentro del método Operar llamamos a la referencia de la función anónima y el
valor que esta nos retorna la retornamos mediante un return.
En el método Main llamamos al método Operar y le enviamos un 10, un 3 y una
expresión lambda:
var suma = [Link](10, 3, (x, y) => { return x + y;
} );
Una expresión lambda tiene la siguiente sintaxis para este problema:
(x, y) => { return x + y; }
Podemos decir que es una función anónima ya que no tiene nombre, tiene dos
parámetros llamados x e y. Luego de operador => indicamos entre llaves el
algoritmo propiamente de la función anónima.
Cuando llamamos a Operar sabemos que dentro de dicho método llamará y
ejecutará la expresión lambda.
La función anónima debe respetar la firma del delegado Operación, es decir que
reciba dos enteros y retorne un entero.
Para poder obtener la resta de dos enteros llamamos al método Operar y le
pasamos dos enteros y una expresión lambda que recibe dos enteros y retorna
la diferencia entre ellos:
var resta = [Link](10, 3, (x, y) => { return x - y;
});
[Link]("La resta de 10 y 3 es {0}", resta);
Como podemos ver nuestro método Operar es bastante general y permite indicar
que operaciones hacer con los datos recibidos indicando mediante una
expresión lambda el algoritmo a ejecutar.
Para elevar el primer valor a una potencia indicada en el segundo valor debemos
implementar el siguiente algoritmo en la expresión lambda:
var elevadoOctava = [Link](2, 8, (x, y) => {
var valor = x;
for (int f = 1; f < y; f++)
valor = valor * x;
return valor;
});
[Link]("2 elevado a la 8 es {0}", elevadoOctava);
Lo más común es el empleo de lambdas con un algoritmo muy corto. En este
ejemplo el más largo es el que eleva un número a una determinada potencia.

Acotaciones

Cuando el algoritmo de una expresión lambda tiene una sola instrucción lo más
común es no disponer las llaves ni la palabra clave return (esto hace nuestro
expresión lambda más concisa):
using System;
using [Link];
using [Link];
using [Link];
using [Link];

namespace Lambda1
{
delegate int Operacion(int x1, int x2);

class Program
{

public static int Operar(int x1, int x2, Operacion delegado)


{
return delegado(x1, x2);
}

static void Main(string[] args)


{
var suma = [Link](10, 3, (x, y) => x + y );
[Link]("La suma de 10 y 3 es {0}", suma);
var resta = [Link](10, 3, (x, y) => x - y );
[Link]("La resta de 10 y 3 es {0}", resta);
var elevadoOctava = [Link](2, 8, (x, y) => {
var valor = x;
for (int f = 1; f < y; f++)
valor = valor * x;
return valor;
});
[Link]("2 elevado a la 8 es {0}", elevadoOctava);
[Link]();
}
}
}
Como vemos para sumar la expresión lambda no requiere las llaves ni la palabra
clave return:
var suma = [Link](10, 3, (x, y) => x + y );
Lo mismo para calcular la resta:
var resta = [Link](10, 3, (x, y) => x - y );
Para elevar un valor a una determinada potencia el algoritmo tiene más de una
instrucción, luego debe ir en forma obligatoria entre llaves:
var elevadoOctava = [Link](2, 8, (x, y) => {
var valor = x;
for (int f = 1; f < y; f++)
valor = valor * x;
return valor;
});
Para entender el orden de ejecución de los algoritmos podemos ejecutar cada
instrucción una a una presionando la tecla F11 del Visual Studio .net:

Podremos comprobar como llamamos al método Operar y comienza su


ejecución, en este salga nuevamente al método Main a ejecutar el algoritmo de
la expresión lambda x + y. Ahora nuevamente regresa al método Operar y recién
ejecuta el return del método Operar.
El orden temporal de ejecución de métodos lo podemos resumir con 4 pasos:
1. Llamamos al método Operar y le enviamos dos enteros y una expresión
lambda.
2. Desde el método Operar llamamos a la función anónima y le enviamos
dos enteros, que son los recibidos en el método Operar.
3. Se ejecuta el algoritmo de la función anónima (suma los parámetros x e y
y los retorna.
4. Finaliza la ejecución del método Operar retornando el valor devuelto en la
llamada al delegado.

Problema

Declarar una clase Estudiante con dos propiedades que permitan almacenar el
nombre y su nota.
Por otro lado declarar una clase llamada Curso que tenga un campo de tipo
vector con componentes de tipo Estudiante. Definir el vector de 5 elementos.
Permitir cargar estudiantes en el curso e imprimir en forma completa todos los
estudiantes.
Definir un método en la clase Curso que reciba como parámetro un delegado
que tiene un parámetro de tipo entero y retorna un true o false indicando si se
debe imprimir los datos del estudiante.
En la función Main definir un objeto de la clase Curso, cargar 5 estudiantes.
Luego imprimir:
Todos los estudiantes.
Todos los estudiantes que tienen una nota mayor o igual a 7.
Todos los estudiantes que tienen un 2.
using System;
using [Link];
using [Link];
using [Link];
using [Link];

namespace Lambda2
{
public delegate bool Comparacion(int
elemento);

public class Estudiante


{
public String Nombre { get; set; }
public int Nota { get; set; }
}

public class Curso


{
private Estudiante[] vec = new
Estudiante[5];

public void Cargar(int pos, Estudiante


est)
{
vec[pos] = est;
}

public void ImprimirTodo()


{
foreach(var elemento in vec)
[Link]("Nombre: {0}
Nota: {1}", [Link], [Link]);
}

public void ImprimirSi(Comparacion


compara)
{
foreach(var elemento in vec)
if (compara([Link]))
[Link]("Nombre:
{0} Nota: {1}", [Link], [Link]);
}
}

class Program
{
static void Main(string[] args)
{
Curso curso1 = new Curso();
[Link](0, new Estudiante {
Nombre = "jose", Nota = 9 });
[Link](1, new Estudiante {
Nombre = "ana", Nota = 10 });
[Link](2, new Estudiante {
Nombre = "luis", Nota = 2 });
[Link](3, new Estudiante {
Nombre = "pedro", Nota = 7 });
[Link](4, new Estudiante {
Nombre = "carla", Nota = 3 });
[Link]("Listado completo
de alumnos");
[Link]();
[Link]("Listado completo
de estudiantes con notas mayores o iguales a
7");
[Link]((nota) => nota >=
7);
[Link]("Listado completo
de estudiantes que tienen un 2");
[Link]((nota) => nota ==
2);
[Link]("Listado completo
de estudiantes empleando el método ImprimirSi");
[Link]((nota) => true);
[Link]();
}
}
}
Primero declaramos un delegado que recibe un parámetro entero y retorna un
valor bool (es decir un true o false):
public delegate bool Comparacion(int elemento);
Declaramos la clase Estudiante con sus dos propiedades:
public class Estudiante
{
public String Nombre { get; set; }
public int Nota { get; set; }
}
Declaramos la clase Curso con un atributo de tipo vector de 5 elementos de tipo
Estudiante:
public class Curso
{
private Estudiante[] vec = new Estudiante[5];
El método Cargar recibe como parámetro un objeto de la clase Estudiante y la
posición dentro del vector donde almacenarlo:
public void Cargar(int pos, Estudiante est)
{
vec[pos] = est;
}
El método ImprimirTodo muestra el vector de estudiantes en forma completo:
public void ImprimirTodo()
{
foreach(var elemento in vec)
[Link]("Nombre: {0} Nota: {1}",
[Link], [Link]);
}
El método ImprimirSi recibe como parámetro un tipo de dato Comparacion que
es un delegate. Dentro del método recorremos con un foreach cada elemento
del vector y llamamos a la expresión lambda que nos retorna si debemos imprimir
dicho estudiante:
public void ImprimirSi(Comparacion compara)
{
foreach(var elemento in vec)
if (compara([Link]))
[Link]("Nombre: {0} Nota: {1}",
[Link], [Link]);
}
}
Como podemos ver podemos llamar a la expresión lambda varias veces, una por
cada nota de estudiante almacenada en el vector.
En el método Main creamos un objeto de la clase Curso y almacenamos 5
estudiantes:
static void Main(string[] args)
{
Curso curso1 = new Curso();
[Link](0, new Estudiante { Nombre = "jose", Nota = 9
});
[Link](1, new Estudiante { Nombre = "ana", Nota = 10
});
[Link](2, new Estudiante { Nombre = "luis", Nota = 2
});
[Link](3, new Estudiante { Nombre = "pedro", Nota =
7 });
[Link](4, new Estudiante { Nombre = "carla", Nota =
3 });
Para imprimir todos los estudiantes llamamos al método ImprimirTodo:
[Link]("Listado completo de estudiantes");
[Link]();
Para imprimir los datos de estudiantes que tienen una nota igual o superior a 7
llamamos al método ImprimirSi y le pasamos una expresión lambda donde
analizamos si la nota que llega como parámetro a la expresión lambda cumple
con la condición de ser mayor o igual a 7:
[Link]("Listado completo de estudiantes con notas
mayores o iguales a 7");
[Link]((nota) => nota >= 7);
Para imprimir todos los estudiantes que tienen una nota de 2 volvemos a llamar
al método ImprimirSi pasando otro expresión lambda:
[Link]("Listado completo de estudiantes que
tienen un 2");
[Link]((nota) => nota == 2);
También podemos utilizar el método ImprmirSi para imprimir todos los
estudiantes empleando una expresión lambda cuyo algoritmo retorna siempre
true:
[Link]("Listado completo de estudiantes
empleando el método ImprimirSi");
[Link]((nota) => true);

Acotaciones

El lenguaje C# nos permite eliminar los paréntesis cuando la expresión lambda


tiene un solo parámetro, luego el código queda con una sintaxis más concisa:
[Link]("Listado completo de estudiantes con notas
mayores o iguales a 7");
[Link](nota => nota >= 7);
[Link]("Listado completo de estudiantes que
tienen un 2");
[Link](nota => nota == 2);
[Link]("Listado completo de estudiantes
empleando el método ImprimirSi");
[Link](nota => true);

Problema

Plantear un método estático que reciba un string y retorne otro. Mediante una
expresión lambda analizar cada caracter del string original y verificar si lo debe
agregar al string de retorno.
El método debe llamarse Filtrar y luego debemos acceder al mismo desde la
Main para obtener:
Todas las vocales que contiene un string.
Solo los dígitos.
Solo los caracteres minúsculas.
Solo los caracteres y números.
using System;
using [Link];
using [Link];
using [Link];
using [Link];

namespace Lambda3
{
public delegate bool Agrega(char caracter);
class Program
{
public static string Filtrar(string
cadena, Agrega agrega)
{
StringBuilder cadenaNueva = new
StringBuilder();
foreach(var caracter in cadena)
if (agrega(caracter))

[Link](caracter);
return [Link]();
}

static void Main(string[] args)


{
var cadena = "1 Hola 2 Mundo 3";
[Link]("Cadena
original:{0}", cadena);
var cad1 = [Link](cadena ,
caracter => caracter == 'a' || caracter == 'e'
|| caracter == 'i' || caracter == 'o' ||
caracter == 'u' ||

caracter == 'A' || caracter == 'E' || caracter


== 'I' || caracter == 'O' || caracter == 'U');
[Link]("Solo las vocales:
{0}", cad1);
var cad2 = [Link](cadena,
caracter => [Link](caracter));
[Link]("Solo los dígitos:
{0}",cad2);
var cad3 = [Link](cadena,
caracter => [Link](caracter));
[Link]("Solo los
caracteres minúsculas: {0}", cad3);
var cad4 = [Link](cadena,
caracter => [Link](caracter) ||
[Link](caracter));
[Link]("Solo los
caracteres y números: {0}", cad4);
[Link]();
}
}
}
La estructura del método delegado es:
public delegate bool Agrega(char caracter);
El método Filtrar recibe un string y la referencia a la expresión lambda:
public static string Filtrar(string cadena, Agrega agrega)
Creamos un StringBuilder para generar el string a retornar:
{
StringBuilder cadenaNueva = new StringBuilder();
Mediante un foreach recorremos el string original y por cada caracter llamamos
mediante el delegado a la expresión lambda que nos indica si debemos añadir o
no el caracter al string de retorno:
foreach(var caracter in cadena)
if (agrega(caracter))
[Link](caracter);
return [Link]();
}
En la función Main definimos un string al cual le aplicaremos distintos filtros:
static void Main(string[] args)
{
var cadena = "1 Hola 2 Mundo 3";
[Link]("Cadena original:{0}", cadena);
Para generar un string solo con las vocales contenidas en otro string debemos
pasar la siguiente expresión lambda:
var cad1 = [Link](cadena , caracter => caracter ==
'a' || caracter == 'e' || caracter == 'i' || caracter == 'o' || caracter
== 'u' ||
caracter
== 'A' || caracter == 'E' || caracter == 'I' || caracter == 'O' ||
caracter == 'U');
[Link]("Solo las vocales: {0}", cad1);
Para filtrar solo los dígitos contenidos en un string en el algoritmo de la expresión
lambda verificamos cada caracter si se trata de un dígito:
var cad2 = [Link](cadena, caracter =>
[Link](caracter));
[Link]("Solo los dígitos: {0}",cad2);
De forma similar podemos filtrar si es un caracter minúscula:
var cad3 = [Link](cadena, caracter =>
[Link](caracter));
[Link]("Solo los caracteres minúsculas: {0}",
cad3);
Por último si queremos filtrar solo las letras o números debemos implementar:
var cad4 = [Link](cadena, caracter =>
[Link](caracter) || [Link](caracter));
[Link]("Solo los caracteres y números: {0}",
cad4);

Introducción rápida a LINQ


con C#: manejar información
en memoria nunca fue tan
sencillo
Por Jorge Turrado. Publicado el 21 de agosto de 2019 a las 08:00

💡 Aprende con nosotros [Link] Core 3 MVC. A tu ritmo y sin horarios.

Como programadores, es muy habitual tener que trabajar sobre colecciones de datos por
un motivo u otro, seleccionar datos, agruparlos, sumarlos ...

Una manera muy socorrida de trabajar con este tipo de datos es recorrer la colección. De
hecho, es la solución para la gran mayoría de los lenguajes y la que mejor rendimiento
nos ofrece.

Suponiendo que tenemos una lista de enteros, si queremos sumarlos podríamos hacer algo
así:

var valores = new List<int> {1,2,3,4,5,6,7,8,9};


var suma = 0;
foreach (var valor in valores)
{
suma += valor;
}

O si por ejemplo, queremos buscar los números que sean pares:

var valores = new List<int> {1,2,3,4,5,6,7,8,9};


var pares = new List<int>();
foreach (var valor in valores)
{
if (valor % 2 == 0)
{
[Link](valor);
}
}

La lista de ejemplos es infinita, y siempre vamos a poder encontrar una solución iterando
la colección, pero... ¿Es la mejor opción?

¿Qué es LINQ?

Dicho de manera sencilla, LINQ (Language Integrated Query) es un conjunto de


extensiones integradas en el lenguaje C#, que nos permite trabajar de manera cómoda y
rápida con colecciones de datos, como si de una base de datos se tratase. Es decir,
podemos llevar a cabo inserciones, selecciones y borrados, así como operaciones sobre
sus elementos.

Language-Integrated Query (LINQ) es el nombre de un conjunto de tecnologías


basadas en la integración de capacidades de consulta directamente en el lenguaje
C#
Fuente: MSDN

Todas estas operaciones las vamos a conseguir muy fácilmente gracias a los métodos de
extensión para colecciones que nos ofrece el espacio de nombres "[Link]" y a
las expresiones lambda. Sin ir más lejos, los dos ejemplos anteriores se podrían hacer
así:

var valores = new List<int> {1,2,3,4,5,6,7,8,9};


var suma = [Link]();
var pares = [Link](x => x % 2 == 0).ToList();
Como se puede comprobar, la lectura es mucho más clara, por lo que ganamos en
mantenibilidad del código.

En el ejemplo anterior, vemos que LINQ puede devolvernos valores de operaciones, o


devolvernos colecciones.

Seguramente te hayas fijado en el ToList() del segundo caso. Esto es porque LINQ
siempre nos va a devolver un objeto de tipo IEnumerable<T>, el cual debemos
iterar. Hasta que no lo iteremos, la consulta no se ha ejecutado todavía, y solo
tenemos una expresión sobre una colección, por eso invocamos ToList() para
forzar la ejecución de la consulta.

Sobre la ejecución diferida de consultas se puede hablar largo y tendido ya que es


una materia en sí misma. Para más información puedes consultar este enlace.

Vamos a ver un resumen de algunas de sus herramientas más útiles.

Para los ejemplos, vamos a suponer una clase como esta:

public class Alumno


{
public string Nombre { get; set; }

public int Nota { get; set; }


}

Y partamos de la base de que tenemos una colección de alumnos como esta:

var alumnos = new List<Alumno>


{
new Alumno {Nombre = "Pedro",Nota = 5},
new Alumno {Nombre = "Jorge",Nota = 8},
new Alumno {Nombre = "Andres",Nota = 3}
};

Ahora veamos algunas de las operaciones que podemos llevar a cabo con LINQ sobre
esta:

Select
Nos va a permitir hacer una selección sobre la colección de datos, ya sea seleccionándolos
todos, solo una parte o transformándolos:

var nombresAlumnos = [Link](x =>


[Link]).ToList();

Where
Nos permite seleccionar una colección a partir de otra con los objetos que cumplan las
condiciones especificadas:

var alumnosAprobados = [Link](x => [Link] >=


5).ToList();

First/Last
Esta extensión nos va a permitir obtener respectivamente el primer y el último objeto de
la colección. Esto es especialmente útil si la colección está ordenada.

var primero = [Link]();


var ultimo = [Link]();

OrderBy
Gracias a este método, vamos a poder ordenar la colección en base a un criterio de
ordenación que le indicamos mediante una expresión lambda. Análogamente, también
existe OrderByDescending, el cual va a ordenar la colección de manera inversa
según el criterio:

var ordenadoMenorAMayor = [Link](x =>


[Link]).ToList();

var ordenadoMayorAMenos = [Link](x =>


[Link]).ToList();

Sum
Como hemos visto más arriba, nos va a permitir sumar la colección:

var sumaNotas = [Link](x => [Link]);

Max/Min
Gracias a esta extensión, vamos a poder obtener los valores máximo y mínimo de la
colección:

var notaMaxima = [Link](x => [Link]);

var notaMinima = [Link](x => [Link]);

Average
Este método nos va a devolver la media aritmética de los valores (numéricos) de los
elementos que le indiquemos de la colección:

var media = [Link](x => [Link]);

All/Any
Con este último operador, vamos a poder comprobar si todos o alguno de los valores de
la colección cumplen el criterio que le indiquemos:

var todosAprobados = [Link](x => [Link] >= 5);

var algunAprobado = [Link](x => [Link] >= 5);

Sintaxis integrada

Aunque en los ejemplos anteriores hemos visto el uso directo de los métodos de extensión,
otra de las grandes ventajas que tiene LINQ es que permite crear expresiones directamente
en el código, de manera similar a si escribiésemos SQL directamente en C#. Por ejemplo:

var resultado = from alumno in alumnos


where [Link] >= 5
orderby [Link]
select alumno;

nos devolverá la lista de alumnos que tienen una nota superior a o igual a 5, ordenados
por nota ascendentemente.

¿No es algo casi mágico?

Ventajas y desventajas

Ahora que hemos visto un poco por dónde pisamos, es hora de que hablemos sobre las
ventajas y desventajas que nos puede aportar utilizar LINQ en vez de iterar las
colecciones.

La principal y única desventaja que tiene, es que es un poco más lenta que si
utilizásemos bucles for o foreach para iterar la colección y hacer la operación. Por
supuesto esto no es apreciable en prácticamente ninguna situación convencional, pero en
entornos donde cada milisegundo cuenta, debes conocer que tiene un impacto.

Por otro lado, las ventajas que nos aporta LINQ son principalmente que el código es
más legible, ya que utiliza una sintaxis muy declarativa de lo que está haciendo, y sobre
todo, nos ofrece una manera unificada de acceder a datos, sean el tipo que sean, y tengan
el origen que tengan. Por ejemplo, podemos utilizar LINQ para trabajar con bases de
datos, con XML, con Excel, con objetos en memoria, ¡y hasta con Twitter!

Resumiendo

Pese a que en esta entrada solo hemos hecho una pequeña introducción con un resumen
reducido de las extensiones más frecuentes que nos aporta LINQ (créeme que muy
pequeño... te recomiendo mirar el espacio de nombres y ver todas sus opciones), es una
herramienta muy potente. Tanto, que otros lenguajes la han implementado también.

Si bien es cierto que existe una merma de rendimiento respecto a iterar el bucle
directamente, el rendimiento perdido en el 99,99% de los casos se compensa con el
beneficio que aporta tener un código claro, legible y mantenible.

Examen 70-483: Certifica tu dominio de C# y diferénciate del resto

Jorge lleva en el mundo de la programación desde los tiempos de .Net Framework


3.0. Experto en la plataforma .NET, .NET Core y en técnicas de integración continua, trabaja desde
hace varios años en el desarrollo de sistemas comerciales de visión artificial. Microsoft lo ha
reconocido como MVP en tecnologías de desarrollo en 2018. Puedes seguirlo en
Twitter: @JorgeTurrado o en su blog FixedBufferVer todos los posts de Jorge Turrado
Archivado en: Lenguajes y plataformas

3 comentarios

¿Te ha gustado este post?


Pues espera a ver nuestro boletín
mensual...

¿Te ha gustado este artículo? ¡Compártelo!

Si nuestro blog te parece interesante, espera a ver


nuestros cursos
Ver cursos
Comentarios (3) -

David
20/09/2019 23:04:16

Hola Jorge gracias por tu aporte.

Cuando se utilizan los métodos de extensión, que es lo que hace realmente C#? como recorre los
registros? "por debajo" lo que hace es hacer un for o foreach?

Si fuera así la ventaja seria como dices que el código se vuelve más legible y realizar filtros de
manera más fácil.
Responder

Jorge Turrado
21/09/2019 23:00:26

Buenas tardes David,


Muchas gracias por tu comentario!!
Depende un poco de la extensión, por ejemplo, si vemos el código de la extensión "Max" en
GitHub [Link]/.../[Link]#L28
No esta haciendo ni uno ni otro del todo, esta utilizando el GetEnumerator y Next(), pero podríamos
decir que eso es el "equivalente" a utilizar un foreach.
La implementación de "Last", comprueba si la colección es "indexable", y si es indexable accede al
último indice, y sino lo es la itera (con el mismo método que el anterior) para quedarse con el final:
[Link]/.../[Link]#L55

Cada método de extensión trabaja de manera un poco diferente ya que para optimizarlos no se puede
generalizar. Como el código es público, puedes echarle un ojo al funcionamiento de Linq:
[Link]/.../[Link]

Y sí, la gran ventaja de Linq es la facilidad de uso y la legibilidad que aporta.


Responder

Marcelo Pfannkuche
04/10/2019 9:36:34

LINQ para mí todavía es un "cuco", cosas de la edad... pero lo has explicado tan bien y sencillo que
tal vez me anime a ahondar más profundo en el tema. Exelente aporte!

(Marcelo PF)

Saludos
Responder
Agregar comentario

Nombre*

Correo Electrónico*

Sitio Web
Comentario*

Notificarme cuando se publiquen nuevos comentarios

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y,


si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se
procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.

Huevo, perro, abeja o viernes, ¿cuál es un día de la semana?

SENTENCIAS EN LINQ (V):


FUNCIONES DE AGREGACIÓN

6 Votes

Agregación, agrupación… son dos conceptos que parecen iguales pero que
corresponden a características distintas dentro del álgebra relacional.

La agrupación, tal y como vimos en artículos anteriores, consiste en generar un


conjunto de datos que poseen una característica común. Por ejemplo, agrupar los
pedidos por cliente implicaría la obtención de varios subconjuntos (un subconjunto por
cliente) compuestos por un dato que identifique unívocamente al cliente y sus pedidos
asociados. Para realizar esta operación hacíamos uso de la sentencia group by.

La agregación está relacionada con la agrupación, ya que en lugar de proyectar un


conjunto de datos asociados a otro, realiza una operación aritmética sobre uno o
varios de estos datos. Por ejemplo, en lugar de obtener toda la información de los
pedidos asociados a un cliente, una operación de agregación consistiría en obtener,
por ejemplo el número de pedidos asociados a un cliente. Como podremos imaginar,
todo cálculo de agregación debe tener asociada, por definición, una operación de
agregación.

Las operaciones de agregación, por lo tanto, son de carácter matemático, y se suelen


corresponder con las siguientes cinco operaciones:

 Cuenta (Count): devuelve el número de registros pertenecientes a la


agrupación.
 Sumatorio (Sum): devuelve la suma de todos los valores de un campo numérico
concreto perteneciente a la agrupación.
 Máximo (Max): devuelve el máximo de los valores de un campo numérico
concreto perteneciente a la agrupación.
 Mínimo (Min): devuelve el mínimo de los valores de un campo numérico
concreto perteneciente a la agrupación.
 Media (Average): devuelve la media aritmética de un campo numérico concreto
perteneciente a la agrupación.

Existen muchas otras posibles operaciones de agrupación, generalmente de carácter


estadístico, como la varianza o desviación típica. Sin embargo, las cinco operaciones
anteriores son las más extendidas en cualquier modelo de datos relacional, y por lo
tanto, las más utilizadas.

Count

Devuelve el número de registros de la agrupación. Por ejemplo, el número de líneas de


pedido pertenecientes a un pedido.

var consulta = from lineaPedido in


[Link]
group lineaPedido by [Link]
into grupo
select new
{
IdPedido = [Link],
NumPedidos = [Link]()
};

REPORT THIS AD
Distinct

Es posible restringir la operación count para que únicamente tenga en consideración


los elementos únicos dentro de un conjunto de datos. De este modo, evitaremos
contar elementos iguales dos veces. Por ejemplo, un cliente puede haber realizado más
de un pedido, pero no todos los clientes tienen por qué haber realizado algún pedido.

Con la siguiente sentencia obtendremos los clientes que han realizado al menos un
pedido, ya que si un mismo identificador de cliente aparece más de una vez, éste no
será tenido en cuenta para el cómputo, devolviendo únicamente los registros distintos.
Así, el siguiente código efectuará un cómputo de todos los elementos presentes en el
listado:

var consulta = from pedido in [Link]


select [Link];

[Link]([Link]("Existe un total de {0}


pedidos realizados por clientes.",
[Link]()));

REPORT THIS AD

Sin embargo, si anteponemos el método Distinct antes de ejecutar el método Count,


filtraremos el resultado ignorando aquellos valores repetidos:

var consulta = from pedido in [Link]


select [Link];

[Link]([Link]("Existe un total de {0}


clientes distintos que han realizado pedidos.",
[Link]().Count()));
No obstante, este caso es sencillo: se trata de un listado de números sobre los que es
muy sencillo realizar una comparación. Pero, ¿qué ocurriría si el listado fuese de
pedidos completos? Hagamos la prueba:

var consulta = from pedido in [Link]


select pedido;

[Link]([Link]("Existe un total de {0}


clientes distintos que han realizado pedidos.",
[Link]().Count()));

REPORT THIS AD

¡¡Error!! Hemos realizado un cómputo de todos los objetos distintos dentro del listado.
Y aunque existan pedidos que pertenezcan a un mismo cliente, lo que realiza el
método Distinct es ignorar aquellos elementos que sean completamente iguales, por lo
que al no haber dos pedidos iguales, devolverá su totalidad.

¿Cómo haremos entonces para recuperar únicamente aquellos cuyo IdCliente sea
distinto? Pues haciendo uso de algo que vimos brevemente en el artículo anterior:
las expresiones lambda.

Los objetos que implementan las interfaces IEnumerable e IQueryable exponen un


método Select() que permite realizar proyecciones tal y como realizamos de forma
nativa en LINQ. Así, los siguientes métodos serían equivalentes:

var consultaLinq = from pedido in [Link]


select [Link];

var consultaLambda = [Link](pedido


=> [Link]);

El método Select puede recibir como argumento un delegado a una función que se
encargue de realizar el filtrado. Por lo tanto, si le pasamos una expresión lambda con
el campo que queremos utilizar como filtro, restringiremos automáticamente los
campos que recuperará la invocación de este método.

var consulta = from pedido in [Link]


select pedido;

[Link]([Link]("Existe un total de {0}


clientes distintos que han realizado pedidos.",
[Link](elemento =>
[Link]).Distinct().Count()));

REPORT THIS AD

Si queremos ir más allá, podríamos combinar una sentencia LINQ con una expresión
lambda y hacer uso del método First o FirstOrDefault para recuperar únicamente el
primer elemento de cada grupo, obteniendo así el primer pedido realizado por cada
cliente. Así, su conteo simbolizaría el número de clientes que han realizado pedidos.

var consulta = (from pedido in [Link]


select pedido).GroupBy(ped =>
[Link]).Select(p => [Link]());

Sum

La siguiente función de agregación será Sum, que devuelve una suma de todos los
valores de un campo concreto perteneciente a la lista. Así, la siguiente operación
devolvería la suma del beneficio obtenido por todas las ventas:

// Recuperamos una lista de registros compuesto por un


elemento float
// que contiene el producto de la cantidad por el precio.
var consulta = (from lineaPedido in
[Link]
join producto in [Link]
on [Link] equals
[Link]
select [Link] *
[Link]);

// Usamos la función de agregación Sum para calcular la


suma de todos los elementos
float resultadoTotal = [Link]();

// Mostramos los elementos de la consulta


int i = 1;
foreach (var valor in consulta)
{
[Link]([Link]("Cantidad obtenida en
la línea de venta {0}: {1}",
i++, valor));
}

// Mostramos el total
[Link]([Link]("Total ingresos: {0}",
resultadoTotal));

REPORT THIS AD

Esto nos mostrará la siguiente información:

Sin embargo, la solución no es del todo elegante: un listado de


elementos float proporciona muy poca información. Podemos refinar un poco los datos
haciendo que la consulta devuelva un tipo anónimo con un poco más de información y
añadiendo una expresión lambda al método Sum que le indique cuál de los campos
debe sumar.

// Recuperamos una lista de registros anónimos compuestos


por cuatro campos
// que identifican nombre, precio, cantidad y precio
total.
var consulta = (from lineaPedido in
[Link]
join producto in [Link]
on [Link] equals
[Link]
select new
{
NombreProducto = [Link],
PrecioUnitario = [Link],
Unidades = [Link],
PrecioTotal = [Link] *
[Link]
});

// Usamos la función de agregación Sum para calcular la


suma del campo "PrecioTotal"
// de todos los elementos de tipo anónimo devueltos por la
consulta.
float resultadoTotal = [Link](elemento =>
[Link]);

// Mostramos los elementos de la consulta


foreach (var elemento in consulta)
{
[Link]([Link]("Producto {0}: {1} EUR
x {2} = {3}",
[Link], [Link],
[Link], [Link]));
}

// Mostramos el total
[Link]([Link]("Total ingresos: {0}",
resultadoTotal));

REPORT THIS AD

Como observamos, el resultado proporciona un poco más de información:

Por supuesto, es posible utilizar este método combinándolo con otras operaciones,
como por ejemplo una agrupación.

// Recuperamos una lista de registros anónimos compuestos


por cuatro campos
// que identifican nombre, precio, cantidad y precio
total.
var consulta = (from lineaPedido in
[Link]
join producto in [Link]
on [Link] equals
[Link]
group lineaPedido by new
{
IdPedido = [Link],
NombreProducto = [Link],
PrecioUnitario = [Link],
Unidades = [Link]
} into grupoPedido
select new
{
NombreProducto =
[Link],
PrecioProducto =
[Link],
Unidades = [Link],
CantidadElementos =
[Link](linea => [Link]),
PrecioLinea = [Link](linea =>
[Link]) * [Link]
});

// Usamos la función de agregación Sum para calcular la


suma del campo "PrecioTotal"
// de todos los elementos de tipo anónimo devueltos por la
consulta.
float resultadoTotal = [Link](elemento =>
[Link]);

// Mostramos los elementos de la consulta


foreach (var elemento in consulta)
{
[Link]([Link]("Producto {0}: {1} EUR
x {2} = {3}",
[Link], [Link],
[Link], [Link]));
}

REPORT THIS AD

El resultado, como vemos, sera el mismo que en el caso anterior.


Max

Devuelve el máximo valor de un campo concreto dentro de un listado. Así, en el código


anterior podríamos obtener el valor máximo de una venta aplicándole la siguiente
función a la consulta:

float valorMaximo = [Link](elemento =>


[Link]);

[Link]([Link]("La mayor venta ha sido de


{0} EUR",
valorMaximo));

El resultado será el registro cuyo campo PrecioLinea sea mayor, en este caso, un
pedido de grapadoras.

Min

Devuelve el mínimo valor de un campo concreto dentro de un listado. Similar al caso


anterior.

float valorMinimo = [Link](elemento =>


[Link]);
[Link]([Link]("La menor venta ha sido de
{0} EUR",
valorMinimo));

REPORT THIS AD

El resultado será el registro cuyo campo PrecioLinea sea menor, en este caso, un
pedido de grapadoras.

Average

Devuelve el valor medio de un campo concreto de un listado. Nuevamente, similar a los


casos anteriores.

float valorMedio = [Link](elemento =>


(float)[Link]);

[Link]([Link]("El valor medio de venta


ha sido de {0} EUR",
valorMedio));

Y con esto concluimos la introducción a las funciones de agregación en LINQ.

También podría gustarte