Delegados e lambdas
Um delegado define um tipo que representa referências aos métodos que têm uma lista de parâmetros e um tipo de retorno específicos. Um método (estático ou instância) cuja correspondência de lista de parâmetros e tipo de retorno podem ser atribuídos a uma variável desse tipo, então chamado diretamente (com os argumentos adequados) ou passado como um argumento para outro método e, então, chamado. O exemplo a seguir demonstra o uso de um delegado.
using System;
using System.Linq;
public class Program
{
public delegate string Reverse(string s);
static string ReverseString(string s)
{
return new string(s.Reverse().ToArray());
}
static void Main(string[] args)
{
Reverse rev = ReverseString;
Console.WriteLine(rev("a string"));
}
}
- A linha
public delegate string Reverse(string s);
cria um tipo de delegado de um método que usa um parâmetro de cadeia de caracteres e retorna um parâmetro de cadeia de caracteres. - O método
static string ReverseString(string s)
, que tem exatamente a mesma lista de parâmetros e tipo de retorno que o tipo de delegado definido, implementa o delegado. - A linha
Reverse rev = ReverseString;
mostra que você pode atribuir um método a uma variável do tipo de delegado correspondente. - A linha
Console.WriteLine(rev("a string"));
demonstra como usar uma variável de um tipo de delegado para invocar o delegado.
Para simplificar o processo de desenvolvimento, o .NET inclui um conjunto de tipos de delegados que os programadores podem reutilizar, sem precisar criar novos tipos. Esses tipos são Func<>
, Action<>
e Predicate<>
, e podem ser usados em vários locais das APIs .NET sem a necessidade de definir novos tipos de delegado. Há algumas diferenças entre os três tipos que têm a ver com a maneira como eles devem ser usados:
Action<>
é usado quando é necessário executar uma ação usando os argumentos do delegado. O método que ele encapsula não retorna um valor.Func<>
é normalmente é usado quando você tem uma transformação à mão, ou seja, quando é necessário transformar os argumentos do delegado em um resultado diferente. Projeções são um bom exemplo. O método que ele encapsula retorna um valor especificado.Predicate<>
é usado quando você precisa determinar se o argumento satisfaz a condição do delegado. Ele também pode ser escrito como umFunc<T, bool>
, o que significa que o método retorna um valor booliano.
Agora, podemos pegar nosso exemplo acima e reescrevê-lo usando o delegado Func<>
em vez de um tipo personalizado. O programa continuará sendo executado exatamente da mesma forma.
using System;
using System.Linq;
public class Program
{
static string ReverseString(string s)
{
return new string(s.Reverse().ToArray());
}
static void Main(string[] args)
{
Func<string, string> rev = ReverseString;
Console.WriteLine(rev("a string"));
}
}
Para este exemplo simples, ter um método definido fora do método Main
parece um pouco supérfluo. O .NET Framework 2.0 introduziu o conceito de representantes anônimos, que permitem criar delegados "embutidos" sem necessidade de especificar nenhum tipo ou método adicional.
No exemplo a seguir, um delegado anônimo filtra uma lista apenas para os números pares e os imprime no console.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 1; i <= 100; i++)
{
list.Add(i);
}
List<int> result = list.FindAll(
delegate (int no)
{
return (no % 2 == 0);
}
);
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}
Como você pode ver, o corpo do delegado é apenas um conjunto de expressões, como aconteceria com qualquer outro delegado. Mas em vez de ele ser uma definição separada, nós o introduzimos ad hoc em nossa chamada ao método List<T>.FindAll.
No entanto, mesmo com essa abordagem, ainda há muito código que podemos descartar. É aí que as expressões lambda entram em cena. As expressões lambda ou apenas "lambdas" para abreviar, foram introduzidas no C# 3.0 como um dos principais elementos de construção da LINQ (Consulta integrada à linguagem). Elas são apenas uma sintaxe mais conveniente para usar delegados. Elas declaram uma lista de parâmetros e um corpo do método, mas não têm uma identidade formal própria, a menos que sejam atribuídas a um delegado. Ao contrário dos representantes, elas podem ser atribuídas diretamente como o lado direito do registro de eventos ou em várias cláusulas e métodos LINQ.
Como uma expressão lambda é apenas outra maneira de especificar um delegado, nós podemos reescrever o exemplo acima para usar uma expressão lambda em vez de um delegado anônimo.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 1; i <= 100; i++)
{
list.Add(i);
}
List<int> result = list.FindAll(i => i % 2 == 0);
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}
No exemplo anterior, a expressão lambda usada é i => i % 2 == 0
. Mais uma vez, é apenas uma sintaxe conveniente para usar delegados. O que acontece nos bastidores é semelhante ao que acontece com o delegado anônimo.
Novamente, as lambdas são apenas delegados, o que significa que podem ser usadas como um manipulador de eventos sem problemas, como o snippet de código a seguir ilustra.
public MainWindow()
{
InitializeComponent();
Loaded += (o, e) =>
{
this.Title = "Loaded";
};
}
O operador +=
nesse contexto é usado para assinar um evento. Para obter mais informações, confira Como assinar e cancelar a assinatura de eventos.