Nota
L'accés a aquesta pàgina requereix autorització. Podeu provar d'iniciar la sessió o de canviar els directoris.
L'accés a aquesta pàgina requereix autorització. Podeu provar de canviar els directoris.
Tip
¿No está familiarizado con el desarrollo de software? Comience primero con los tutoriales de introducción . Desarrolle las habilidades de tipos y métodos antes de usar expresiones lambda.
A veces usted quiere pasar una pequeña porción de comportamiento, una función, directamente a otro método. Por ejemplo, es posible que quiera filtrar una lista, pero la condición de filtrado cambia en función de la situación. En lugar de escribir un método con nombre independiente para cada condición posible, se pasa la propia condición como argumento.
Las expresiones lambda son la característica de C# que hace posible esto. Una expresión lambda es una función insertada compacta que se escribe sin asignarle un nombre. Use el operador => de flecha para separar la lista de parámetros del cuerpo:
x => x * 2
Lectura de izquierda a derecha: x es el parámetro de entrada, => significa "va a" y x * 2 es el cuerpo. Calcula el valor devuelto. Cuando no hay parámetros o más de uno, encapsularlos entre paréntesis: () => 42 o (left, right) => left + right.
Los delegados admiten expresiones lambda
Para usar una expresión lambda, el compilador de C# debe conocer dos cosas: los tipos de los parámetros y el tipo de valor devuelto. Esa descripción, los tipos de parámetro más el tipo de valor devuelto, se denominan tipo delegado.
Un tipo delegado es un tipo que representa una firma de método. Una variable de un tipo delegado puede contener cualquier método coincidente, como una expresión lambda o un método con nombre, siempre y cuando sus tipos de parámetros y el tipo de valor devuelto coincidan.
Declara un tipo de delegado con la delegate palabra clave :
delegate int Transform(int value);
Esta declaración dice: "Transform es un tipo delegado para los métodos que aceptan uno int y devuelven un int". A continuación, puede asignar una expresión lambda o un método con nombre a una variable de ese tipo:
Transform doubler = x => x * 2; // assign a lambda expression
Transform squarer = Square; // assign a named method
Console.WriteLine(doubler(5)); // 10
Console.WriteLine(squarer(5)); // 25
static int Square(int value) => value * value;
Tanto doubler como squarer contienen un valor de tipo Transform. Se invocan exactamente igual que los métodos normales. El compilador comprueba que lo que asigne coincida con la firma declarada.
Tipos de delegados integrados: Func y Action
Declarar un tipo de delegado personalizado para cada situación puede ser repetitivo. .NET proporciona dos familias de tipos de delegado genéricos, Func y Action, que cubren la mayoría de los escenarios, por lo que rara vez necesita usar la palabra clave delegate usted mismo.
Ambas familias vienen en versiones con cero a dieciséis parámetros de tipo de entrada, por lo que se escalan a cualquier número de entradas. La diferencia clave entre las dos familias es:
-
System.Func<T,TResult> (y
Func<T1, T2, TResult>, etc.) representa un método que devuelve un valor. El último parámetro de tipo es siempre el tipo de valor devuelto; todos los anteriores son tipos de entrada. -
System.Action<T> (y
Action<T1, T2>, etc.) representa un método que no devuelve nada (void). Todos los parámetros de tipo son tipos de entrada. System.Action sin parámetros de tipo representa un método sin entradas y ningún valor devuelto.
Por ejemplo, Func<int, int, int> describe un método con dos int entradas y un int resultado.
Action<string> describe un método con una string entrada y ningún valor devuelto.
Func<int, int, int> add = (left, right) => left + right;
Action<string> report = message => Console.WriteLine($"Report: {message}");
int total = add(5, 9);
report($"5 + 9 = {total}");
Use nombres de parámetro descriptivos en expresiones lambda para que los lectores puedan comprender la intención sin examinar el cuerpo completo del método.
Pasar una expresión lambda a un método
Cuando un método declara un Func parámetro o Action , los llamadores pasan una expresión lambda que coincide con ese tipo delegado. El compilador comprueba que los tipos de parámetros de la lambda y el tipo de valor devuelto coinciden con el tipo delegado declarado. Si no coinciden, el código no se compila.
int[] numbers = [1, 2, 3, 4, 5, 6];
int[] evenNumbers = Filter(numbers, value => value % 2 == 0).ToArray();
Console.WriteLine(string.Join(", ", evenNumbers));
El Filter método declara un Func<int, bool> parámetro denominado predicate. El Func<int, bool> tipo indica a los llamadores la forma esperada: una int entrada, un bool resultado. El autor de la llamada pasa value => value % 2 == 0 como dicho argumento. Este patrón aparece a lo largo de LINQ y muchas API de .NET.
Mantener las expresiones lambda independientes
Una expresión lambda puede hacer referencia a variables desde el código circundante. La captura significa que la expresión lambda contiene una referencia a una variable declarada fuera de su propio cuerpo. La combinación de la expresión lambda y las variables que captura se denomina cierre.
Cuando no sea necesario capturar nada, agregue el static modificador a la expresión lambda. Una expresión lambda estática solo puede usar sus propios parámetros y valores declarados dentro de su cuerpo. No puede capturar variables locales ni estado de instancia desde el ámbito envolvente.
Func<int, bool> isEven = static value => value % 2 == 0;
Console.WriteLine(isEven(14));
Console.WriteLine(isEven(15));
Las lambdas estáticas hacen que la intención sea clara y evitan capturas accidentales.
Uso de parámetros de descarte cuando las entradas son irrelevantes
A veces, una firma de delegado incluye parámetros que no necesita. Use el descarte _ para indicar esa opción explícitamente.
Entre los ejemplos comunes se incluyen manejadores de eventos en los que no se usan sender o EventArgs, callbacks en los que solo se necesitan algunas de varias entradas, y sobrecargas de LINQ que proporcionan un índice que no se utiliza.
Action<int, int, string> statusUpdate = (_, _, message) => Console.WriteLine(message);
statusUpdate(200, 42, "Operation completed");
Los descartes mejoran la legibilidad porque muestran qué parámetros importan.
Los eventos proporcionan notificaciones opcionales
Un evento es un mecanismo que usa un objeto (el publicador) para notificar a otros objetos ( los suscriptores) cuando sucede algo. El publicador no necesita saber quién escucha o cuántos suscriptores hay. Los suscriptores eligen participar.
Los eventos se basan en delegados. Un evento es un campo delegado con restricciones adicionales aplicadas por la palabra clave event: el código externo solo puede suscribirse (+=) o cancelar la suscripción (-=) del evento; solo la clase que declara el evento puede invocarlo (generar).
La convención de .NET para los tipos de delegado de eventos es System.EventHandler<TEventArgs>, donde T es el tipo de datos incluidos con la notificación. Su firma siempre tiene dos parámetros: sender (el objeto que generó el evento) y los datos del evento de tipo T.
MessagePublisher publisher = new();
publisher.MessagePublished += (_, message) => Console.WriteLine($"Received: {message}");
publisher.Publish("Records updated");
Recorrer el código:
-
MessagePublisherdeclaraevent EventHandler<string>? MessagePublished. Laeventpalabra clave significa que los autores de llamadas solo pueden suscribirse o cancelar la suscripción; no pueden invocarla directamente. -
publisher.MessagePublished += (_, message) => ...se suscribe con una expresión lambda._Descarta elsenderparámetro porque este controlador no lo necesita. -
publisher.Publish("Records updated")genera el evento y ejecuta todos los controladores suscritos.
La suscripción es opcional. El ?.Invoke(...) en el método Publish significa que el evento se genera solo cuando hay al menos un suscriptor adjunto. El publicador genera el evento sin saber ni preocuparse de si alguien está escuchando.