Compartilhar via


Delegados e lambdas

Um delegado define um tipo que representa referências a métodos que têm uma lista de parâmetros específica e um tipo de retorno. Um método (estático ou de instância) que tenha a lista de parâmetros e o tipo de retorno compatíveis com uma variável desse tipo pode ser atribuído a ela, sendo então chamado diretamente (com os argumentos apropriados) ou passado como argumento para outro método e, em seguida, 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 public delegate string Reverse(string s); linha cria um tipo delegado de um método que usa um parâmetro de cadeia de caracteres e retorna um parâmetro de cadeia de caracteres.
  • O static string ReverseString(string s) método, que tem exatamente a mesma lista de parâmetros e o tipo de retorno que o tipo de delegado definido, implementa o delegado.
  • A Reverse rev = ReverseString; linha mostra que você pode atribuir um método a uma variável do tipo delegado correspondente.
  • A Console.WriteLine(rev("a string")); linha demonstra como usar uma variável de um tipo delegado para invocar o delegado.

Para simplificar o processo de desenvolvimento, o .NET inclui um conjunto de tipos delegados que os programadores podem reutilizar e não precisam criar novos tipos. Esses tipos são Func<>e Action<>Predicate<>podem ser usados sem precisar definir novos tipos de delegado. Há algumas diferenças entre os três tipos que têm a ver com a maneira como eles foram destinados a serem usados:

  • Action<> é usado quando há a necessidade de executar uma ação usando os argumentos do delegado. O método que ele encapsula não retorna um valor.
  • Func<> geralmente é usado quando você tem uma transformação em mãos, ou seja, você precisa transformar os argumentos do delegado em um resultado diferente. Projeções são um bom exemplo. O método encapsulado 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 um Func<T, bool>, o que significa que o método retorna um valor booliano.

Podemos agora pegar o nosso exemplo acima e reescrevê-lo usando o Func<> delegate em vez de um tipo personalizado. O programa continuará executando exatamente o mesmo.

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 Main método parece um pouco supérfluo. O .NET Framework 2.0 introduziu o conceito de representantes anônimos, que permitem criar delegados "embutidos" sem precisar 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 qualquer outro delegado. Mas, em vez de ser uma definição separada, introduzimos-a ad hoc em nossa chamada ao List<T>.FindAll método.

No entanto, mesmo com essa abordagem, ainda há muito código que podemos jogar fora. É aqui que as expressões lambda entram em jogo. Expressões Lambda, ou apenas "lambdas" para abreviar, foram introduzidas no C# 3.0 como um dos principais blocos de construção da LINQ (Consulta Integrada à Linguagem). Elas são apenas uma sintaxe mais conveniente para usar delegados. Eles declaram uma lista de parâmetros e o corpo do método, mas não têm uma identidade formal própria, a menos que sejam atribuídos 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, devemos ser capazes de reescrever a amostra 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 sob as capas é 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, consulte Como inscrever-se em eventos e cancelar a inscrição.

Leitura e recursos adicionais