Recentemente recordei uma das dificuldades que tive quando entrei no mundo .NET, aliás minha experiência era extremamente limitada se comparada a hoje, apesar de ter muita coisa para estudar.

Pretendo neste post abordar um tópico muito importante do .NET framework que poucas pessoas usam ou desconhecem.

Quem já programou em linguagens como C/C++ isso não deve ser um problema, a menos que tenha feito somente algoritmos básicos de estrutura de dados (pilhas, filas, listas e etc).

Delegates é nada mais nada menos que um ponteiro para função. Ponteiro para muitos é um pesadelo, eu ainda tenho dificuldade quando começo a escrever um programa em C, sempre me esqueço das notações, pois em linguagens de alto nível como Java e C# elas ficam escondidas.

Bom, entenda da seguinte maneira, digamos que você possua uma subrotina/função, se você souber onde ela se encontra em memória é possível invocá-la. Um ponteiro é um endereço para uma região da memória, no caso do delegate é ponteiro para uma subrotina/função.

A questão é que em C# as coisas são tipadas, quando você define um delegate você define um contrato com quem quer "disponibilizar" um rotina ou função.

A sintaxe é a seguinte:

delegate <return_type> MyDelegate(<params>);

Por exemplo:

delegate int Calcula(int a, int b);

Podemos referenciar um função qualquer que aceite dois inteiros e retorne um inteiro como resultado, poderiamos utilizar:

public int Soma(int num1, int num2)
{
  return num1 + num2;
}

Agora definimos que um ponteiro para essa rotina da seguinte forma:

Calcula calc = Soma;

Simples, nâo?

Antigamente para eventos usava-se a seguinte nomenclatura:

Calcula calc = new Calcula(Soma);

Com o C# 2.0 podemos fazer assim:

Calcula calc = delegate(int a, int b)
{
  return a * b;
};

Ou podemos fazer, neste caso estranho:

Calcula calc = delegate
{
  return 2; // magic number detected!
}

O exemplo acima é meramente ilustrativo.

Ok, agora que conhecemos as sintaxes vamos conhecer alguns delegates muito úteis já existentes no C#:

  • Action (C#2)
  • Predicate (C#2)
  • Func (C#3)

Action

Action corresponde a uma ação a ser executada sobre um valor, onde T define o tipo de valor. Se você nâo conhece generics, sugiro que estude um pouco, não é nada complicado, só não entrarei em detalhes por que não é o escopo deste texto.

O action recebe um parametro do tipo T, por exemplo:

Action<string> printText = delegate(string s)
{
  Console.WriteLine(s);
};

Invocando:

printText("Hello World");

Simples não? No caso, podemos alterar o valor de 's', pois 's' é um reference type, ou seja, poderiamos tratar o texto, retirando por exemplo, todas as vírgulas do texto:

Action<string> removeVirgula = delegate(string s)
{
  s.Replace(",", string.Empty);
};

removeVirgula(Hello, World); // produz Hello World

Predicate

A predicate is the portion of a clause, excluding the subject, that expresses something about the subject

Predicate ou predicado corresponde a uma clausula a qual expressa alguma coisa. Digamos que desejamos saber se um número é par (even):

Predicate<int> even = delegate(int number)
{
  return number % 2 == 0;
};

even(2); // true
even(3); // false

Veja que retornamos um booleano para predicates, pois desejamos saber se o 'number' que foi passado a rotina expressa o que meu predicado quer, no caso saber se o número é par.

Func (C#3)

E por último, existente apenas no C# 3.0 o Func. Diferente do Action e Predicate, o Fund possui 5 variações:

Func<TResult>
Func<T, TResult>
Func<T1, T2, TResult>
Func<T1, T2, T3, TResult>
Func<T1, T2, T3, T4, TResult>

Bom, as assinaturas são muito claras e são lidas da seguinte forma: o tipo de retorno e nenhum parâmetro ou o tipo de retorno com 1 a 4 parâmetros.

Acredito que o número de parâmetros são mais que suficientes, se a rotina começar a ter mais que esse número de parâmetros é possível que ela esteja fazendo mais coisas do que precisaria e podendo ser sub-divida em menores rotinas, facilitando tanto a leitura como reuso. Se você ainda assim achar que precisa de mais, bastaria criar uma outra assinatura com N parâmetros.

Ok, você poderia pensar em fazer uma Func que tratasse a string e não retornasse nada por exemplo. Entretanto, esse tipo de retorno não é possível. Se você pretende usar Func<> você deve fato deseja retornar um valor.

Quando lembro de function e subroutine me recordo de um trecho do texto do livro Code Complete 2nd Edition onde diferenciava o uso das duas palavras.

Function: é como definido na matemática, y = f(x), onde f é uma função. Ou seja, a função recebe um parâmetro e retorna um valor por definição.

Subrountine: já sub-rotina corresponde a uma tarefa a ser realizada, e não retorna valor. Se você já programou em Visual Basic deve ter notado a distinção delas.

Bom, voltando. O correto se deseja usar um delegate com retorno void seria Action, porém ele só possui 1 parâmetro. Mas segundo este link me parece que futuramente será extendido para mais parâmetros como o Func.

O uso é praticamente o mesmo como mostrado para predicate e action, sendo um mix de ambos.

Func<int, int, int> sum = (a, b) => a + b;

sum(1, 2) // return 3

Se não tivermos parâmetro algum, podemos usar:

Func<object> CreateObject = () => { return new object(); }

object o = CreateObject();

Observe que as delegates predefinidas como Action, Predicate e Func não precisam ser obrigatóriamente usadas, você pode criar suas próprias assinaturas como foi feito no início deste post, mas se for possível usar as já existentes use, pois é uma linguagem comum para todos os programadores em C#, poderíamos ter um delegate Process<T> igual a Action<T>, se usasse Action, seria muito claro o que pretende-se fazer. Não é uma regra, mas sempre que possível use as já predefinidas.

Bom, o post ficou bem grande, tentei ser o mais detalhado e claro possível. Até um próximo post.