Até a versão 3 do C#, não era possível fazer o que vou mostrar aqui. Havia como contornar, mas nada que soasse bem, ou ficasse muito bonito ou fácil de ler.

O objetivo é pegar um arquivo XML, qualquer arquivo XML, com qualquer esquema, lê-lo, e então ser capaz de acessar seus elementos como se fossem referências a outros objetos, e seus atributos como se fossem propriedades primitivas (strings).

Assim, um xml como esse:

<?xml version="1.0" encoding="utf-8" ?>
<raiz propriedade1="p1">
  <no1 propriedade2="p2">
    <no2 propriedade3="p3">
      <no3 propriedade4="p4" propriedade5="p5" >
        <no4 propriedade6="p6" propriedade7="p7" />
      </no3>
    </no2>
  </no1>
</raiz>

Deve ser possível de ser lido de alguma forma, e, no final, me apresentar um objeto que me permita fazer isso:

Assert.AreEqual("p7", leitor.Raiz.No1.No2.No3.No4.Propriedade7);

A questão é que o xml pode mudar, então como definir que os nós devem originar propriedades dinamicamente? Essa era uma limitação do C# (e do VB) até a versão 3 (9 no VB). E, para atender esse tipo de requisito ou utilizávamos um dicionário ou algo parecído, ou tínhamos que cair em linguagens dinâmicas para fazer esse trabalho. Em Ruby isso é feito com algumas magias de metaprogramação bem interessantes. Tudo isso mudou e agora é possível no C# e no VB.

Foi introduzido o conceito de dinamismo no C# na sua versão 4, sobre o qual eu bloguei a mais de um ano (vejam o post principal aqui, e algumas implicações aqui, aqui e aqui) e escrevi um artigo na .Net Magazine, também ano passado. Algumas coisas foram melhoradas de lá pra cá, e uma destas é a que vou mostrar agora.

Antes disso, vamos deixar clara a especificação do que eu quero. Todo mundo sabe que eu trabalho com TDD, então especifiquei o que quero com um teste. Mas esse não é um post sobre TDD, então vou colocar aqui um teste que resume tudo o que eu espero que aconteça:

[TestMethod]
public void Quando_Passo_Um_XML_Simples_Ele_Cria_Um_Objeto_Com_Propriedades_E_Filhos_E_Netos_Complexos()
{
    var xml = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
                <raiz propriedade1=""p1"">
                  <no1 propriedade2=""p2"">
                    <no2 propriedade3=""p3"">
                      <no3 propriedade4=""p4"" propriedade5=""p5"" >
                        <no4 propriedade6=""p6"" propriedade7=""p7"" />
                      </no3>
                    </no2>
                  </no1>
                </raiz>";
    var leitor = LeitorXML.Ler(xml);
    Assert.AreEqual("p1", leitor.Raiz.Propriedade1);
    Assert.AreEqual("p2", leitor.Raiz.No1.Propriedade2);
    Assert.AreEqual("p3", leitor.Raiz.No1.No2.Propriedade3);
    Assert.AreEqual("p4", leitor.Raiz.No1.No2.No3.Propriedade4);
    Assert.AreEqual("p5", leitor.Raiz.No1.No2.No3.Propriedade5);
    Assert.AreEqual("p6", leitor.Raiz.No1.No2.No3.No4.Propriedade6);
    Assert.AreEqual("p7", leitor.Raiz.No1.No2.No3.No4.Propriedade7);
}

Notem que chamo o método estático “Ler” que retorna um objeto e acesso as propriedades deste objeto, que se baseiam no XML. Para ser capaz de fazer isso preciso trabalhar com um objeto dinâmico. O objeto dinâmico que estou usando foi introduzido pela Microsoft recentemente, e permite incluir membros dinamicamente, tanto propriedades quanto métodos. É o ExpandoObject. Na prática, você pode fazer isso:

dynamic o = new ExpandoObject();
o.Nome = "Giovanni";
o.ObterNomeMaiusculo = (Func<string>) ( () => o.Nome.ToUpper());

Assert.AreEqual("Giovanni", o.Nome);
Assert.AreEqual("GIOVANNI", o.ObterNomeMaiusculo());

Na linha 1 eu crio o objeto, e seto ele para dynamic. É importante lembrar de dizer que ele é dinâmico, porque se você declará-lo com var ou tipá-lo como ExpandoObject não vai poder trabalhá-lo dinamicamente. Na linha 2 eu crio uma propriedade, e na 3 eu crio um método. Nas linhas 5 e 6 eu chamo esta propriedade e este método.

O ExpandoObject também implementa um dicionário de string/objeto, onde ele guarda todos os membros (propriedades, métodos, etc) o que permite a você listar estes itens, e incluir e excluir itens de maneira ainda mais dinâmica.

Bem simples, certo?

Agora que vocês já sabem como funciona o ExpandoObject, já até devem saber como fazer o tal do leitor de XML. Aqui está o código:

public class LeitorXML
{
    public static dynamic Ler(string xml)
    {
        dynamic leitor = new ExpandoObject();
        var elementoRaiz = System.Xml.Linq.XElement.Parse(xml);                
        var raiz = CriarObjetoDinamicoAPartirDeElementoXML(elementoRaiz);
        ((IDictionary<string, object>) leitor).Add(elementoRaiz.Name.LocalName.DeixaPrimeiraLetraMaiuscula(), raiz);
        return leitor;
    }
    private static IDictionary<string, object> CriarObjetoDinamicoAPartirDeElementoXML(XElement elemento)
    {
        IDictionary<string, object> filho = new ExpandoObject();
        foreach (var atributo in elemento.Attributes())
            filho.Add(atributo.Name.LocalName.DeixaPrimeiraLetraMaiuscula(), atributo.Value);
        foreach (var elementoFilho in elemento.Elements())
            filho.Add(elementoFilho.Name.LocalName.DeixaPrimeiraLetraMaiuscula(), CriarObjetoDinamicoAPartirDeElementoXML(elementoFilho));                    
        return filho;
    }
}

O ExpandoObject que representa o leitor é criado logo na primeira linha do método Ler. A partir daí, leio os elementos do XML e os transformo em propriedades que apontam para outros ExpandoObjects. Os atributos são transformados em propriedades que retornam strings. Os nomes das propriedades são definidos a partir dos nomes dos elementos e dos atributos. Em 20 linhas tudo foi definido, esse código é suficiente para passar na especificação anterior (o único código que não está aí é um código auxiliar, um método de extensão, usado para tornar a primeira letra maiuscula em cada propriedades, ou seja, é cosmético).

Esse código vai se adaptar a qualquer xml que você gerar, desde que você respeite a convenção: elementos viram referências a outros objetos, atributos viram propriedades primitivas.

Para os que diziam que dynamic não servia pra nada, o que acharam?


Postado na(s) categoria(s) C# pelo Giovanni Bassi em 9 de dezembro de 2009 às 07:34 | Tags:

Comentários


dezembro 9. 2009 08:56
José Filipe
Sempre achei o esquema de dynamics questionável, não por "não servir de nada", como você citou, mas por achar que abre a porta pra gambiarras nervosas (tem toda a discussão que restringir isto é o papel de um bom arquiteto ou desenvolvedor, whatever)..

Independente disto, o exemplo desse post é realmente fantástico. Conseguir criar propriedades dinâmicas com base em dados quaisquer abre um número de possibilidades muito grande pra modelos que podem ser customizados posteriormente, muito legal.

E viva os metadados!

http://addwatch.wordpress.com/http://addwatch.wordpress.com/


dezembro 9. 2009 10:49
Vinicius Quaiato
Giggio, parabéns pelo artigo.

Duas perguntinhas:
1 - Quais as implicações de performance para o abuso no uso de dynamics?
2 - Diferentemente do var e dos anonymous types, é possível trabalhar com dynamic fora do escopo local. No entanto eu não tenho ajuda do intellisense e devo conhecer a estrutura do objeto em desgin time, certo?

Abraços,
Vinicius Quaiato.

http://www.viniciusquaiato.com/http://www.viniciusquaiato.com/


dezembro 9. 2009 13:32
Giovanni Bassi
Vinicius,
1) Há um impacto em performance, mas o CLR dá um jeito de cachear isso pra minimizar. Mas há, como em toda linguagem dinâmica.
2) Certo. Também como toda linguagem dinâmica, você tem que saber a estrutura do objeto. Tem prós, e tem contras. Consegue abrir mão da checagem em compile time? Smile

http://unplugged.giggio.net/http://unplugged.giggio.net/


dezembro 24. 2009 00:32
pingback
Pingback from unplugged.giggio.net

Rodando Ruby com C#: Parte 1

http://unplugged.giggio.net/unplugged/post/Rodando-Ruby-com-C-Parte-1.aspxhttp://unplugged.giggio.net/unplugged/post/Rodando-Ruby-com-C-Parte-1.aspx

Comentar


(Vai mostrar seu Gravatar)

  Country flag

biuquote
  • Comentário
  • Pré-visualização
Loading



Quem é Giovanni Bassi

Giovanni Bassi Sou uma pessoa apaixonada por tecnologia e especificamente por .Net. Sou consultor independente especialista em .Net, focado em arquitetura e melhores práticas. Tenho dezenas de artigos publicados na .Net Magazine, revista da qual sou editor técnico. Ministro palestras e cursos de vez em quando, e quando dá tempo eu respiro um pouco. Mais detalhes nesta página.

Busca

Selos

MVP

MCPD

MCSD

.Net Magazine

Abaixo ao if!

Calendário

«  março 2010  »
seteququsedo
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234
Ver detalhamento de posts no calendário

Blogs interessantes

    OPMLDownload OPML file

    Postagens recentes

    Comentários recentes

    Disclaimer / Aviso
    As opiniões colocadas neste blog são minhas e pessoais e não expressam necessariamente as opiniões de meus empregadores, pareceiros e amigos. Da mesma forma, os comentários feitos por leitores do blog não expressam a minha opinião.

    © Copyright 2010 .Net Unplugged
    Log in