Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Sugestão
Novo no desenvolvimento de software? Começa primeiro pelos tutoriais para começar . Desenvolve aí competências de tipo e método básicos antes de usares expressões lambda.
Por vezes, queres passar um pequeno pedaço de comportamento, uma função, diretamente para outro método. Por exemplo, pode querer filtrar uma lista, mas a condição de filtragem muda consoante a situação. Em vez de escrever um método nomeado separado para cada condição possível, passa a condição em si como argumento.
As expressões lambda são a característica C# que torna isto possível. Uma expressão lambda é uma função compacta e inline que se escreve sem lhe dar um nome. Usas o operador => de seta para separar a lista de parâmetros do corpo:
x => x * 2
Lendo da esquerda para a direita: x é o parâmetro de entrada, => significa "vai para", e x * 2 é o corpo. Calcula o valor devolvido. Quando não houver parâmetros ou mais do que um, envolva-os entre parênteses: () => 42 ou (left, right) => left + right.
Os delegados apoiam expressões lambda
Para usar uma expressão lambda, o compilador C# precisa de saber duas coisas: os tipos dos parâmetros e o tipo de retorno. Essa descrição, os tipos de parâmetros mais o tipo de retorno, chama-se tipo de delegado.
Um tipo de delegado é um tipo que representa uma assinatura de método. Uma variável de tipo delegado pode conter qualquer método de correspondência, como uma expressão lambda ou um método nomeado, desde que os seus tipos de parâmetros e o tipo de retorno coincidam.
Declaras um tipo de delegado com a delegate palavra-chave:
delegate int Transform(int value);
Esta declaração diz: "Transform é um tipo de delegado para métodos que aceitam um int e retornam um int." Pode então atribuir uma expressão lambda ou um método nomeado a uma variável desse 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 têm um valor de tipo Transform. Chamas os exatamente como chamarias métodos regulares. O compilador verifica se o que atribuis corresponde à assinatura declarada.
Tipos de delegados incorporados: Func e Action
Declarar um tipo de delegado personalizado para cada situação pode ser repetitivo. .NET fornece duas famílias de tipos genéricos de delegados, Func e Action, que cobrem a maioria dos cenários, por isso, raramente precisa usar a palavra-chave delegate por si mesmo.
Ambas as famílias existem em versões com zero a dezasseis parâmetros de tipo de entrada, pelo que escalam para qualquer número de entradas. A principal diferença entre as duas famílias é:
-
System.Func<T,TResult> (e
Func<T1, T2, TResult>, e assim sucessivamente) representa um método que devolve um valor. O último parâmetro de tipo é sempre o tipo de retorno; todos os anteriores são tipos de entrada. -
System.Action<T> (e
Action<T1, T2>, e assim sucessivamente) representa um método que não retorna nada (void). Todos os parâmetros de tipo são tipos de entrada. System.Action sem parâmetros de tipo representa um método sem entradas e sem valor de retorno.
Por exemplo, Func<int, int, int> descreve um método com duas int entradas e um int resultado.
Action<string> descreve um método com uma entrada string e sem valor de retorno.
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 nomes de parâmetros descritivos em lambdas para que os leitores possam compreender a intenção sem analisar todo o corpo do método.
Passar uma expressão lambda a um método
Quando um método declara um Func parâmetro ou, Action os chamadores passam uma expressão lambda que corresponde a esse tipo de delegado. O compilador verifica se os tipos de parâmetros e o tipo de retorno do lambda correspondem ao tipo de delegado declarado. Se não coincidirem, o código não compila.
int[] numbers = [1, 2, 3, 4, 5, 6];
int[] evenNumbers = Filter(numbers, value => value % 2 == 0).ToArray();
Console.WriteLine(string.Join(", ", evenNumbers));
O Filter método declara um Func<int, bool> parâmetro chamado predicate. O Func<int, bool> tipo indica aos chamadores a forma esperada: uma int entrada, um bool resultado. O chamador passa value => value % 2 == 0 como argumento. Este padrão aparece em todo o LINQ e em muitas APIs .NET.
Mantenha as expressões lambda autónomas
Uma expressão lambda pode referenciar variáveis do código circundante. Capturar significa que o lambda detém uma referência a uma variável declarada fora do seu próprio corpo. A combinação entre o lambda e as variáveis que este captura é chamada de closure.
Quando não precisares de capturar nada, adiciona o static modificador ao lambda. Um lambda estático só pode usar os seus próprios parâmetros e valores declarados dentro do seu corpo. Não consegue captar variáveis locais ou estados da instância a partir do escopo envolvente.
Func<int, bool> isEven = static value => value % 2 == 0;
Console.WriteLine(isEven(14));
Console.WriteLine(isEven(15));
As lambdas estáticas deixam a intenção clara e evitam capturas acidentais.
Use parâmetros de descarte quando as entradas são irrelevantes
Por vezes, a assinatura de um delegado inclui parâmetros que não precisas. Use o descarte _ para sinalizar essa escolha explicitamente.
Exemplos comuns incluem manipuladores de eventos onde não usa sender ou EventArgs, callbacks onde só necessita de algumas das entradas disponíveis, e sobrecargas do LINQ que fornecem um índice não utilizado.
Action<int, int, string> statusUpdate = (_, _, message) => Console.WriteLine(message);
statusUpdate(200, 42, "Operation completed");
Os descartes melhoram a legibilidade porque mostram quais os parâmetros que importam.
Os eventos fornecem notificações opcionais
Um evento é um mecanismo que um objeto (o editor) usa para notificar outros objetos (os subscritores) quando algo acontece. O editor não precisa de saber quem está a ouvir ou quantos subscritores há. Os subscritores optam por aderir.
Os eventos são baseados nos delegados. Um evento é um campo de delegado com restrições adicionais impostas pela event palavra-chave: código externo só pode subscrever (+=) ou cancelar a subscrição (-=) do evento; apenas a classe que declara o evento pode invocá-lo (elevá-lo).
A convenção .NET para tipos de delegados de eventos é System.EventHandler<TEventArgs>, onde T é o tipo de dados incluídos com a notificação. A sua assinatura tem sempre dois parâmetros: o sender (o objeto que gerou o evento) e os dados do evento do tipo T.
MessagePublisher publisher = new();
publisher.MessagePublished += (_, message) => Console.WriteLine($"Received: {message}");
publisher.Publish("Records updated");
Percorrendo o código:
-
MessagePublisherdeclaraevent EventHandler<string>? MessagePublished. Aeventpalavra-chave significa que os chamadores só podem subscrever ou cancelar a subscrição — não podem invocá-la diretamente. -
publisher.MessagePublished += (_, message) => ...regista-se com uma expressão lambda._descarta o parâmetrosenderporque este handler não precisa dele. -
publisher.Publish("Records updated")aumenta o evento e executa todos os handlers subscritos.
Subscrever-se é opcional. O ?.Invoke(...) no Publish método significa que o evento só surge quando pelo menos um assinante está ligado. O publicador gera o evento sem saber nem se importar se alguém está a ouvir.