Caelum | Ensino e Inovação - Cursos de Java, Scrum, Ruby on Rails


RESTfulie com C# – O poder do dynamic

Por Luiz Costa em 26/02/10

Há um mês lançamos uma versão inicial do projeto Restfulie para C#, projeto que tem ganhado bastante visibilidade.  Para deixa-lo tão fácil quanto a versão original em Ruby, utilizamos as novas características dinâmicas da versão 4.0 do C#, alterando a estrutura de objetos em tempo de execução. Algumas pessoas acham esse recurso perigoso demaisoutras acham a escolha certa.

Para enxergar o uso da nova palavra chave dynamic, consideremos um recurso REST que representa um pedido com sua representação em XML como a que segue:

<pedido>
  <data>26/12/2009 11:40</data>
  <total>300.00</total>
  <atom:link rel=”refresh” href=”http://www.caelum.com.br/pedidos/1″ xmlns:atom=”http://www.w3.org/2005/Atom”/>
  <atom:link rel=”update” href=”http://www.caelum.com.br/pedidos/1″ xmlns:atom=”…”/>
  <atom:link rel=”pagar” href=”http://www.caelum.com.br/pedidos’/1/pagar” xmlns:atom=”…”/>
  <atom:link rel=”excluir” href=”http://www.caelum.com.br/pedidos/1″ xmlns:atom=”…”/>
  <atom:link rel=”obterCliente” href=”http://www.caelum.com.br/pedidos/1/clientexmlns:atom=” xmlns:atom=”…”/>
</pedido>

É interessante notar que neste XML temos os dados (data, total) e temos ações que podem ser executadas sobre este recurso. Por exemplo, depois que obtivemos a representação deste recurso, é possível executar algumas ações, descritas através de links no próprio xml: pagar, excluir, obterCliente. Para obter a representação de um recurso no restfulie C#  fazemos da seguinte maneira:

dynamic pedido =
  Restfulie.At(“www.caelum.com.br\pedidos\1”).Get();

Repare que o primeiro passo que fazemos ao declarar a variável pedido é ignorar o seu tipo.  Ou melhor, dizer explicitamente que ela é dinâmica, através do tipo dynamic. Isso nos trás uma série de vantagens como, por exemplo, simplesmente acessar suas properties:

Console.WriteLine(string.Format(“A data do pedido é: {0}”, pedido.Data));
Console.WriteLine(string.Format(“O valor total do pedido é: {0}”, pedido.Total));

Além de podermos acessar suas properties, podemos também seguir os links que estão disponíveis na representação, utilizando invocações de métodos:

pedido.Pagar();
pedido.Excluir();

dynamic cliente = pedido.obterCliente();
Console.WriteLine(string.Format(“Nome do cliente: {0}”, cliente.Nome));

Isso é bastante poderoso: perceba que do lado cliente não precisamos definir nada, apenas dizer que uma referência é dinâmica. Mas como esta mágica acontece com o C#?

Além de existir a palavra reservada dynamic existe uma classe DynamicObjectno C#. Diferente de Ruby, em C#, nem todos os objetos podem ser modificados em Runtime. Para que seja possível adicionar comportamento dinâmico em a um objeto, precisamos estender a classe DynamicObject.

Ao estender esta classe, ganhamos a oportunidade de alterar o comportamento do objeto. Quando fizermos então uma invocação a pedido.Total, o que de fato tem que ocorrer aqui? De acordo com o xml de representação do recurso, devemos obter o valor que está na tag <Total>.

Para implementar isso utilizamos um recurso bem parecido com o method_missing em Ruby e, de certa forma, como as dynamic proxies do Java (apesar destas precisarem de interfaces explícitas). Vejamos a classe DynamicXmlResource:

class DynamicXmlResource : DynamicObject {
   private XElement xmlRepresentation;

   public override bool TryGetMember(GetMemberBinder binder,
             out object result) {
     //pseudo código que encontra a tag xml de acordo
     //com o nome da property informada
     object = XmlRepresentation.FindXMLTagWithName( binder.Name)
             .ReadTheValue()
     return result != null;
   }
}

Esta classe estende DynamicObject e tem um atributo que é a representação do recurso em XML (xmlRepresentation). O mais interessante é o método TryGetMember que intercepta qualquer invocação a um Get de uma property e nos permite fazer o que for necessário. No nosso caso, o que é feito é buscar o valor da da property no xml, que nesse caso tem o nome igual ao da tag do xml. Com isso, toda vez que fizermos pedido.Total, o que está acontecendo é a invocação de TryGetMember da classe DynamicXmlResource.

Da mesma forma que existe o método TryGetMember, existem outros que nos permitem alterar o comportamento do objeto. No nosso projeto, também fizemos o uso do TryInvokeMember:

class DynamicXmlResource : DynamicObject {
  private XElement xmlRepresentation;

  public override bool TryGetMember(GetMemberBinder binder, out object result) {
    result = XmlRepresentation.FindXMLTagWithName(binder.Name).ReadTheValue()
    return result != null;
  }

  public override bool TryInvokeMember(InvokeMemberBinder binder,
        object[] args, out object result) {
    // pseudo código para pegar o link no xml a partir do nome do método
    object link = XmlRepresentation.FindRelAttributeWithName(Binder.Name).ReadTheLink();
    if (link == null)
      throw new ArgumentException
        (string.Format("There is not method defined with name:", binder.Name));

    // faz a chamada remota para o servidor através do link.
    HttpRemoteResponse response = 
      (HttpRemoteResponse) this.InvokeRemoteResource(value.ToString(), binder.Name);
    return result != null;
  }
}

Este método também é parecido com o method_missing, mas neste caso ele intercepta a chamada de um método. No caso do restfulie,  quando chamamos um método qualquer, o que ele faz é, procurar na representação XML e descobrir o link relacionado com o nome do método. Depois disso é só fazer uma requisição http. Novamente, quando fizermos a invocação pedido.Pagar(), ela será interceptada pelo TryInvokeMember e executará uma invocação remota para o Restfulie Server.

Utilizando estas características do C# conseguimos alcançar algo muito próximo do que se faz com o Restfulie Ruby, como comparamos a seguir. Em Ruby:

pedido = Restfulie.at(“http://restfulie-test.heroku.com/orders/14”).get
puts pedido.customer-name
pedido.cancel

E, em C#, temos o código tão sucinto quanto:

dynamic pedido = Restfulie.At(“http://restfulie-test.heroku.com/orders/14.xml”).Get();
Console.WriteLine(pedido.customer_name);
pedido.Cancel();

Uma das consequências da utilização de tipos dinâmicos é que perdemos as vantagens da fase da tipagem estática: não há code complete, e pode ser que a gente erre o nome de um membro, como customer_name, e só viremos a saber disso em tempo de execução. Nada que testes unitários bem colocados não resolvam.

Como podemos ver, a utilização de tipos dinâmicos facilita bastante a utilização da api do Restfulie, assim como diversos outros frameworks vem tirando vantagem com o auxílio desse recurso.

Reforçando a tendência, o Java também anda nesse caminho, com a JSR 292, que melhora o suporte de linguagens dinâmicas na JVM. Em abril de 2003 Robert C. Martin escreveu o artigo “As linguagens dinâmicas vão substituir as estáticas?” e, no final deste artigo, ele faz seguinte pergunta: Estaremos todos nós programando numa linguagem dinamicamente tipada em 2010?. Isso não aconteceu, mas com certeza cada vez mais cenários de utilização estão surgindo, e a tendência é cada vez mais forte.

  • Share/Bookmark

Não posso descobrir nem instanciar tipos genéricos! Porque?

Por Paulo Silveira em 28/04/08

São incontáveis os posts no GUJ com uma pergunta semelhante: “Posso extrair o nome de um tipo genérico?“, “Não consigo extrair tipo do genérico!“, “Utilizando generics para instanciar objetos“, entre outros. Curioso que esse tipo de pergunta tem aumentado muito nos últimos tempos, identificando um possível crescimento no uso do Java 5 em diante. Já era a hora!

Eu já havia postado sobre isso quando falei de reificação de tipos parametrizados, mas de uma maneira mais geral.

A questão básica é a seguinte: Se eu tenho um tipo genérico, que recebe um tipo parametrizado T como argumento, eu posso instanciar T de alguma forma?

Quem sabe tentar assim:

class Dao<T> {
  public T cria() {
    return new T();
  }  
}

Essa forma não funciona. Você não tem garantias sobre os construtores que o tipo T possui. E então se tentarmos assim:

class Dao<T> {
  public T cria() {
    return T.class.newInstance();
  }  
}

Aqui a sintaxe até poderia ser possível, mas infelizmente o java não sabe quem é T nem mesmo em tempo de execução. Nem mesmo com manipulação de bytecode ou qualquer outro recurso. Isso porque o compilador “apaga” essa informação depois de utilizada: é a tal da mal falada erasure.

Qual seria a vantagem da erasure?

Antes de mais nada: erasure não serve para poder rodar código do Java 5 em VMs Java 1.4 ou menor! Não é esse o objetivo.

Quando a JSR14 do generics foi proposta, eles queriam mais que compatibilidade para trás em relação a compilação, eles queriam também a possibilidade de migrar o código antigo para poder usar código novo: uma ArrayList precisa poder ser passada como argumento para alguém que receba List<QualquerClasse> como argumento!

Para ilustrar a situação, imagine que eu tenho uma aplicação grande X que usasse a classe ArrayList em muitos lugares (como a grande maioria das aplicações). Essa aplicação X usa a biblioteca B, que recebe List como argumento em muitos de seus métodos. Se um dia a bibioteca B passasse a usar List genérica, gostaríamos que a aplicação X atualizasse B sem maiores problemas: sem precisar recompilar nada, nem trocar nada no código fonte. O Neal Gafter escreveu muito sobre isso pouco antes da release do Java 5, dada as inúmeras críticas que eles estavam recebendo.

Se o Java tivesse optado por outra maneira de implementar generics, teria ou quebrado compatibilidade com o uso não genérico de classes que viraram genéricas, ou precisaria criar classes paralelas as atuais, praticamente copiadas e coladas, só que estas genéricas, tornando as antigas deprecated ou legadas.

Como o .NET resolveu o mesmo problema?

O .NET seguiu essa segunda forma, e criou uma hierarquia quase que paralela de coleções dentro do name space System.Collections.Generic.

A IList é a interface que define as operações em uma lista não genérica, e a ArrayList é sua implementação mais comumente usada. Quando entrou generics no .NET eles criaram uma outra interface para a lista, com mesmo nome, só que genérica: a IList<T>.

A classe List já é a implementação da interface genérica, e é ela quem você vai usar em vez de ArrayList. Ela possui uma definição bem estranha:

public class List : IList, ICollection,
    IEnumerable, IList, ICollection, IEnumerable

Ela implementa tanto a lista genérica como a não genérica. O .NET tem um recurso que nós não temos, que faz com que apesar dessa lista também implementar a interface não genérica, você só consegue invocar os métodos não genéricos (que trabalham com Object) se estiver se referenciando a ela explicitamente como uma lista não genérica, como o código abaixo:

IList x = new List<String>();
x.Add(2);

Apesar desse código compilar, no .NET temos essa informação dos tipos genéricos em tempo de execução, o que fará gerar uma exceção:

System.ArgumentException: The value "2" is not of type "System.String" e cannot be used in this generic collection

No Java teríamos apenas um unchecked warning na linha da declaração da referência, e uma possível ClassCastException mais a frente no código.

Aqui a vantagem é você poder passar uma List genérica para um código .NET antigo, que recebe como argumento uma IList não genérica. Além disso, no .NET você pode sim descobrir quem é T em tempo de execução:

class ClasseGenerica<T> {
  void metodo() {  Console.WriteLine(typeof(T))}
}

Em resumo: o sistema de generics do .NET é realmente seguro, não temos como burlá-lo através de unchecked casts, como ocorre em Java. O Java novamente sacrificou alguns recursos interessantes em favor a compatibilidade de versões e interoperabilidade entre classes genéricas e as não genéricas já existentes. Cada um com sua vantagem. Como citei no outro artigo, existem algumas idéias de dar suporte a tipos genéricos reificados no Java, ao mesmo tempo que outros ficariam ainda com a erasure, sendo que você pode escolher qual o que te agrada para aquela classe genérica em particular. Talvez ter as duas opções adicione ainda mais complexidade a tipagem genérica do Java, mas eu particularmente gosto da idéia.

Mais uma vez um post que era para ser sucinto ficou longo. Agradeço ao Rafael Steil e Rodrigo Kumpera pela colaboração, e ao Lucas Cavalcanti e Guilherme Moreira pedindo para que fosse elaborado um post mais completo sobre esse assunto tão pertinente.

  • Share/Bookmark



Caelum | Ensino e Inovação
São Paulo: Rua Vergueiro, 3185, cj. 87, próximo ao Metrô Vila Mariana   |   Tel. (11) 5571-2751
Rio de Janeiro: Rua Senador Dantas, 80, cj. 307/308 - Centro   |   Tel. (21) 2220-4156 ou 2297-0033
Brasília: SCS Qd. 8 Bl. B-50, Sala 521 - Ed. Venâncio 2000   |   Tel. (61) 3039-4222