Prefácio do tradutor
Isso é mais uma recontagem gratuita, não uma tradução. Incluí neste artigo apenas as partes do original que estão diretamente relacionadas aos mecanismos internos do DLR ou explicam idéias importantes. As notas serão colocadas entre colchetes.Muitos desenvolvedores .NET ouviram falar sobre o Dynamic Language Runtime (DLR), mas não sabem quase nada sobre isso. Os desenvolvedores que escrevem em linguagens como C # ou Visual Basic evitam linguagens de digitação dinâmicas por medo de problemas de escalabilidade historicamente relacionados. Eles também estão preocupados com o fato de que linguagens como Python ou Ruby não executam verificação de tipo em tempo de compilação, o que pode levar a erros de tempo de execução difíceis de encontrar e corrigir. Esses são medos bem fundamentados que podem explicar por que o DLR não é popular entre a maioria dos desenvolvedores .NET, mesmo dois anos após o lançamento oficial
[o artigo é bastante antigo, mas nada mudou desde então] . Afinal, qualquer .NET
Runtime que contenha as palavras
Dynamic e
Language em seu nome deve ser projetado estritamente para suportar idiomas como Python, certo?
Desacelere. Embora o DLR tenha sido realmente projetado para suportar a implementação Iron do Python e Ruby no .NET Framework, sua arquitetura fornece abstrações muito mais profundas.

Sob o capô, o DLR oferece um rico conjunto de interfaces para comunicação entre processos [Comunicação entre processos (IPC)]. Ao longo dos anos, os desenvolvedores viram muitas ferramentas da Microsoft para interação entre aplicativos: DDE, DCOM, ActiveX, .Net Remoting, WCF, OData. Essa lista pode continuar por muito tempo. Este é um desfile quase infinito de acrônimos, cada um dos quais representa uma tecnologia que promete que este ano será ainda mais fácil trocar dados ou chamar código remoto do que antes.
Língua das línguas
A primeira vez que ouvi Jim Hugunin falar sobre DLR, seu discurso me surpreendeu. Jim criou uma implementação Python para a Java Virtual Machine (JVM) conhecida como Jython. Pouco antes do show, ele se juntou à Microsoft para criar o IronPython for .NET. Com base em sua experiência, eu esperava que ele se concentrasse na linguagem, mas Jim falava quase o tempo todo sobre coisas abstrusas, como árvores de expressão, envio dinâmico de chamadas e mecanismos de cache de chamadas. Jim descreveu um conjunto de serviços de compilação em tempo de execução que permitia a interação de dois idiomas, praticamente sem perda de desempenho.
Durante esse discurso, escrevi um termo que surgiu na minha cabeça quando ouvi Jim recontando a arquitetura DLR: a linguagem das línguas. Quatro anos depois, esse apelido ainda caracteriza o DLR com muita precisão. No entanto, tendo adquirido uma experiência de uso no mundo real, percebi que o DLR não se refere apenas à compatibilidade de idiomas. Graças ao suporte de tipos dinâmicos em C # e Visual Basic, o DLR pode atuar como um gateway das nossas linguagens .NET favoritas para dados e códigos em qualquer sistema remoto, independentemente do tipo de equipamento ou software usado por este último.

Para entender a idéia por trás do DLR, que é um mecanismo integrado na linguagem IPC, vamos começar com um exemplo que não tem nada a ver com programação dinâmica. Imagine dois sistemas de computador: um chamado iniciador e o segundo - o sistema de destino. O iniciador precisa executar a função
foo no sistema de destino, passando para lá um determinado conjunto de parâmetros e obter os resultados. Após a descoberta do sistema de destino, o iniciador deve fornecer todas as informações necessárias para a execução da função em um formato que seja compreensível para ela. No mínimo, essas informações incluirão o nome da função e os parâmetros passados. Após descompactar a solicitação e validar os parâmetros, o sistema de destino executará a função foo. Depois disso, ele deve compactar o resultado, incluindo os erros que ocorreram durante a execução, e enviá-los de volta ao iniciador. Por fim, o iniciador deve ser capaz de descompactar os resultados e notificar a meta. Esse padrão de solicitação-resposta é bastante comum e, em um nível alto, descreve a operação de quase qualquer mecanismo IPC.
Dynamicmetaobject
Para entender como o DLR implementa o padrão apresentado, vejamos uma das classes centrais do DLR:
DynamicMetaObject . Começamos explorando três dos doze métodos principais desse tipo:
- BindCreateInstance - cria ou ativa um objeto
- BindInvokeMember - chame o método encapsulado
- BindInvoke - execução de objeto (como uma função)
Quando você precisa executar um método em um sistema remoto, primeiro precisa criar uma instância do tipo. Obviamente, nem todos os sistemas são orientados a objetos; portanto, o termo "instância" pode ser uma metáfora. De fato, o serviço que precisamos pode ser implementado como um pool de objetos ou como um singleton, para que os termos "ativação" ou "conexão" possam ser usados com o mesmo direito que "instância".
Outras estruturas seguem o mesmo padrão. Por exemplo, o COM fornece uma função
CoCreateInstance para criar objetos. No .NET Remoting, você pode usar o método
CreateInstance da classe
System.Activator . O DLR
DynamicMetaObject fornece um
BindCreateInstance para fins semelhantes.
Depois de usar o método
BindCreateInstance ,
algo criado pode ser um tipo que expõe vários métodos. O
método de metaobjeto
BindInvokeMember é usado para ligar uma operação que pode chamar uma função. Na figura acima, a string foo pode ser passada como um parâmetro para indicar ao fichário que um método com esse nome deve ser chamado. Além disso, estão incluídas informações sobre o número de argumentos, seus nomes e um sinalizador especial que indica ao fichário se é possível ignorar maiúsculas e minúsculas ao procurar um elemento nomeado adequado. Afinal, nem todos os idiomas diferenciam maiúsculas de minúsculas.
Quando
algo retornado de
BindCreateInstance é apenas uma função (ou delegado), o método BindInvoke é usado. Para esclarecer a figura, vejamos o pequeno pedaço de código dinâmico a seguir:
delegate void IntWriter(int n); void Main() { dynamic Write = new IntWriter(Console.WriteLine); Write(5); }
Este código não é a melhor maneira de imprimir o número 5 no console. Um bom desenvolvedor nunca usará algo tão inútil. No entanto, esse código ilustra o uso de uma variável dinâmica cujo valor é um delegado que pode ser usado como uma função. Se o tipo de delegado implementar a interface
IDynamicMetaObjectProvider , o método
BindInvoke de
DynamicMetaObject será usado para vincular a operação ao trabalho real. Isso ocorre porque o compilador reconhece que o objeto de
gravação dinâmico é usado
sintaticamente como uma função. Agora considere outro trecho de código para entender quando o compilador irá gerar
BindInvokeMember :
class Writer : IDynamicMetaObjectProvider { public void Write(int n) { Console.WriteLine(n); }
Omitirei a implementação da interface neste pequeno exemplo, porque será necessário muito código para demonstrar isso corretamente. Neste exemplo resumido, implementamos um meta-objeto dinâmico com apenas algumas linhas de código.
Uma coisa importante a entender é que o compilador reconhece que
Writer.Write (7) é uma operação de acesso ao elemento. O que geralmente chamamos de "operador de ponto" em C # é formalmente chamado de "operador de acesso de membro de tipo". O código DLR gerado pelo compilador nesse caso acabará chamando
BindInvokeMember , no qual passará a seqüência de gravação e o número do parâmetro 7 para a operação que é capaz de fazer a chamada. Em resumo,
BindInvoke é usado para chamar um objeto dinâmico como uma função, enquanto
BindInvokeMember é usado para chamar um método como um elemento de um objeto dinâmico.
Acesse propriedades através do DynamicMetaObject
Pode ser visto nos exemplos acima que o compilador usa a sintaxe da linguagem para determinar quais operações de ligação do DLR devem ser executadas. Se você usar o Visual Basic para trabalhar com objetos dinâmicos, sua semântica será usada. O operador de acesso (ponto), é claro, é necessário não apenas para acessar métodos. Você pode usá-lo para acessar propriedades. O metaobjeto DLR fornece três métodos para acessar as propriedades de objetos dinâmicos:
- BindGetMember - obtém o valor da propriedade
- BindSetMember - define o valor da propriedade
- BindDeleteMember - excluir um item
O objetivo de
BindGetMember e
BindSetMember deve ser óbvio. Especialmente agora que você sabe como eles se relacionam com o modo como o .NET funciona com propriedades. Quando o compilador calcula as propriedades
get ("read") de um objeto dinâmico, ele usa uma chamada para
BindGetMember . Quando o compilador calcula o conjunto ("registro"), ele usa
BindSetMember .
Representação de um objeto como uma matriz
Algumas classes são contêineres para instâncias de outros tipos. O DLR sabe como lidar com esses casos. Cada método de meta-objeto "orientado a matriz" possui um postfix de "Índice":
- BindGetIndex - obtém valor por índice
- BindSetIndex - defina o valor por índice
- BindDeleteIndex - excluir um valor por índice
Para entender como
BindGetIndex e
BindSetIndex são usados , imagine uma
classe de wrapper
JavaBridge que possa carregar arquivos com classes Java e permita que você os use a partir do código .NET sem dificuldades. Esse wrapper pode ser usado para carregar a classe Java do
Cliente , que contém algum código ORM. O meta-objeto DLR pode ser usado para chamar esse código ORM do .NET no estilo C # clássico. Abaixo está um código de exemplo que mostra como o
JavaBridge pode funcionar na prática:
JavaBridge java = new JavaBridge(); dynamic customers = java.Load("Customer.class"); dynamic Jason = customers["Bock"]; Jason.Balance = 17.34; customers["Wagner"] = new Customer("Bill");
Como a terceira e a quinta linhas usam o operador de acesso pelo índice ([]), o compilador reconhece isso e usa os
métodos BindGetIndex e
BindSetIndex ao trabalhar com o meta objeto retornado do
JavaBridge . Entende-se que a implementação desses métodos no objeto retornado solicitará a execução do método da JVM por meio da Java Remote Method Invocation (RMI). Nesse cenário, o DLR atua como uma ponte entre C # e outro idioma com digitação estática. Espero que isso esclareça por que chamei o DLR de "linguagem das línguas".
O método
BindDeleteMember , assim como
BindDeleteIndex , não se destina ao uso de idiomas com digitação estática como C # e Visual Basic, pois eles não oferecem suporte ao próprio conceito. No entanto, você pode concordar em considerar "remover" alguma operação expressa pelos meios do idioma, se isso for útil para você. Por exemplo, você pode implementar BindDeleteMember como anulando um elemento pelo índice.
Transformações e operadores
O último grupo de métodos de metaobjeto DLR trata de lidar com operadores e transformações.
- BindConvert - converte um objeto para outro tipo
- BindBinaryOperation - usando um operador binário em dois operandos
- BindUnaryOperation - usando um operador unário em um operando
O método
BindConvert é usado quando o compilador percebe que o objeto precisa ser convertido para outro tipo conhecido. A conversão implícita ocorre quando o resultado de uma chamada dinâmica é atribuído a uma variável com um tipo estático. Por exemplo, no exemplo C # a seguir, atribuir a variável
y leva a uma chamada implícita para
BindConvert :
dynamic x = 13; int y = x + 11;
Os
métodos BindBinaryOperation e
BindUnaryOperation são sempre usados quando operações aritméticas ("+") ou incrementos ("++") são encontradas. No exemplo acima, adicionar a variável dinâmica
x à constante 11 chamará o método
BindBinaryOperation . Lembre-se deste pequeno exemplo, nós o usamos na próxima seção para adicionar outra classe DLR chave chamada CallSite.
Envio dinâmico com CallSite
Se sua introdução ao DLR não fosse além do uso da palavra-chave
dinâmica , você provavelmente nunca saberia sobre a existência do CallSite no .NET Framework. Esse tipo modesto, formalmente conhecido como
CallSite < T > , reside no
espaço para nome System.Runtime.CompilerServices . Esta é a "fonte de energia" da metaprogramação: é preenchida com todos os tipos de métodos de otimização que tornam o código .NET dinâmico rápido e eficiente. Mencionarei os aspectos de desempenho do
CallSite < T > no final do artigo.
A maior parte do que o CallSite faz no código .NET dinâmico envolve a geração e compilação de código em tempo de execução. É importante observar que a
classe CallSite < T > está no espaço para nome que contém as palavras "
Runtime " e "
CompilerServices ". Se o DLR é uma "linguagem de idiomas", o
CallSite < T > é uma de suas construções gramaticais mais importantes. Vejamos nosso exemplo da seção anterior novamente para conhecer o CallSite e como o compilador os incorpora em seu código.
dynamic x = 13; int y = x + 11;
Como você já sabe, os métodos
BindBinaryOperaion e
BindConvert serão chamados para executar esse código. Em vez de mostrar uma lista longa do código MSIL desmontado gerado pelo compilador, fiz um diagrama:

Lembre-se de que o compilador usa a sintaxe da linguagem para determinar quais métodos de tipo dinâmico serão executados. Em nosso exemplo, duas operações são executadas: adicionando a variável
x ao número (
Site2 ) e convertendo o resultado em int (
Site1 ). Cada uma dessas ações se transforma em CallSite, que é armazenado em um contêiner especial. Como você pode ver no diagrama, os CallSites são criados na ordem inversa, mas são chamados da maneira correta.
Na figura, você pode ver que os métodos de metaobjeto
BindConvert e
BindBinaryOperation são chamados imediatamente antes das operações "create CallSite1" e "create CallSite2". No entanto, operações vinculadas são executadas apenas no final. Espero que a visualização o ajude a entender que métodos de ligação e chamá-los são operações diferentes no contexto do DLR. Além disso, a ligação ocorre apenas uma vez, enquanto a chamada ocorre quantas vezes for necessária, reutilizando os CallSites já inicializados para otimizar o desempenho.
Siga o caminho mais fácil
No coração do DLR, as árvores de expressão são usadas para gerar funções ligadas aos doze métodos de ligação apresentados acima. Muitos desenvolvedores são constantemente confrontados com árvores de expressão usando o LINQ, mas apenas alguns têm experiência suficiente para implementar completamente o contrato
IDynamicMetaObjectProvider . Felizmente, o .NET Framework contém uma classe base chamada
DynamicObject que cuida da maior parte do trabalho.
Para criar sua própria classe dinâmica, tudo o que você precisa fazer é herdar do
DynamicObject e implementar os doze métodos a seguir:
- TryCreateInstance
- TryInvokeMember
- Tryinvoke
- TryGetMember
- TrySetMember
- TryDeleteMember
- TryGetIndex
- TrySetIndex
- TryDeleteIndex
- Tryconvert
- TryBinaryOperation
- TryUnaryOperation
Os nomes dos métodos parecem familiares? Você deve, porque acabou de estudar os elementos da classe Abstract
DynamicMetaObject , que incluem métodos como
BindCreateInstance e
BindInvoke . A classe
DynamicMetaObject fornece uma implementação para
IDynamicMetaObjectProvider , que retorna um
DynamicMetaObject de seu único método. As operações associadas à implementação base do objeto meta simplesmente delegam suas chamadas aos métodos, começando com "Try" na instância
DynamicObject . Tudo o que você precisa fazer é sobrecarregar métodos como
TryGetMember e
TrySetMember em uma classe herdada do
DynamicObject , enquanto o objeto meta assume todo o trabalho sujo com árvores de expressão.
Armazenamento em cache
[Você pode ler mais sobre armazenamento em cache no meu artigo anterior sobre DLR ]A maior preocupação ao trabalhar com linguagens dinâmicas para desenvolvedores é o desempenho. O DLR toma medidas extraordinárias para dissipar essas experiências.
Mencionei brevemente o fato de que o
CallSite < T > reside em um espaço para nome chamado
System.Runtime.CompilerServices . No mesmo espaço para nome, existem várias outras classes que fornecem armazenamento em cache multinível. Usando esses tipos, o DLR implementa três níveis principais de armazenamento em cache para acelerar operações dinâmicas:
- Cache global
- Cache local
- Cache delegado polimórfico
O cache é usado para evitar desperdício desnecessário de recursos para a criação de ligações para um CallSite específico. Se dois objetos do tipo
string forem passados para um método dinâmico que retorna
int , o cache global ou local salvará a ligação resultante. Isso simplificará bastante as chamadas subseqüentes.
O cache de delegado, que está dentro do próprio CallSite, é chamado polimórfico, porque esses delegados podem assumir diferentes formas, dependendo de qual código dinâmico é executado e de quais regras de outros caches foram usadas para gerá-los. O cache delegado também é chamado de cache embutido. O motivo para usar esse termo é que as expressões geradas pelo DLR e seus ligantes são convertidas em código MSIL que passa pela compilação JIT, como qualquer outro código .NET. A compilação no tempo de execução ocorre simultaneamente com a execução "normal" do seu programa. É claro que a transformação de códigos dinâmicos dinâmicos em código MSIL compilado durante a execução do programa pode afetar bastante o desempenho do aplicativo, portanto, os mecanismos de cache são vitais.