Extension Methods e interfaces

Com certeza não colocaria a implementação de uma rotina de comparação de propriedades entre duas instâncias distintas de um dado objeto (que possam retornar um dicionário contento apenas o que existe de diferente entre elas) como sendo algo complexo, na verdade existem diversas maneiras de implementar esta solução. A título ilustrativo poderíamos elencar algumas idéias como, por exemplo escrever um de/para manual diretamente nos objetos², o uso de reflection para varrer as propriedades de escopo público de qualquer objeto da aplicação, ou até mesmo se formos pensar completamente out of the box, trabalhar com o compare entre as strings XML serializadas dos objetos em questão.

Mas vamos adicionar ao requisito principal ( comparar objetos) um outro requisito que diz respeito a impossibilidade de alterarmos a implementação atual dos objetos em questão (ex: estes objetos estão ‘trancados’ em um assembly que não temos acesso / não são de nossa responsabilidade) e, existe também a necessidade de obtermos o máximo de performance desta rotina. Um detalhe importante relativo ao lado da performance é que, eventualmente, poderemos trabalhar com objetos muito grandes e complexos (propriedades com lista / dicionários de outros tipos do sistema) que mudam com o tempo sem que todos os envolvidos no projeto sejam notificados. Agora ficou divertido!

Pra que toda a solução proposta aqui no post fique bem clara (e ninguém queira me queimar sob acusação de magia negra), vamos começar bem do começo.

Extension Methods – Parte Zero

Extension Methods apareceram no universo .Net na versão 3.5 do framework como forma de permitir aos desenvolvedores realizar alterações (extensões, pra ser mais preciso e fazer jus ao nome da parada) em objetos existentes (sejam eles nativos ou não), provendo um nível gigantesco de maleabilidade / flexibilidade ao desenvolvedor.

A MSDN diz o seguinte:

“Extension methods enable you to “add” methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type.”

Os exemplos comumente vistos em posts sobre EM mostram um cenário onde você quer por exemplo adicionar um método – digamos – ConcatTo(“bola”) pra todas as strings do seu sistema. Muito simples e sem nenhuma confusão até aí e, sem dúvidas é no que mais se vê EM aplicados. Mas claro, não é o único motivo!

Extension Methods – Parte 101

Dado o snippet abaixo, me diga, o que você acha que aconteceria quando eu apertasse F5 no meu VisualStudio?

public interface IInterface { }
public class MinhaClasse
{
  public MinhaClasse()
  {
    IInterface qualquerCoisa = null;
    qualquerCoisa.Do();
  }
}
public static class IInterfaceExtensions
{
   public static void Do(this IInterface @this) { Console.Write("Opa!"); }
}

Ok. Compilar faz sentido, certo? Um sentido meio estranho, mas faz! Agora, se você rodou o código, provavelmente viu algo como a imagem a seguir:

Opa!

Opa!

OOOOPA!!

OOOOPA!!

Agora é que vem a pergunta que não quer calar (pode perguntar, não tem problema)! – Como diabos uma interface, com uma referência nula, pode ter comportamento?!

Pois é, na verdade esta característica do Extension Methods já é conhecida de alguns artigos anteriores a este¹ e, é realmente algo interessante de se ver funcionando. Em uma explicação resumida, eis o que está acontecendo por trás das cortinas do framework: Durante o processo de compilação (na verdade, se você está usando o VisualStudio isto deve acontecer durante a escrita do código sem que você tenha que pedir explicitamente), o compilador procura por uma assinatura que faça sentido para a chamada que você está pretendendo realizar e, na geração da IL ele simplesmente substitui a chamada que você está realizando para um dado tipo e a coloca como uma referência direta para o EM passando seu tipo como primeiro parâmetro.

Acho que com exemplos tudo fica mais fácil de entender… Digamos que definindo nossa interface, temos o seguinte código:

public interface IInterface { void Do(); }
public class MinhaClasse
{
  public MinhaClasse()
  {
    IInterface qualquerCoisa = null;
    qualquerCoisa.Do();
  }
}
public static class IInterfaceExtensions
{
   public static void Do(this IInterface @this) { Console.Write("Opa!"); }
}

Repare que ao contrário do código anterior, agora nossa interface realmente tem um método chamado “Do” em sua definição! Se compilarmos este código veremos que a IL da chamada ao “Do” ficou da seguinte forma:

IL_000b:  callvirt   instance void Diffy.IInterface::Do()

Claramente, a chamada está sendo realizada utilizando o que quer que esteja referenciado pela interface, diretamente e sem rodeios. Agora, tente rodar a aplicação e você verá a boa e velha mensagem “Object reference not set to an instance of an object.”. Agora vamos voltar ao primeiro código onde nossa interface era a coisa mais feia do mundo e simplesmente não definia método algum e, após compilar o código veremos que a IL da chamado ao “Do” agora é a seguinte:

IL_000b:  call       void Diffy.IInterfaceExtensions::Do(class Diffy.IInterface)

Wow, right?! Sim, caro companheiro, o compilador agora, na falta de uma correspondência adequada (na verdade baseado nas prioridades de execução relacionadas aos EM¹) trocou a referência do método que deve ser executado e está apontando diretamente para o “Do” definido dentro da classe de extensões. De uma forma mais clara, a chamada se tornou isto (equivalente em C#):

   IInterfaceExtensions.Do(qualquerCoisa);

De volta ao problema

Agora que sabemos um pouco mais sobre o que pode ser feito com EM, podemos prosseguir com tranqüilidade em busca de uma solução que além de nos permitir adicionar comportamento a objetos que não nos pertencem de uma forma que certamente não irá afeta-los de forma negativa. Pensando de uma forma simplista podemos dizer que o problema está praticamente resolvido visto que, independentemente do objeto podemos enxertar comportamento no mesmo, permitindo que a feature de comparação seja implementada (seja usando reflection ou com implementações manuais), certo? Bem… Certo até um dado ponto. A questão é que conforme disse no começo do post, não conheço todos os objetos que terei que implementar esta feature e, pior que isto, mesmo que consiga uma lista com todos os tipos, estes tipos estão em constante mudança e novos tipos são criados com uma certa freqüência então preciso de algo que sirva para ambos os casos: tipos conhecidos e “desconhecidos”.

É aí que podemos fazer uso da inteligência do compilador e da força que as extensões de interface nos permitem (mesmo eu ainda tendo muito medo disto).

Imagine que o responsável por manter os objetos que serão comparados tenha muito receito (por uma série de motivos que você concorda e não tem como discutir) em realizar alterações nos mesmos, logo, não existe chances de adicionar qualquer comportamento extra a estes objetos para cumprir sua tarefa. É importante mencionar isto pois, dada esta realidade estamos impossibilitados de, por exemplo, fazer uso de uma interface com métodos de comparação em sua definição e, tão pouco, de uma classe abstrata (base) onde nós mesmo realizamos uma implementação de comparação genérica (por reflection, por exemplo) por conta da limitação do framework frente à herança múltipla (o que poderia criar um conflito com o ecossistema de classes atual). Esta última opção também esbarraria em nosso requisito de performance extrema. Por sorte, no nosso caso, nossa prancheta já tem um projeto pronto para ser colocado em prática.

Imagine você que poderíamos propor então a adição de uma interface vazia em todas suas entidades (leia-se marker interefaces°). Ok, deixando o purismo de lado de que uma interface vazia não representa absolutamente nada, não existe nenhum problema e/ou risco em fazer esta alteração, certo? Sei que você já entendeu aonde quero chegar com esta história toda.

Ao conseguirmos estabelecer uma base para indicar ao compilador quais aos objetos que poderão ser comparados por meio de um EM, já estamos com mais de 50% do caminho andado. Apenas com esta solução já poderíamos fazer implementar uma função genérica de comparação usando reflection porém, neste caso estamos pensando muito em performance, precisariamos de algo um pouco mais assertivo.

Prototipando a solução

Digamos que definimos a seguinte interface como sendo nosso ponto de liga entre os objetos do sistema que serão comparados e nossas EM:

   public interface ICanCompare { }

Baseado neste cara, escrevemos a seguinte EM:

public static class ExtensionsFor_ICanCompare
      {
            public static Dictionary<string, object>CompareTo(this ICanCompare @this, ICanCompare to)
            {
                  /// Implementação de Reflection
                  return null; /// Retorna o dicionário com as diferenças
            }
      }

Ok, mas imagine que sabemos que um dado objeto chamado “ObjetoA” é bem simples e não faria sentido pagar o preço (em termos de tempo de execução) de um reflection para ler seus membros. Neste caso, podemos fazer uso do poder do compilador para escrever um overload para a interface “ICanCompare” permitindo que para chamadas de comparação do objeto “ObjetoA”, outro modo de comparação seja utilizado, como podemos ver no snippet a seguir:

public static Dictionary<string, object> CompareTo(this ICanCompare @this, ObjetoA to)
{
      /// Implementação das comparações.. EX:
      /// Dictionary<string, object> dic = new Dictionary<string, object>();
      /// if(!@this.Propriedade == to.Propriedade){ dic.Add(“Propriedade”, to.Propriedade); }
      return null; /// dic;
}

Fácil, certo?! Agora você pode incrementar a brincadeira com coisas mais elegantes como por exemplo utilizar um esquema de mapeamento das propriedades para evitar que uma eventual mudança na nomenclatura dos objetos quebre seu código (o que certamente acontecerá se você fizer uso de strings no seu dicionário). Apenas a título de bônus, segue um esquema bem simples de mapeamento que você poderia utilizar para realizar este processo:

public class ComparableContainerFor<TObject> where TObject : ICanCompare
      {
            public TObject PreviousState { get; private set; }
            public Dictionary<string, object> FinalState { get; private set; }
            private ComparableContainerFor(TObject previousState)
            {
                  this.PreviousState = previousState;
                  this.FinalState = new Dictionary<string, object>();
            }
            public static ComparableContainerFor<TObject> Compare(TObject previousState)
            {
                  return new ComparableContainerFor<TObject>(previousState);
            }
            internal ComparableContainerFor<TObject> CastTo<TReal>() where TReal : TObject
            {
                  return new ComparableContainerFor<TObject>(this.PreviousState as TReal);
            }
            internal ComparableContainerFor<TObject> CompareFieldValue<TProperty>(Expression<Func<TObject, TProperty>> oldValueExpression, TProperty currentValue)
            {
                  if (currentValue != null)
                  {
                        TProperty oldValue = oldValueExpression.Compile()(this.PreviousState);
                        if (!object.Equals(oldValue, currentValue))
                        {
                              this.FinalState.Add(GetMemberExpression(oldValueExpression).Member.Name, currentValue);
                        }
                  }
                  return this;
            }
private MemberExpression GetMemberExpression<TPropertyFrom>(Expression<Func<TObject, TPropertyFrom>> expression)
            {
                  MemberExpression memberExpression = null;
                  if (expression.Body.NodeType == ExpressionType.Convert)
                  {
                        var body = (UnaryExpression)expression.Body;
                        memberExpression = body.Operand as MemberExpression;
                  }
                  else if (expression.Body.NodeType == ExpressionType.MemberAccess)
                  {
                        memberExpression = expression.Body as MemberExpression;
                  }
                  if (memberExpression == null) { throw new ArgumentException("Not a member access"); }
                  return memberExpression;
            }
      }

O uso deste cara mudaria um pouco a cara dos nossos EM para algo parecido com o seguinte:

      public static ComparableContainerFor<ObjetoB> To (this ComparableContainerFor<ICanCompare> @this, ObjetoB currentState)
      {
            return @this
                  .CastTo<ObjetoB>()
                  .CompareFieldValue(old => old.MyProperty, currentState.MyProperty)
                  .CompareFieldValue(old => old.MyProperty1, currentState.MyProperty1);
      }

E o uso do comparador seria algo como:

      ObjetoA objetoABase = new ObjetoA { MyProperty = 1 };
      ObjetoA objetoANew = new ObjetoA { MyProperty = 3 };
      var containerA = ComparableContainerFor<ICanCompare>.Compare(objetoABase).To(objetoANew);

¹ – Alguns bons exemplos de interfaces e EM, além da definição da ordem de priorização de uso de uma EM sobre os membros do objeto em questão – Mais informações

² – Infelizmente sobrescrever o método Equals não serviria para o nosso caso pois precisamos saber exatamente quais foram as diferenças encontradas durante a comparação das entidades. Idealmente a implementação de um override de Equals pode interromper a comparação (com short-circuit) já na primeira divergência e não precisa anotar nada que eventualmente tenha sido discrepante entre as instâncias (que inclusive, segundo a definição do método, podem ser de objetos com tipos distintos).

° – Mais sobre marker interfaces aqui e aqui.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: