segunda-feira, 28 de janeiro de 2013

Utilizando Delegates com Co-Rotinas - parte 1

No post anterior comentei sobre delegates, funcs e actions, mas infelizmente não dei exemplos práticos de utilização.

Nesta primeira parte do post, introduzirei o conceito de co-rotina e um exemplo de utilização.

Co-Rotinas


Co-rotinas facilitam a programação e visualização de código quando temos a necessidade de fazer chamadas assíncronas de forma sequencial, ou seja, esperar o resultado de uma operação antes de iniciar a próxima operação, permitindo que tomemos decisões de acordo com os resultados obtidos.

Na programação síncrona, o fluxo da execução de um programa ocorre em uma mesma Thread. Na programação assíncrona, uma outra Thread é criada para execução de uma determinada tarefa que tem um tempo de duração indefinido, como por exemplo, chamadas a serviços da Web.

Geralmente os métodos assíncronos possuem eventos ou callbacks que são disparados no momento em que a operação for finalizada/completada, permitindo que o programador escreva código para tratar o resultado.  A dor de cabeça vem quando a lógica de uma aplicação fica complexa, por exemplo, quando há a necessidade de fazer várias chamadas utilizando eventos e callbacks, tornando o código difícil de ler, e sujeito a bugs.

Para exemplificar, vamos escrever um programa simples que faz o download do código html de uma página, e imprime o conteúdo numa aplicação console:

     class Program
    {    
        public static void Main(string[] args)
        {
            var webClient1 = new WebClient();
            webClient1.DownloadStringCompleted += (s1, e1) => {
                Console.WriteLine(e1.Result);            
            };
            webClient1.DownloadStringAsync(new Uri("http://silverlightrush.blogspot.com"));    
            Console.ReadLine();
        }
    }


Ok, esse código quando rodado irá instanciar uma classe chamada WebClient. Esta classe contém um método chamado DownloadStringAsync. Quando executado, o código não "espera" o download terminar, pois é um método assíncrono, e então segue para a próxima linha. Antes de chamar o método, devemos subscrever ao evento DownloadStringCompleted. Através de uma expressão lambda, imprimimos o conteúdo da página toda na tela.

Agora vamos inserir uma regra de negócio ao programa. Digamos que ao terminar de ler o html, caso o Lenght do resultado seja maior que 1000 bytes, faremos uma outra chamada para baixar o html de um outro site. Então o novo código ficou definido assim:
     
    var webClient1 = new WebClient();
    webClient1.DownloadStringCompleted += (s1, e1) => 

    {
        if (e1.Result.Length < 1000)
            Console.WriteLine(e1.Result);
        else {
            var webClient2 = new WebClient();
            webClient2.DownloadStringCompleted += (s2, e2) => {
                Console.WriteLine(e2.Result);
            };
            webClient2
.DownloadStringAsync(new Uri("http://google.com"));
        };
    };
    webClient1
.DownloadStringAsync(new Uri("http://silverlightrush.blogspot.com"));
    Console.ReadLine();

Pronto. A nossa primeira regra de negócio foi inserida. O código funciona perfeitamente. Mas e se agora quisermos inserir uma nova regra baseado no resultado da segunda chamada ? Repare que o código está difícil de ler e provavelmente vai ficar difícil de dar manutenção.

Utilizando Co-Rotinas


Utilizando a técnica de co-rotinas, escrevemos código sequencial, enfileirando diversas chamadas assíncronas, utilizamos os tradicionais callbacks apenas dar prosseguimento ao fluxo de trabalho.


Vamos ver como fica a sintaxe para esse mesmo programa mas com a utilização de co-rotinas:

    class Program
    {
        static string htmlBlogspot;
        static string htmlGoogle;
    
        public static void Main(string[] args)
        {
            Coroutine.BeginExecute(Operacoes());        
            Console.ReadLine();
        }
    
        static IEnumerable<Routine> Operacoes() {
            yield return PrimeiraOperacao;
        
            if (htmlBlogspot.Length < 1000) {
                Console.WriteLine(htmlBlogspot);
                yield break//cancela a execução da próxima
            }
        
            yield return SegundaOperacao;
        
            Console.WriteLine(htmlGoogle);
        }
    
        static void PrimeiraOperacao(Action completed) {
            var webClient1 = new WebClient();
        
            webClient1.DownloadStringCompleted += (s1,e1) => {
                htmlBlogspot = e1.Result;
                completed(); //marca o fim da operação
            };
            webClient1.DownloadStringAsync(new
                Uri("http://silverlightrush.blogspot.com"));        
        }
    
        static void SegundaOperacao(Action completed) {
            var webClient2 = new WebClient();
            webClient2.DownloadStringCompleted += (s2, e2) => {
                htmlGoogle = e2.Result;
                completed(); //marca o fim da operação
            };
            webClient2.DownloadStringAsync(new Uri("http://google.com"));
        }
    }



O novo código:

  • A co-rotina é inicializada por Coroutine.BeginExecute(Operacoes()) 
  • A co-rotina é executada linha a linha dentro do método Operacoes()
  • O método Operacoes() retorna IEnumerable<Routine>
  • Cada operação assíncrona é executada no momento em que utilizamos yield return
  • A co-rotina pode ser cancelada com yield break;
  • Cada rotina ficou separada em seu próprio método. Quando a rotina completa, chama-se a action completed()

Como isso é possível ?


O Código abaixo é responsável pela enumeração e execução sequencial da co-rotina. Através da iteração do método que retorna IEnumerable<Routine>, o compilador C# faz uma mágica criando uma State Machine.




Delegates

Repare que foi criado um Delegate especial chamada Routine. Este delegate é utilizado no enumerador da co-rotina. A action que se passa como parâmetro é uma referência a um callBack que devemos executar para dar continuação à enumeração (MoveNext()).

Para ver o código completo no GitHub, clica no link abaixo.

terça-feira, 22 de janeiro de 2013

.NET Delegates, Funcs, Actions

Delegate é um tipo especial de classe no .NET que quando instanciado, armazena referência a um método ou a uma lista de métodos, incluindo anonymous methods, desde que possuam assinatura compatível com sua definição.

Com esse recurso, podemos passar delegates como parâmetros a outros métodos e/ou guardar referências a esses métodos para uso posterior.

Delegates, assim como classes, devem ser previamente definidos e podem ser instanciados direta ou indiretamente, e após sua instanciação, se comportam como se fossem métodos comuns. Quando invocados, executam o método referenciado.

Os delegates mais usados no .NET são os do tipo: EventHandler, Action e suas variações, Func<TResult> e suas variações.

Se você utiliza .NET 3.5 ou superior, provavelmente nunca irá criar um novo tipo de delegate, basta utilizar um delegate já existente.

Declarando Delegates


Para entendermos melhor os delegates, vamos declarar alguns antes de utilizá-los. Cada linha abaixo define um delegate diferente:

    public delegate void VoidSemParametros();
    public delegate void VoidComParametros(string valor);
    public delegate int IntSemParametros();
    public delegate int IntComParametros(int x, int y);

A primeira linha define um delegate chamado VoidSemParametros, e podemos associá-lo a um método em que seu retorno seja do tipo void, e que não possua nenhum parâmetro. Vamos então declarar um método que satisfaça essas condições:

    public void MetodoSemParametros() {
        Console.WriteLine("MetodoSemParametros");
    }

Agora que temos um método com assinatura e retorno compatível, podemos instanciar o delegate passando o método como parâmetro em seu construtor:

    /* Instancia VoidSemParametros utilizando construtor (new()) */
    var delegateNew = new VoidSemParametros(MetodoSemParametros);
    delegateNew(); /* executa */

Um outro método que também é compatível com o delegate VoidSemParametros:

    public void OutroMetodoSemParametros() {
         Console.WriteLine("OutroMetodoSemParametros");
    }

Dessa vez, instanciaremos o delegate passando diretamente o método:

    /* Instancia VoidSemParametros apontando direto para um método com assinatura compatível */
    VoidSemParametros delegateExplicito = OutroMetodoSemParametros;
    delegateExplicito(); /* executa */

Quando rodarmos este programa, ao executar o delegate, veremos que o método que foi passado como referência será executado, imprimindo alguma coisa na saída do Console.

Podemos também instanciar um delegate via anonymous methods:

    /* Instancia VoidSemParametros utilizando anonymous delegate */
    VoidSemParametros delegateInline = delegate() {
        Console.WriteLine("Anonymous delegate...");
    };

Também é possível criar instâncias de delegates através da combinação de dois ou mais instâncias de delegates diferentes. Quando este delegate é executado, executará os métodos referenciados na ordem em que foram "somados":

    /* Instancia um delegate combinando vários delegates */
    var delegateCombinado = delegateInline + delegateNew;

Pode-se também utilizar concatenação de delegates, utilizando o operador += em um delegate previamente instanciado:

    /* Concatena delegates */
    delegateCombinado += delegateExplicito;

    /* O método Console.WriteLine possui várias sobrecargas, uma delas possui assinatura compatível com VoidSemParametros */
    delegateCombinado += Console.WriteLine;

    /*executa todos os delegates que foram combinados na mesma ordem*/
    delegateCombinado();

Agora vamos utilizar o segundo delegate que declaramos, na segunda linha, identificado como VoidComParametros. Para isso, vamos criar um método com assinatura compatível. O retorno deve ser void, e deve possuir um parâmetro do tipo string:

    public void Escreve(string valor) {
        Console.WriteLine("Valor: {0}", valor);
    }

Seguindo a mesma linha de raciocínio, instanciaremos o delegate VoidComParametros de duas maneiras para que fique bem claro:

    var escreve = new VoidComParametros(Escreve);
    escreve("Escrito via método programa.Escreve");

    /* O método Console.WriteLine possui várias sobrecargas, uma delas possui assinatura compatível com VoidComParametros */
    VoidComParametros escreveDireto = Console.WriteLine;
    escreveDireto("Escrito via Console.WriteLine");

O terceiro delegate identificado como IntSemParametros possui um retorno do tipo Int e não possui parâmetros. Um método com assinatura compatível ficaria assim:

    public int FuncaoQueRetornaInt(){
        return 1;
    }

Da mesma maneira, para instanciar IntSemParametros, o código é este:

    var funcao = new IntSemParametros(FuncaoQueRetornaInt);
    escreve(funcao().ToString());

Para finalizar vamos criar dois métodos compatíveis com a assinatura do último delegate, identificado como IntComParametros:

    public int Soma(int x, int y) {
        return x + y;
    }

    public int Multiplica(int x, int y){
        return x * y;
    }

Vamos inicializar IntComParametros primeiro apontando para o método Soma, executá-lo, e depois vamos alterar o delegate para que aponte para o método Multiplica:

    var calculadora = new IntComParametros(Soma);
    var resultadoSoma = calculadora(3,3);
    Console.WriteLine("Soma: {0}", resultadoSoma);

    calculadora = Multiplica; //mudou a referência para outro método
    var resultadoMultiplicacao = calculadora(3,3);
    Console.WriteLine("Multiplicação: {0}", resultadoMultiplicacao);

Encontramos neste útlimo exemplo um caso de uso real para a utilização de delegates. Imagine uma aplicação do tipo calculadora. O usuário troca a operação (soma, multiplicação, divisão, etc) e trocamos o método referenciado pelo delegate, retornando resultados diferentes.

Action

Disponibilizado no .NET 3.5, amplamente utilizado em sintaxe LINQ e em expressões Lambda, Action nada mais é que um delegate com a seguinte assinatura:
   
    public delegate void Action();

O .NET Framework possui previamente já definidos uma série de delegates/Actions com parâmetros genéricos para suportar LINQ: Action<T>, Action<T1,T2>, Action<T1, T2, T3>, Action<T1, T2, T3, T4>, etc. 

    public delegate void Action<T>(T parametro);
    public delegate void Action<T1, T2>(T param1, T2 param2);
    public delegate void Action<T1, T2, T3>(T param1, T2 param2, T3 param3);
//etc...

Cada "T" representa um tipo de parâmetro que o método deve possuir. Por exemplo, Action<string> corresponde a um delegate do tipo void com um parâmetro string. Action<string, int> corresponde a um delegate do tipo void que recebe dois parâmetros: string, int. De forma genérica, podemos utilizar esses delegates sem a necessidade de declarar delegates específicos. Vejamos alguns exemplos:

    /* Action é um delegate semelhante ao VoidSemParametros */
    var action = new Action(MetodoSemParametros);
    Console.WriteLine(action.Method.Name);

    /* Action<T> é um delegate semelhante ao VoidComParametros se T for do tipo String */
    Action<string> actEscreve = Escreve;
    actEscreve("Escrito via Action<string>");

Func<TResult>

Semelhantes a Action's, exceto pelo fato de que possuem retorno do tipo <TResult>. A assinatura do delegate Func<TResult> é a seguinte:

    public delegate TResult Func<out TResult>();

Do mesmo modo que Actions possuem diversas implementações disponíveis no .NET Framework, Func's também. Um detalhe importante é que o último parâmetro é sempre o tipo genérico de retorno que o método deve possuir em sua assinatura.


    public delegate TResult Func<T, out TResult>(T parametro);
    public delegate TResult Func<T1, T2, out TResult>(T1 param1, T2 param2 );
    public delegate TResult Func<T1, T2, T3, out TResult>(T1 param1, T2 param2, T3 param3);
    //etc.

Veja algumas maneiras de utilizar Func:

    Func<int> funqInt = FuncaoQueRetornaInt;
    Console.WriteLine("Func<int>: {0}"funqInt());

    Func<int> funqLambda = () => { return FuncaoQueRetornaInt(); };
    Console.WriteLine("Lambda: {0}"funqLambda());

    Func<intintint> funqParameters = Soma;
    Console.WriteLine("funqParameters: {0}"funqParameters(23));

    funqParameters = Multiplica;
    Console.WriteLine("funqParameters: {0}"funqParameters(23));

Eventos

Delegates são utilizados na implementação de eventos em aplicações .NET. Num próximo post, mostrarei como criar e implementar seus próprios eventos.

LINQ

A partir da versão .NET 3.5, com o advento do LINQ, delegates (na forma de Actions e Func's) são amplamente utilizados.

Resumo

Neste post, apresentei como declarar e utilizar delegates. É importante notar que a partir do .NET 3.5, com o advento do LINQ, foi disponibilizado os delegates genéricos Action e Func, e provavelmente você nunca mais precisará declarar um delegate novo. Basta utilizar uma das versões de Action e Func que se encaixa nas suas necessidades.

Código

O código completo para este programa encontra-se no Gist

segunda-feira, 21 de janeiro de 2013

ArgumentNullExceptions com Expressions

Introdução

Quando escrevemos código reaproveitável, criamos funções e/ou classes que recebem parâmetros. Na maioria das vezes esses parâmetros são fundamentais na implementação da lógica dentro de uma classe ou método, e o programador mais experiente irá, de forma defensiva, verificar se um determinado parâmetro recebido é válido antes de prosseguir com a lógica, e caso contrário, informar ao cliente (que está consumido o código) do erro cometido com a exceção correta.

Exemplo fictício

Vamos supor que temos uma classe chamada Formulario. Essa classe, quando instanciada, recebe em seu construtor uma instância de outra classe chamada Registro, que representa um registro no banco de dados, veja o código abaixo:


Note que o formulário recebe um registro, e quando o usuário clicar no botão Gravar, o registro por si mesmo irá ser persistido no banco de dados. Para consumir esse código, basta fazer a seguinte chamada:

Até aí tudo bem, mas a linguagem permite que façamos o seguinte código, sem erros de compilação:

O erro só será visto em tempo de execução, pois quando o usuário clicar em Gravar, o valor de "registro" é nulo e consequentemente o .NET irá lançar uma exceção não tratada e a aplicação provavelmente irá parar de funcionar.

Adicionando ArgumentNullException

É uma boa prática verificar se um parâmetro é nulo antes de sair consumindo o valor passado por referência.
Desta maneira, destacamos a importância do valor do parâmetro na classe, e que sem um valor válido, lançaremos uma exception informando o nome do argumento inválido. Vale ressaltar que classe ArgumentNullException recebe uma string que representa o nome do argumento que estamos tratando e esse valor deve ser passado de forma correta. Assim, se o cliente que estiver consumindo esse código tratar a excessão dentro de um bloco Try/Catch, ele terá essa informação de forma precisa.

Evitando código tedioso

Imagine agora um método e que contenha 4 parâmetros, todos eles obrigatórios. Teríamos o trabalho tedioso de escrever 4 vezes a mesma expressão, além do trabalho de digitar na mão o nome dos argumentos ao lançar a exception.
Para automatizar essa tarefa, criei uma classe chamada Requires, contendo um método chamado That, para utilizar da seguinte maneira:

O método That recebe uma Lambda Expression que representa uma função que retorna verdadeiro ou falso. Desejamos que o parâmetro "registro" não seja nulo. Caso o valor seja nulo, o método lança automaticamente para nós um ArgumentNullException com o nome do parâmetro corretamente, sem nos preocuparmos em digitar isso na mão, facilitando a nossa vida.
A classe requires ficou conforme o código abaixo:

Outro exemplo com Extension Method

Extension Methods recebem pelo menos um parâmetro (this), e devemos verificar se possuem valores. É muito fácil passar valores nulos para ExtensionMethods, basta fazer um Cast implícito.

No exemplo abaixo, possuímos uma classe Cliente que implementa a interface IEntidade.

Como no caso anterior, podemos facilmente escrever um programa que consome essas classes chamando o extension method criado:
No ExtensionMethod "AlteraEstado", não verificamos se passamos um valor nulo (entidade). Se escrevermos o seguinte código, a aplicação compilará normalmente, e só veremos o problema em tempo de execução, pois o cast que foi feito retornará nulo:
O Extension Method AlteraEstado deve ser modificado para ficar assim:
Dessa forma o programador será informado corretamente sobre um erro de argumento nulo, e terá oportunidade de fazer correções antes de entregar ao cliente. =)