Menu

Func e Action em C#: delegates genéricos, Predicate e exemplos

Como Func e Action funcionam em C#: Func<T, TResult> para funções que retornam um valor, Action<T> para as que não retornam, Predicate e Comparison, passar e retornar funções, um dicionário de comandos, lambdas async e quando declarar o seu próprio delegate.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Func e Action são tipos delegate genéricos que o .NET declara para você, então dá para guardar e passar funções sem declarar um tipo delegate antes. Func retorna um valor; Action não retorna nada.

Saída:

4
46.00
False
Hello!
Mia scored 87

A regra para lembrar: em um Func, o último argumento de tipo é o tipo de retorno. Func<int, int, bool> recebe dois int e retorna um bool. Func<int> não recebe nada e retorna um int. Os argumentos de tipo de um Action são todos parâmetros.

A família

TipoParâmetrosRetorna
Actionnenhumvoid
Action<T>Tvoid
Action<T1, T2> ... até 16T1, T2, ...void
Func<TResult>nenhumTResult
Func<T, TResult>TTResult
Func<T1, T2, TResult> ... até 16T1, T2, ...TResult
Predicate<T>Tbool
Comparison<T>T, Tint (negativo, zero, positivo)
Converter<TIn, TOut>TInTOut

Predicate, Comparison e Converter são mais antigos e aparecem nos métodos de List<T> e Array: FindAll(Predicate<T>), Sort(Comparison<T>), ConvertAll(Converter<T, TOut>). O LINQ usa Func em todo lugar, e é por isso que list.Where(...) e list.FindAll(...) recebem lambdas da mesma forma, mas de tipos diferentes:

Saída:

17, 15
34, 52, 28
52, 34, 28, 17, 15
52y 34y 28y 17y 15y

Passando funções para métodos

Um parâmetro Func ou Action permite que quem chama forneça a parte de um algoritmo que varia. Duas formas comuns são "execute isto e meça" e "tente isto até funcionar":

Saída:

attempt 1 failed
attempt 2 failed
<html>ok</html>
clicks: 2

Retry é genérico, então o Func<int, T> dele pode retornar qualquer coisa; o compilador inferiu T como string a partir da lambda. A lambda passada para Twice muda a variável local clicks, o que funciona porque uma lambda captura a própria variável, não uma cópia do valor.

Grupos de métodos: passando um método pelo nome

Você não precisa de uma lambda para preencher um Func ou Action. O nome de um método existente com assinatura compatível (um "grupo de métodos") é convertido diretamente:

Saída:

parsed: 43
sat is weekend: True
total quantity: 20

Select(int.Parse) é o mesmo que Select(s => int.Parse(s)), com uma camada de chamada a menos. O compilador escolhe a sobrecarga que se encaixa no tipo de destino: int.Parse tem várias sobrecargas, e só Parse(string) corresponde a Func<string, int>. Quando o compilador não consegue decidir por uma (várias sobrecargas se encaixam igualmente bem, ou os argumentos de tipo de um método genérico não podem ser inferidos a partir de um grupo de métodos), a chamada não compila, e uma lambda que deixa a chamada explícita resolve.

Um grupo de métodos usado como Func ainda executa depois, sempre que o delegate é invocado, exatamente como uma lambda. A diferença está só na forma de escrever.

Retornando funções

Um método pode montar e retornar uma função. O Func retornado carrega os valores com que foi montado:

Saída:

140
180
162

Twice(staff) monta uma função nova que aplica o desconto de funcionário duas vezes: 200, depois 180, depois 162.

Um dicionário de funções

Guardar funções em um dicionário transforma um switch longo em uma tabela de consulta. É a forma usual de handlers de comandos, calculadoras e ações de menu, e novas entradas podem ser adicionadas em tempo de execução:

Saída:

12 + 30 = 42
7 * 6 = 42
9 / 0 = cannot divide by zero
2 ^ 3 = unknown operator
Hello, Sam
DONE

Adicionar um operador é uma entrada a mais no dicionário, sem nenhuma mudança no laço. O StringComparer.OrdinalIgnoreCase passado para o segundo dicionário faz os nomes de comandos ignorarem maiúsculas e minúsculas.

Funções async: Func<Task>, não Action

Com código async, o tipo delegate decide se quem chama pode esperar o trabalho terminar:

Func<Task> save = async () => await File.WriteAllTextAsync("a.txt", "data");
await save();                       // the caller can await it and see its exceptions

Func<int, Task<string>> load = async id => await FetchUserAsync(id);
string user = await load(42);

Action bad = async () => await File.WriteAllTextAsync("a.txt", "data");
bad();                              // async void: fire and forget, exceptions escape

Uma lambda async atribuída a um Action vira um método async void: nada pode aguardá-la, e uma exceção dentro dela nunca chega a quem chamou. Ela é relançada no thread pool (ou na thread de interface em um app desktop), onde normalmente derruba o processo. Quando você escreve um método que aceita trabalho async, receba um Func<Task> (ou Func<Task<T>>).

Quando declarar seu próprio delegate

Func e Action cobrem a maioria das necessidades. Declare um tipo delegate com nome quando:

  • A assinatura tem parâmetros ref, out ou in. Func não consegue expressá-los, então uma função no estilo TryParse precisa de delegate bool TryParser<T>(string text, out T value);.
  • O nome documenta um papel em uma API pública. delegate bool Validator(string input) na assinatura de um método diz mais que Func<string, bool>, e os nomes dos parâmetros aparecem no IntelliSense.
  • Você precisa de um parâmetro params, que delegates genéricos também não podem ter.

Os eventos são o outro lugar onde tipos delegate próprios ainda aparecem, embora o EventHandler<TEventArgs> embutido cubra a maior parte deles. A página de delegates cobre como declarar e combinar tipos delegate.

Perguntas frequentes

Qual a diferença entre Func e Action em C#?

Func retorna um valor e Action não. Em Func<int, string, bool>, o último argumento de tipo (bool) é o tipo de retorno e os outros são parâmetros. Action<int, string> recebe um int e uma string e retorna void. Os dois existem em versões de 0 a 16 parâmetros.

Como passar uma função como parâmetro em C#?

Declare o parâmetro como um Func ou Action com a forma certa e chame-o dentro do método: static decimal Apply(decimal price, Func<decimal, decimal> rule) => rule(price);. Quem chama passa uma lambda (Apply(80m, p => p * 0.9m)) ou o nome de um método (Apply(80m, HalfPrice)).

O que é Predicate<T> em C#?

Um tipo delegate que recebe um T e retorna bool, a mesma forma de Func<T, bool>. Ele é anterior ao Func e é usado por List<T>.Find, FindAll, RemoveAll, Exists e Array.Find. Os dois tipos não se convertem um no outro, mas uma lambda se converte em qualquer um deles.

Como usar Func com código async?

Use Func<Task> para uma função async sem resultado e Func<T, Task<TResult>> para uma com resultado, para que quem chama possa usar await nela. Evite atribuir uma lambda async a um Action: isso a transforma em async void, que não pode ser aguardada e cujas exceções não podem ser capturadas por quem chama.

Func pode ter parâmetros out ou ref?

Não. Os parâmetros genéricos de Func e Action são parâmetros comuns por valor, então não há como escrever Func<string, out int, bool>. Para uma assinatura como a de TryParse, declare um delegate próprio: delegate bool TryParser<T>(string text, out T value);.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR