CodeGym /Cursos /C# SELF /Delegates como tipo para expressões lambda

Delegates como tipo para expressões lambda

C# SELF
Nível 50 , Lição 0
Disponível

1. Introdução

Imagine que você tem uma máquina de café. Normalmente ela só faz café e não incomoda ninguém, mas às vezes o chefe diz: “Você pode, por favor, depois de fazer o café gritar ‘Pronto!’?” — aí precisa de uma configuração. Não reescrevemos a máquina de café. Só criamos uma função que ela vai executar no final.

Em C# esse papel é feito pelos delegates — eles permitem passar para métodos pedaços de código (métodos, lambdas ou métodos anônimos), para que esse método os invoque no momento certo. Resumindo, um delegate é um tipo que pode guardar referências para métodos com uma assinatura específica.

Definição de delegate

Em C# um delegate é definido com a palavra-chave delegate. Exemplo:

// Delegate que referencia métodos que aceitam int e retornam bool
public delegate bool PredicateInt(int x);

Agora uma variável do tipo PredicateInt pode referenciar qualquer método (ou lambda!) que aceite um int e retorne um bool.

Para que servem os delegates?

  • Passar lógica como argumento (por exemplo, para sort, filter, manipulação de eventos)
  • Assinatura para eventos (sobre isso falamos depois)
  • Implementar callbacks
  • APIs flexíveis, onde parte do comportamento é definida pelo chamador

Esquema visual simples

Tipo de delegate Assinatura Exemplo de chamada
Action
void()
Action act = () => ...
Func<int>
int()
Func<int> f = () => 42
Func<int, bool>
bool(int)
Func<int, bool> p = x => x > 0

2. Como a lambda vira um delegate

Sintaxe

Quando você escreve uma expressão lambda, por exemplo, x => x > 5, você na prática está criando um objeto delegate. A lambda não “vive” no vácuo: ela precisa de um tipo (alguém tem que saber quais são seus parâmetros e o valor retornado). Por isso uma expressão lambda em C# é sempre convertida, implicitamente (ou explicitamente), para um delegate.

Exemplo 1: Vinculando um delegate a um método

// Definimos explicitamente um delegate
public delegate bool MyPredicate(int number);

class Program
{
    static void Main()
    {
        // Atribuímos uma lambda a uma variável do tipo MyPredicate
        MyPredicate isEven = x => x % 2 == 0;

        Console.WriteLine(isEven(4));  // true
        Console.WriteLine(isEven(7));  // false
    }
}

Exemplo 2: Usando delegates padrão

C# tem um conjunto de delegates genéricos padrão: Action, Func<>, Predicate<>. Eles aparecem quase sempre quando você escreve lambdas no código.

// Usando Func
  
    Func
   
     isPositive = number => number > 0; Console.WriteLine(isPositive(-5)); // false 
   
  

3. Delegates padrão: Func, Action, Predicate

Func<...>

Usado para métodos que recebem algo e retornam algo.

Assinatura:
— O último tipo é o valor retornado, os anteriores são os tipos dos parâmetros, por exemplo:
Func<int, string> — recebe int, retorna string

Func
  
    intToString = number => "Número: " + number; Console.WriteLine(intToString(7)); // "Número: 7" 
  

Action<...>

Usado quando você precisa executar algo, mas não retornar nada (void).

Action
  
    printHello = name => Console.WriteLine("Olá, " + name + "!"); printHello("Vasili"); // "Olá, Vasili!" 
  

Predicate<T>

Na prática, um atalho para Func<T, bool>. Usado quando precisa de uma verificação booleana sobre um objeto (true/false).

Predicate
  
    isOdd = x => x % 2 != 0; Console.WriteLine(isOdd(3)); // true 
  

Resumo visual rápido

Delegate Assinatura Aplicação
Func<T1, TResult>
Result(T1)
Transformação, projeção
Action<T1>
void(T1)
Efeitos colaterais, saída
Predicate<T>
bool(T)
Filtragem, busca

Qual tipo de delegate escolher para uma lambda?

  • Se for esperado um valor de retorno, use Func<...>
  • Se o método não retorna nada (void), use Action<...>
  • Se precisa de uma verificação condicional, use Predicate<T>

Exemplo: Filtrando uma lista

List
  
    numbers = new List
   
     { 1, 2, 3, 4, 5, 6 }; // Espera Predicate
    
      List
     
       evenNumbers = numbers.FindAll(x => x % 2 == 0); Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6 
     
    
   
  

4. Dicas úteis

Expressões lambda e métodos de coleção: o que rola por baixo?

Quando você chama um método de coleção passando uma lambda, por exemplo:

var adults = users.Where(u => u.Age >= 18);

O método Where espera um argumento do tipo Func<T, bool>. Ou seja, sua lambda u => u.Age >= 18 é convertida pelo compilador em um objeto delegate desse tipo.

Fluxo: como isso funciona


Sua lambda           -->      Compilador C#        -->     Objeto delegate (Func
  
   ) (u => u.Age >= 18) [tipo conhecido] (Pronto pra chamar em Where()) 
  

Mais sobre tipagem: type inference

Geralmente o tipo do delegate é inferido automaticamente pelo compilador quando está claro pelo contexto. Por exemplo, para o método List<T>.Find é esperado um Predicate<T>, e o compilador conhece o tipo do parâmetro pela assinatura do método.

List
  
    words = new List
   
     { "one", "two", "three" }; var result = words.Find(word => word.Length == 5); // Find espera Predicate
    
      Console.WriteLine(result); // "three" 
    
   
  

Se o contexto não for claro, você precisa ajudar o compilador:

// Especificando o tipo explicitamente
Func
  
    check = x => x > 10; 
  

Delegates retornados: fábricas de funções

Às vezes métodos podem retornar delegates — ou seja, criar “fábricas de funções”. Isso é útil para gerar comportamento dinâmico.

// Função que retorna um delegate (lambda)
Func
  
    GetMultiplier(int factor) { return x => x * factor; } var times3 = GetMultiplier(3); Console.WriteLine(times3(5)); // 15 
  

Isso funciona porque a lambda (x => x * factor) captura a variável factor do contexto externo (closure/fechamento) e é retornada como um objeto do tipo Func<int, int>.

5. Erros e mal-entendidos com delegates e lambdas

Assinaturas incompatíveis

O compilador não deixa atribuir uma lambda a um delegate se os parâmetros ou o valor de retorno não coincidirem.

Func
  
    f = x => "Não dá pra retornar uma string!"; // Erro de compilação 
  

Erro ao tentar usar uma lambda sem delegate

Não dá para simplesmente escrever uma lambda e tentar chamá-la sem tipo:

// Isso não vai funcionar - o compilador não consegue inferir o tipo
// var myFunc = x => x * 2; // Erro CS0815 
// myFunc(10);

Para funcionar, você precisa especificar o tipo, ou fornecer contexto:

Func
  
    myFunc = x => x * 2; Console.WriteLine(myFunc(10)); // 20 
  

Confusão entre Action, Func e Predicate

Às vezes você escolhe o tipo errado de delegate e daí dá erro de compatibilidade de assinatura. Lembre-se da regra simples: Func — quando há um resultado, Action — quando não há resultado (void), Predicate<T> — quando precisa de uma resposta booleana (bool).

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION