André Alves de Lima

Talking about Software Development and more…

Quando utilizar delegates, Funcs ou Actions no C#?

Olá caro(a) leitor(a)!

Esse não era exatamente o tópico sobre o qual eu estava planejando escrever nesta semana, mas, ao ler um post no blog do Wennder Santos, acabei tendo a ideia de complementá-lo. O post que eu estou me referindo é este: C# – Delegate. Ao ler esse artigo, parei para pensar quanto tempo faz que eu tive realmente que criar um delegate em si no meu dia-a-dia. Caso você não saiba o que é e para que serve um delegate, é fundamental que você leia o artigo do Wennder antes de continuar.

O que são Actions e Funcs?

A utilização mais comum de um delegate no C#, como o Wennder explicou no post dele, é passar um método (ou função) como parâmetro para outro método (ou função). Só que, curiosamente, eu (e a equipe aqui onde eu trabalho) quase nunca acabo criando um delegate customizado para resolver essa necessidade. Ao invés disso, utilizamos os delegatesgenéricos” chamados Actions ou Funcs, presentes no C# já há um bom tempo.

Um Action no C# representa um delegate de um método que retorna void e não recebe parâmetros. Portanto, considerando que você tenha o seguinte cenário:

        private delegate void MeuDelegate();

        private void MetodoQueRecebeUmDelegate(MeuDelegate meuDelegate)
        {
            meuDelegate();
        }

        private void MetodoASerExecutado()
        {
            Console.WriteLine("Hello world");
        }

        private void OutroMetodo()
        {
            MetodoQueRecebeUmDelegate(MetodoASerExecutado);
        }

Podemos facilmente remover “MeuDelegate” e substituí-lo por um simples “Action“. O resultado seria exatamente o mesmo só que com um código um pouco mais simples:

        private void MetodoQueRecebeUmDelegate(Action meuDelegate)
        {
            meuDelegate();
        }

        private void MetodoASerExecutado()
        {
            Console.WriteLine("Hello world");
        }

        private void OutroMetodo()
        {
            MetodoQueRecebeUmDelegate(MetodoASerExecutado);
        }

E como fazemos se precisarmos de um delegate que receba parâmetros? Podemos também substituir por um Action, só que dessa vez um Action<T>:

        private void MetodoQueRecebeUmDelegate(Action<string> meuDelegate, string parametro)
        {
            meuDelegate(parametro);
        }

        private void MetodoASerExecutado(string parametro)
        {
            Console.WriteLine(parametro);
        }

        private void OutroMetodo()
        {
            MetodoQueRecebeUmDelegate(MetodoASerExecutado, "Hello world");
        }

Caso você queira passar mais parâmetros, você pode utilizar um Action<T1, T2>, ou Action<T1, T2, T3> e por aí vai. Um Action pode receber até dezesseis parâmetros, o que tende a ser suficiente para a maioria das situações.

Já o “Func” representa um delegate de uma função que recebe entre zero e dezesseis parâmetros e, além disso, retorna um valor. Veja um exemplo abaixo:

        private void MetodoQueRecebeUmDelegate(Func<string, bool> meuDelegate, string parametro)
        {
            bool retorno = meuDelegate(parametro);
            Console.WriteLine(retorno.ToString());
        }

        private bool MetodoASerExecutado(string parametro)
        {
            Console.WriteLine(parametro);
            return true;
        }

        private void OutroMetodo()
        {
            MetodoQueRecebeUmDelegate(MetodoASerExecutado, "Hello world");
        }

Quando utilizar Actions e Funcs?

Na minha opinião, podemos utilizar Actions e Funcs sempre que possível, pois isso faz com que o código fique menos complexo. Por exemplo, vejam o código final do artigo do Wennder (adaptado para rodar em uma Console Application):

        public delegate void ExibirMensagem(string msg);

        static void Main(string[] args)
        {
            Calculo(EscreverNaTela);
            Console.ReadLine();
        }

        private static void EscreverNaTela(string texto)
        {
            Console.WriteLine(texto);
        }

        private static void Calculo(ExibirMensagem exibirMensagem)
        {
            exibirMensagem((1 + 1).ToString());
        }

Agora vejam o mesmo código substituindo o delegate por um Action:

        static void Main(string[] args)
        {
            Calculo(EscreverNaTela);
            Console.ReadLine();
        }

        private static void EscreverNaTela(string texto)
        {
            Console.WriteLine(texto);
        }

        private static void Calculo(Action<string> exibirMensagem)
        {
            exibirMensagem((1 + 1).ToString());
        }

Uma vez que entendemos quando podemos utilizar Actions e Funcs, vamos estudar as situações em que não é possível utilizá-los.

Quando NÃO utilizar Actions e Funcs?

Antes de escrever este post eu fiz essa pergunta a mim mesmo e, infelizmente, eu não sabia a resposta. Depois de pesquisar um pouco, encontrei esta pergunta no StackOverflow e tudo ficou mais claro. Caso a assinatura do seu delegate possua um parâmetro “out” ou “ref“, você não consegue utilizar um Action ou Func, restando como única opção criar um delegate customizado.

Para entender esse cenário, vamos alterar um pouco o exemplo anterior. Suponha que você queira passar como parâmetro para o método “Calculo” um delegate que realize a operação matemática, retorne verdadeiro ou falso (indicando se o cálculo foi efetuado com sucesso) e além disso, retorne o resultado em um parâmetro “out“. Isso poderia ser realizado da seguinte maneira:

        private delegate bool CalculoDelegate(double valor1, double valor2, out double resultado);

        static void Main(string[] args)
        {
            Calculo(new CalculoDelegate(Dividir), EscreverNaTela);
            Console.ReadLine();
        }

        private static void EscreverNaTela(string texto)
        {
            Console.WriteLine(texto);
        }

        private static void Calculo(CalculoDelegate calculoDelegate, Action<string> exibirMensagem)
        {
            double resultado;
            bool sucesso = calculoDelegate(4, 2, out resultado);
            if (sucesso)
                exibirMensagem(resultado.ToString());
            else
                exibirMensagem("Erro no cálculo!");
        }

        private static bool Dividir(double numerador, double denominador, out double resultado)
        {
            bool sucesso;
            resultado = 0;

            try
            {
                resultado = numerador / denominador;
                sucesso = true;
            }
            catch
            {
                sucesso = false;
            }

            return sucesso;
        }

Uma última melhoria nesse exemplo seria transformar esse delegate em genérico, de forma que consigamos passar parâmetros de qualquer tipo para o método (e não só double como estava sendo feito no exemplo anterior):

        private delegate bool CalculoDelegate<T>(T valor1, T valor2, out T resultado);

        static void Main(string[] args)
        {
            Calculo(new CalculoDelegate<double>(Dividir), EscreverNaTela);
            Console.ReadLine();
        }

        private static void EscreverNaTela(string texto)
        {
            Console.WriteLine(texto);
        }

        private static void Calculo(CalculoDelegate<double> calculoDelegate, Action<string> exibirMensagem)
        {
            double resultado;
            bool sucesso = calculoDelegate(4, 2, out resultado);
            if (sucesso)
                exibirMensagem(resultado.ToString());
            else
                exibirMensagem("Erro no cálculo!");
        }

        private static bool Dividir(double numerador, double denominador, out double resultado)
        {
            bool sucesso;
            resultado = 0;

            try
            {
                resultado = numerador / denominador;
                sucesso = true;
            }
            catch
            {
                sucesso = false;
            }

            return sucesso;
        }

Pronto! Esse é um dos cenários em que a utilização de Action ou Func não é possível.

Espero que vocês tenham conseguido entender. Qualquer coisa é só deixar um comentário aqui embaixo. E não se esqueçam de se inscrever para receberem a newsletter do meu blog!

Até o próximo post!

André Lima

6 thoughts on “Quando utilizar delegates, Funcs ou Actions no C#?

  • […] 26/08/2014] Para complementar este estudo sobre delegates recomendo que leiam a publicação  Quando utilizar delegates, funcs ou actions no C#? do MPV André Alves de Lima. […]

  • […] 5) Quando utilizar delegates, Funcs ou Actions no C#? A ideia para esse post surgiu após a leitura de um artigo no blog do Wennder Santos: C# – Delegate. Fazia tanto tempo que eu não utilizava delegates do meu dia a dia (a maior parte do tempo utilizo Funcs ou Actions para substituir a utilização de delegates), e ao topar com o artigo do Wennder Santos sobre esse tema, parei para pensar: quando é que devemos utilizar cada uma dessas estruturas (delegates, Actions e Functions)? Não perdi tempo, fiz uma pesquisa e escrevi esse artigo. E ele acabou sendo o quinto artigo mais acessado do blog em 2014. […]

  • Ótimo artigo, tenho duas duvidas, é possível usar métodos não estáticos com Action? e como faria?
    Outra duvida, vi em um site que pode ser usado assim:
    Action example1 = (int x) => Console.WriteLine(“Write {0}”, x);
    example1.Invoke(1);
    Oque seria isso?
    Obrigado.

    • andrealveslima disse:

      Olá Henrique, obrigado pelo comentário!

      É possível utilizar Actions com métodos não estáticos sem nenhum problema.. Tanto que no próprio artigo, o primeiro exemplo de Action não utiliza métodos estáticos.. No último exemplo eu só utilizei método estático porque eu fiz o exemplo em uma Console Application e o método “main” é estático, então, todo o resto precisava ser estático nesse caso..

      Quanto ao código que você passou, esse Action é basicamente um método inline que imprime o número passado para ele.. Ou seja, é exatamente a mesma coisa se você tivesse um método como este:

      private void ImprimeNumero(int x)
      {
      Console.WriteLine(“Write {0}”, x);
      }

      E chamasse ele dessa forma:

      ImprimeNumero(1);

      Entendeu? Qualquer coisa é só perguntar..

      Abraço!
      André Lima

  • otavio bueno disse:

    André escrevi um artigo sobre compose utilizando o Delegate Action estarei linkando o seu artigo nele pois em meu artigo não pretendo explicar como funciona o Action.
    Caso queria que eu remova por favor entre em contato.
    Valeo

    • andrealveslima disse:

      Olá Otavio!

      Sem problema algum.. Pelo contrário, muito obrigado por linkar o meu artigo no seu.. Depois que você publicá-lo, volta aqui e passa pra gente o link do seu artigo para ficar registrado aqui também..

      Abraço!
      André Lima

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *