Delegates em C# (Ponteiros de Função)
- c#
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.