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.
Parabéns pelo Post, bem claro, simples e direto ao ponto.
Excelente texto! Gostaria que o responsável pelo blog escrevesse com mais frequencia.
Ainda não entendi muito bem o uso disso…
Você usou um predicado para descobrir se um número é par… o mesmo resultado que criar um étodo para isso. qual a vantagem ?
public bool par(int numero)
{
return (numero % 2 == 0)
}
Olá diogo,
O meu exemplo de ‘par’ foi apenas para ilustrar como entender uma interface do tipo Predicate, que se traduz como uma função booleana que recebe um inteiro como parametro.
Muitas vezes fica mais facil criar as funcões inline no codigo ao inves de descrevê-la fora do meu escopo. Isso é uma das coisas que mudaram muito no C#3 com a introdução de lambda expressions.
eu poderia muito bem atribuir a sua funcao a uma variavel do tipo Predicate myEven = par;
Entende que Predicate é uma interface para uma função. Realmente não faz sentido vc definir uma função da forma como defini.
Se você já trabalhou com Linq ou funções de IEnumarable do C# como Find, ForEach e etc, deve ter visto que o que ele pede é um Predicate e Action.
Espero ter conseguido responder a sua duvida.