Nessas ultimas semanas falamos um pouco sobre o tipo dynamic que foi lançado junto com o .Net 4.0, por isso hoje teremos a conclusão dessa “saga dinâmica” ( na minha cabeça pareceu um nome legal).

Os últimos posts tiveram como objetivo de te preparar para o que vem a seguir, a habilidade mais foda do dynamic. Mas antes de prosseguirmos segue o link para os últimos artigos.

1 – (framework construido usando o dynamic) – Simple.Data

2 – Conhecendo o Dynamic

3 – ExpandoObject

 

Objetivo

Nosso objetivo será aprender a trabalhar com a classe DynamicObject e seus métodos TryGetMember/TrySetMember e o TryInvokeMember, conseguindo assim criar métodos “verdadeiramente” dinâmicos.

 

O Simple.Data e seus métodos “fantasmas”

Então, nós vimos várias coisas interessantes que o dynamic “consegue fazer”. Vimos um objeto em que propriedades, métodos e eventos podem ser adicionados e retirados em tempo de execução, e vimos um Framework em que usava o próprio nome dos métodos como parâmetros.

É esse “método fantasma” que iremos fazer hoje.

Se você não lembra de como eram o métodos do Simple.data , dê uma olhada nesse exemplo:

// Não existe o método"FindByCodUsuario", na verdade ele chama o método "FindBy" passando
// "CodUsuario" como parâmetro, alem dos parâmetros desse método (nesse caso o numero 1)
var user = db.Usuario.FindByCodUsuario(1);

Mas que tipo de magia negra é essa?

Isso é mais simples do que parece, graças ao DynamicObject.

 

DynamicObject – O “messias” dinâmico

A classe DynamicObject também faz parte do namespace System.Dynamic. Ela é uma classe abstrata e nos da a habilidade de acessar as chamadas dos métodos/propriedades em tempo de execução.

“Hmmm, continue…”

Quando você acessa uma propriedade de um objeto, o comportamento que esperamos do .NET é: achar a propriedade que queremos,  executar o GET/SET dela, retornar/atribuir o valor nela (ou em outra variável dependendo do SET). Ou seja mais ou menos isso aqui:

Se revermos nossos passos veremos que tem uma coisa que o .NET faz automaticamente, que é achar a propriedade. Ele presume que queremos a propriedade Nome só porque colocamos objeto.Nome! [¬.¬]

MAS E SE nós conseguissemos mecher nesse comportamento? Se por acasso conseguirmos alterar as leis desse mundo para quando chamarmos o objeto.Nome ele retornar a propriedade Idade? E não estou falando alterar o Get do Nome, mas realmente chamar a propriedade Idade.

Com o nosso DynamicObject isso é possivel.

 

The Middle Man

Essa magia que distorce a realidade do .Net (??) só é possivel porque o DynamicObject adiciona alguns intermediários á aquele comportamento, o TryGetMember e o TrySetMember.

Agora sempre que tentarmos setar um valor a uma propriedade, ANTES do .NET sequer achar se essa propriedade realmente existe, ele chama o método TrySetMember. E graças a isso conseguimos escolher que tipo de retorno/rotina queremos que seja executada. Vamos conhecer um pouco sobre eles:

bool TryGetMember (GetMemberBinder binder, out object result)

Binder – É a variavel que contém informações como nome da propriedade que foi chamada  e tipo.

Result – É o valor da propriedade.

public override bool TrySetMember(SetMemberBinder binder, object value)

Value – O valor que foi passado para a propriedade.

Exemplo

Vamos a um exemplo mais pratico, irei criar uma classe que faz a mesma coisas que o ExpandoObject, adicionar propriedades em tempo de execução. Vamos la, primeiro passo é criar uma classe com um dictionary ou hashtable para salvar o nome e valor das propriedades:

 /// <summary>
    /// Herdamos ela de DynamicObject
    /// </summary>
    public class SuperObject : DynamicObject
    {
        /// <summary>
        /// Nosso dicionario que ficara com os nomes e valores das nossas variaveis
        /// </summary>
        public IDictionary<string, object> Dictionary { get; set; }

        public SuperObject()
        {
            // Iniciamos o nosso dicionario no construtor
            Dictionary = new Dictionary<string, object>();
        }

Agora adicionamos os nosso overrides dos métodos TryGetMember e TrySetMember para procurar as propriedades dentro do dictionary ao invés do convencional:

   /// <summary>
        /// Nosso override do método TryGetMember do DynamicObject
        /// </summary>
        /// <param name="binder">Variavel que mostra o nome, tipo do parametro que foi invoca </param>
        /// <param name="result">O resultado da chamada. No caso o valor da propriedade que foi chamada</param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            // Nos verificamos se existe a propriedade existe no dicionario
            if (Dictionary.ContainsKey(binder.Name))
            {
                // Pega o valor da propriedade
                result = Dictionary[binder.Name];
            }
            else
            {
                // Nulo
                result = null;
            }
            return true;
        }

        /// <summary>
        /// Nosso override do método TrySetMember do DynamicObject
        /// </summary>
        /// <param name="binder">Variavel que mostra o nome, tipo do parametro que foi invoca </param>
        /// <param name="value">O valor que foi passado na chamada. </param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            // Só adicionamos o valor no dicionario
            Dictionary[binder.Name] = value;

            return true;
        }

Testando

Agora que nossa classe esta pronta basta testar:

dynamic sup = new SuperObject();

//Seto qualquer propriedades
sup.Nome = "André";
sup.Idade = 23;

//Imprime os resultados
Console.WriteLine(sup.Nome);
Console.WriteLine(sup.Idade);

Sacou? Agora sempre que invocamos uma propriedade ele pega do dicionário! Alem do mais você consegue fazer n coisas com isso! Um ótimo exemplo de como pode ser utilizado é o Simple.Data (ou um dos outros centenas de frameworks de acesso a dados dinâmicos que existem).

Mas isso é apenas a ponta do Iceberg.

 

Alterando a chamada dos métodos

Nós também podemos fazer isso com os métodos, mas para isso usaremos outro método, o TryInvokeMember!  O Funcionamento é parecido, mas alem dos parâmetros binder e do result temos também o args que é um array de objetos com os parâmetros que foi passado para o método.

Vamos implementar uma rotina na nossa classe para quando invocarmos o método imprimeX ele retorna uma mensagem mais o valor da propriedade X.

Primeiro adicionamos nosso método imprime a nossa classe:

        /// <summary>
        /// Método que "Imprime" o valor de uma propriedade
        /// </summary>
        /// <param name="prop">Nome da Propriedade</param>
        /// <returns></returns>
        public virtual string Imprime(string prop)
        {
            // Uma mensagem só para mostrar o funcionamento
            return "O valor é " + Dictionary[prop];
        }

Agora adicionamos o override do TryInvokeMember:

        /// <summary>
        /// Método que é executado sempre que tentamos invocar um método
        /// </summary>
        /// <param name="binder">Variavel que contém informações como nome do método, tipo, etc...</param>
        /// <param name="args">Coleção com os paramêtros que foram passados para o método</param>
        /// <param name="result">Retorno do método</param>
        /// <returns></returns>
        public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result)
        {
            //Checa se o método que chamei é um "Imprime"
            if (binder.Name.Contains("Imprime"))
            {
                // Pega o nome da propriedade
                string valor = binder.Name.Replace("Imprime", "");
                //Chama o método Imprime passando a propriedade
                result = Imprime(valor);

                return true;
            }

            // procura o método da maneira convencional
            return base.TryInvokeMember(binder, args, out result);

        }

E agora finalmente basta chamarmos um “ImprimeIdade()” por exemplo:

 dynamic sup = new SuperObject();

 //Seto qualquer propriedades
 sup.Nome = "André";
 sup.Idade = 23;

 // Chama o Metodo ImprimeIdade
 Console.WriteLine(sup.ImprimeIdade());

 Console.Read();

E o resultado:

Conclusão:

Mais uma porta foi aberta com a chegada do dynamic! Por mais que os exemplos sejam muitos simples e semi-inúteis o importante foi mostrar o funcionamento desses métodos do dynamicObject. É importante lembrar que frameworks como o Simple.Data são criados baseados puramente no dynamic, e conseguem ser incrivelmente úteis. Por isso manter a cabeça aberta é importante, claro que você também não vai fazer o seu programa usando apenas variaveis dinâmicas!

Hoje foi só para mostrar mais uma coisa bacana que da para fazer com o dynamic mesmo. Com isso termino meus posts sobre o dynamic (pelo menos temporariamente).

Até mais!

http://blogs.msdn.com/b/csharpfaq/archive/2009/10/19/dynamic-in-c-4-0-creating-wrappers-with-dynamicobject.aspx