Desenvolvimento - C#

Action Delegate: Encapsulando métodos em C#

Veja neste artigo como utilizar o delegate Action do namespace System para encapsular métodos em C#, definindo seu funcionamento apenas no momento de sua utilização.

por Joel Rodrigues



Existem situações, durante o desenvolvimento de aplicações, em que precisamos atribuir ou modificar funcionalidades de um objeto fora da sua definição, ou seja, apenas no momento de sua utilização. Assim como fazemos com as diversas propriedades de um objeto, às quais atribuímos valores conforme a necessidade, podemos também precisar definir seu comportamento, atribuindo valores distintos a seus métodos.

Na linguagem C++ esta necessidade é suprida por ponteiros para funções. Com eles, por exemplo, é possível declarar uma função que receba como parâmetro um ponteiro para outra função com assinatura predefinida. Dessa forma, o método indicado para preencher o local daquele ponteiro deve atender exatamente à definição solicitada, com tipos de argumento e retorno especificados.

No .NET Framework dispomos dos delegates para desempenhar essa mesma função, porém com uma interface simplificada para o programador, alinhada aos conceitos da Programação Orientada a Objeto.

Aqui conheceremos um delegate em especial, nativo do framework e que está definido no namepace System, que é o delegate Action, que se apresenta nas versões genérica e não genérica. Esse delegate permite encapsular um método sem retorno (void) e sem parâmetro (versão não genérica) ou com tipo de argumentos definidos em sua declaração pelo uso de generics.

Na Listagem 1 é possível ver a definição de uma das versões genéricas desse delegate, como consta no código fonte do .NET.

Listagem 1. Definição do delegate Action no namespace System

namespace System
  {
      // Summary:
      //     Encapsulates a method that has a single parameter and does not return a value.
      //
      // Parameters:
      //   obj:
      //     The parameter of the method that this delegate encapsulates.
      //
      // Type parameters:
      //   T:
      //     The type of the parameter of the method that this delegate encapsulates.This
      //     type parameter is contravariant. That is, you can use either the type you
      //     specified or any type that is less derived. For more information about covariance
      //     and contravariance, see Covariance and Contravariance in Generics.
      public delegate void Action<in T>(T obj);
  }

Por padrão, a primeira versão desse delegate encapsula um método sem retorno e sem parâmetros. Existem ainda 16 sobrecargas que permitem o encapsulamento de métodos com 1 a 16 argumentos, cujos tipos são especificados entre os sinais &lt; e &gt;.

Na Listagem 2 temos um exemplo de classe que contém um delegate Action padrão, sem retorno e sem argumentos. Note que o delegate é utilizado na classe como um método qualquer, porém, no momento de sua definição a classe desconhece qual é o funcionamento real desse método. Assim, o utilizador final dessa classe é que vai definir como esse método irá atuar.

Listagem 2. Declarando um delegate do tipo Action

public class Validador
  {
      public Action OnErro { get; set; }
   
      public bool ValidarCPF(string cpf)
      {
          if (cpf.Length == 11)
              return true;
          OnErro();
          return false;
      }
  }

A seguir, temos na Listagem 3 um exemplo de utilização da classe criada. Observe que após instanciar o objeto fazemos com que o delegate aponte para um método anônimo que definimos, sem argumentos e sem retorno. Dessa forma, quando executarmos o método ValidarCPF, o delegate OnErro, que agora já aponta para um método real, irá executar seu código propriamente dito e imprimirá uma mensagem na tela. Neste caso, apenas escrevemos uma mensagem no console, mas é fácil perceber que essa classe poderia ser usada em qualquer outra plataforma e em cada um poderíamos definir uma forma customizada para apresentação de erros.

Listagem 3. Atribuindo um método anônimo ao delegate Action

static void Main(string[] args)
  {
      Validador v = new Validador();
      v.OnErro = () =>
      {
          Console.WriteLine("Ocorreu um erro ao validar.");
      };
      v.ValidarCPF("123");
  }

Vejamos agora um exemplo de utilização de uma das sobrecargas genéricas do delegate Action. Na Listagem 4 reescrevemos a classe Validador de forma que o delegate encapsula um método que receba um argumento do tipo string. Dentro do método Validar invocamos o método encapsulado através do delegate e passamos um valor para seu argumento. Esse valor poderá ser acessado posteriormente quando utilizarmos essa classe.

No momento da declaração do delegate nós não definimos os nomes dos argumentos que o método deve receber. É necessário apenas que os tipos e ordem dos parâmetros correspondam ao que foi especificado na declaração do tipo genérico. Neste caso, basta que o método não tenha retorno e receba um único parâmetro do tipo string.

Listagem 4. Declarando delegate Action com tipo de entrada

public class Validador
  {
      public Action<string> OnErro { get; set; }
   
      public bool ValidarCPF(string cpf)
      {
          if (cpf.Length == 11)
              return true;
          OnErro("O CPF deve possuir exatamente 11 caracteres.");
          return false;
      }
  }

Agora devemos adaptar a classe cliente, que nesse caso é apenas a classe Program da aplicação console, de forma que o método passado para o delegate OnErro da classe Validador corresponda à assinatura especificada em sua definição. Como podemos ver na Listagem 5, o método anônimo agora recebe um argumento, que aqui foi chamado de erro, mas que poderia possuir qualquer nome. No corpo desse método acessamos o parâmetro erro para exibir uma mensagem customizada. Dessa vez, esse argumento já terá sido preenchido na própria classe Validador, como vimos anteriormente.

Listagem 5. Atribuindo método anônimo ao delegate com argumento

static void Main(string[] args)
  {
      Validador v = new Validador();
      v.OnErro = (erro) =>
      {
          Console.WriteLine("ERRO: " + erro);
      };
      v.ValidarCPF("123");
  }

Semelhantes ao Action, existem também os delegates Func e Predicate, com sobrecargas que permitem definir além dos tipos de argumentos que o método encapsulado recebe, seu tipo de retorno. Esse tipo de delegate é utilizado, por exemplo, em expressões Lambda, em que fornecemos uma expressão com tipos de argumento definidos e tipo de retorno em geral booleano, indicando quando uma condição é atendida.

Links

Action Delegate – MSDN
https://msdn.microsoft.com/en-us/library/system.action(v=vs.110).aspx

Func Delegate – MSDN
https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx

Predicate Delegate – MSDN
https://msdn.microsoft.com/pt-br/library/bfcke1bz%28v=vs.110%29.aspx

Joel Rodrigues

Joel Rodrigues - Técnico em Informática - IFRN Cursando Bacharelado em Ciências e Tecnologia - UFRN Programador .NET/C# e Delphi há quase 3 anos, já tendo trabalhado com Webservices, WPF, Windows Phone 7 e ASP.NET, possui ainda conhecimentos em HTML, CSS e Javascript (JQuery).