.NET dinâmico

Compreendendo a palavra-chave "dynamic" no C# 4

Alexandra Rusina

A palavra-chave dynamic e o DLR (Dynamic Language Runtime, Tempo de execução de linguagem dinâmica) são os novos principais recursos do C# 4 e do Microsoft .NET Framework 4. Esses recursos geraram muito interesse quando foram anunciados e também uma grande quantidade de perguntas. Também há muitas respostas, mas elas estão espalhadas pela documentação e em vários blogs e artigos técnicos. Portanto, com frequência, as pessoas continuam fazendo as mesmas perguntas em fóruns e conferências.

Este artigo fornece uma visão geral dos novos recursos dinâmicos no C# 4 e também fornece mais informações sobre como eles funcionam com recursos de outras linguagens e estruturas, como reflexão e variáveis de tipo implícito. Como já há muitas informações disponíveis, vou reutilizar exemplos clássicos com links para as fontes originais. Também fornecerei uma grande quantidade de links para leitura adicional.

O que é o recurso dinâmico?

Algumas vezes, as linguagens de programação são divididas em linguagens de tipo estático e linguagens de tipo dinâmico. O C# e o Java sempre são considerados como exemplos de linguagens de tipo estático, enquanto o Python, o Ruby e o JavaScript são exemplos de linguagens de tipo dinâmico.

Em termos gerais, as linguagens dinâmicas não executam verificações do tipo tempo de compilação e identificam o tipo de objetos apenas no tempo de execução. Essa abordagem tem seus prós e contras: Normalmente, o código é muito mais rápido e fácil de criar, mas ao mesmo tempo você não obtém erros do compilador e precisa usar teste de unidade e outras técnicas para garantir o comportamento correto de seu aplicativo.

Originalmente, o C# foi criado como uma linguagem puramente estática, mas com o C# 4, foram adicionados elementos dinâmicos para melhorar a interoperabilidade com linguagens e estruturas dinâmicas. A equipe do C# considerou várias opções, mas finalmente decidiu adicionar uma nova palavra-chave para dar suporte a esses recursos: dynamic.

A palavra-chave dynamic funciona como uma declaração de tipo estático no sistema de tipo C#. Dessa maneira, o C# obteve os recursos dinâmicos e ao mesmo tempo continuou sendo uma linguagem de tipo estático. A apresentação “Associação dinâmica no C# 4” de Mads Torgersen na PDC09 (microsoftpdc.com/2009/FT31) explica porque e como essa decisão foi tomada. Entre outras coisas, foi decidido que objetos dinâmicos devem ser cidadãos de primeira classe da linguagem C#, portanto, não há uma opção para ativar ou desativar recursos dinâmicos, e nada semelhante a Option Strict On/Off do Visual Basic foi adicionado ao C#.

Ao usar a palavra-chave dynamic, você instrui o compilador a desativar a verificação em tempo de compilação. Há vários exemplos na Web e na documentação do MSDN (msdn.microsoft.com/library/dd264736) sobre como usar essa palavra-chave. Um exemplo comum é semelhante a este:

dynamic d = "test";
Console.WriteLine(d.GetType());
// Prints "System.String".

d = 100;
Console.WriteLine(d.GetType());
// Prints "System.Int32".

Como você pode ver, é possível atribuir objetos de diferentes tipos a uma variável declarada como dinâmica. O código é compilado e o tipo de objeto é identificado em tempo de execução. No entanto, esse código também é compilado, mas gera uma exceção em tempo de execução:

dynamic d = "test";

// The following line throws an exception at run time.
d++;

O motivo é o mesmo: O compilador não conhece o tipo do tempo de execução do objeto e, portanto, não pode informar a você que a operação de incremento não tem suporte nesse caso.

A ausência de verificação do tipo de tempo de compilação resulta na ausência do IntelliSense também. Como não conhece o tipo do objeto, o C# não pode enumerar suas propriedades e métodos. Esse problema pode ser solucionado com inferência de tipo adicional, como é feito nas ferramentas do IronPython para Visual Studio, mas por enquanto o C# não fornece isso.

No entanto, em muitos cenários que podem se beneficiar de recursos dinâmicos, o IntelliSense não estava disponível de qualquer maneira porque o código usava literais de cadeia de caracteres. Esse problema será abordado em mais detalhes posteriormente neste artigo.

Dynamic, Object ou Var?

Qual a diferença real entre dynamic, object e var, e quando você deve usá-los? Estas são definições breves de cada palavra-chave e alguns exemplos.

A palavra-chave object representa o tipo System.Object, que é o tipo de raiz na hierarquia de classes do C#. Essa palavra-chave normalmente é usada quando não há como identificar o tipo de objeto em tempo de compilação, o que ocorre com frequência em vários cenários de interoperabilidade.

É necessário usar conversões para converter uma variável declarada como um objeto em um tipo específico:

object objExample = 10;
Console.WriteLine(objExample.GetType());

Obviamente, isso imprime System.Int32. No entanto, o tipo estático é System.Object, portanto, você precisa de uma conversão explícita aqui:

objExample = (int)objExample + 10;

É possível atribuir valores de diferentes tipos porque todos eles herdam de System.Object:

objExample = "test";

A palavra-chave var, desde o C# 3.0, é usada para variáveis locais de tipo implícito e para tipos anônimos. Frequentemente, essa palavra-chave é usada com LINQ. Quando uma variável é declarada com o uso da palavra-chave var, o tipo da palavra-chave é inferido da cadeia de caracteres de inicialização em tempo de compilação. O tipo da variável não pode ser alterado em tempo de execução. Se não puder inferir o tipo, o compilador produzirá um erro de compilação:

var varExample = 10;
Console.WriteLine(varExample.GetType());

Isso imprime System.Int32, e é igual ao tipo estático.

No exemplo a seguir, nenhuma conversão é necessária porque o tipo estático de varExample é System.Int32:

varExample = varExample + 10;

Essa linha não é compilada porque você pode atribuir apenas inteiros a varExample:

varExample = "test";

A palavra-chave dynamic, introduzida no C# 4, facilita a criação e a manutenção de certos cenários que tradicionalmente contavam com a palavra-chave object. Na verdade, o tipo dynamic usa o tipo System.Object nos bastidores, mas, ao contrário de object, não requer operações explícitas de conversão em tempo de compilação porque identifica o tipo apenas em tempo de execução:

dynamic dynamicExample = 10;
Console.WriteLine(dynamicExample.GetType());

Isso imprime System.Int32.

Na linha a seguir, nenhuma conversão é necessária, porque o tipo é identificado apenas em tempo de execução:

dynamicExample = dynamicExample + 10;

É possível atribuir valores de diferentes tipos a dynamicExample:

dynamicExample = "test";

Há uma postagem detalhada sobre diferenças das palavras-chave object e dynamic no blog de Perguntas frequentes do C# (bit.ly/c95hpl).

O que, algumas vezes, provoca confusão é que todas essas palavras-chave podem ser usadas em conjunto, elas não são mutuamente exclusivas. Por exemplo, vamos examinar o código a seguir:

dynamic dynamicObject = new Object();
var anotherObject = dynamicObject;

Qual é o tipo de anotherObject? A resposta é: dynamic. Lembre-se de que dynamic, na verdade, é um tipo estático no sistema de tipo C#, portanto, o compilador infere esse tipo para anotherObject. É importante compreender que a palavra-chave var é apenas uma instrução para o compilador inferir o tipo da expressão de inicialização da variável. Var não é um tipo.

O tempo de execução da linguagem dinâmica

Quando você ouve o termo “dinâmico” em relação à linguagem C#, ele normalmente se refere a um ou dois conceitos: a palavra-chave dynamic no C# 4 ou o DLR. Embora esses dois conceitos estejam relacionados, é importante compreender a diferença também.

O DLR atende a duas metas principais. Em primeiro lugar, ele permite interoperação entre linguagens dinâmicas e o .NET Framework. Em segundo lugar, ele introduz comportamento dinâmico no C# e no Visual Basic.

O DLR foi criado com base em lições aprendidas ao criar o IronPython (ironpython.net), que foi a primeira linguagem dinâmica implementada no.NET Framework. Ao trabalhar no IronPython, a equipe descobriu que podia reutilizar sua implementação para mais de uma linguagem, portanto, criou uma plataforma subjacente comum para linguagens dinâmicas .NET. Como o IronPython, o DLR se tornou um projeto de software livre e seu código-fonte agora está disponível em dlr.codeplex.com.

Posteriormente, o DLR também foi incluído no.NET Framework 4 para dar suporte a recursos dinâmicos no C# e no Visual Basic. Se precisar apenas da palavra-chave dynamic no C# 4, você poderá simplesmente usar o .NET Framework e, na maioria dos casos, ele tratará todas as interações com o DLR por conta própria. Mas se desejar implementar ou portar uma nova linguagem dinâmica para o .NET, você poderá se beneficiar das classes extras do auxiliar no projeto de software livre, que têm mais recursos e serviços para implementadores de linguagens.

Usando dynamic em uma linguagem de tipo estático

Não é esperado que todos usem dynamic sempre que possível em vez de declarações de tipo estático. A verificação em tempo de compilação é um instrumento poderoso e, quanto mais benefícios for possível obter dela, melhor. E, novamente, objetos dinâmicos no C# não dão suporte ao IntelliSense, o que pode ter um certo impacto na produtividade geral.

Ao mesmo tempo, há cenários que eram difíceis de serem implementados no C# antes da palavra-chave dynamic e do DLR. Na maioria dos casos, eles usavam o tipo System.Object e a conversão explícita, e não podiam obter muitos benefícios da verificação em tempo de compilação e do IntelliSense de qualquer maneira. Estes são alguns exemplos.

O cenário mais evidente é quando você precisa usar a palavra-chave object para interoperabilidade com outras linguagens ou estruturas. Normalmente, você precisa contar com a reflexão para obter o tipo do objeto e acessar suas propriedades e métodos. Algumas vezes, a sintaxe é difícil de ler e, consequentemente, o código é difícil de manter. O uso da palavra-chave dynamic aqui pode ser muito mais fácil e mais conveniente para reflexão.

Anders Hejlsberg deu um ótimo exemplo no PDC08 (channel9.msdn.com/pdc2008/TL16), que é semelhante ao seguinte:

object calc = GetCalculator();
Type calcType = calc.GetType();
object res = calcType.InvokeMember(
  "Add", BindingFlags.InvokeMethod, 
  null, new object[] { 10, 20 });
int sum = Convert.ToInt32(res);

A função retorna uma calculadora, mas o sistema não sabe o tipo exato desse objeto de calculadora em tempo de compilação. A única coisa com a qual o código conta é que esse objeto deve ter o método Add. Observe que você não obtém o IntelliSense para esse método porque você fornece o nome como uma literal de cadeia de caracteres.

Com a palavra-chave dynamic, esse código é tão simples quanto o seguinte:

dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

As suposições são as mesmas: Há alguns objetos com um tipo desconhecido que esperamos que tenham o método Add. E da mesma forma como no exemplo anterior, você não obtém o IntelliSense para esse método. Mas a sintaxe é muito mais fácil de ler e de usar e é parecida com a chamada de um método .NET típico.

Recipientes de método dinâmico

Outro exemplo onde a palavra-chave dynamic pode ajudar é criar recipientes de métodos dinâmicos, que são objetos que podem adicionar e remover propriedades e métodos em tempo de execução.

O .NET Framework 4 possui um novo namespace: System.Dynamic. Na verdade, esse namespace faz parte do DLR. As classes System.Dynamic.ExpandoObject e System.Expando.DynamicObject em combinação com a nova palavra-chave dynamic pode ajudar você a criar estruturas e hierarquias dinâmicas de uma maneira clara e fácil de ler.

Por exemplo, esta é uma maneira como você pode adicionar uma propriedade e um método por meio da classe ExpandoObject:

dynamic expando = new ExpandoObject();
expando.SampleProperty = 
  "This property was added at run time";
expando.SampleMethod = (Action)(
  () => Console.WriteLine(expando.SampleProperty));
expando.SampleMethod();

Para obter cenários mais profundos, dê uma olhada na documentação do MSDN sobre as classes ExpandoObject e DynamicObject. Também vale a pena ler os artigos ”Recipientes de métodos dinâmicos” de Bill Wagner (msdn.microsoft.com/library/ee658247) e “Recurso dinâmico no C# 4.0: Introdução ao ExpandoObject” no blog de Perguntas frequentes do C# (bit.ly/amRYRw).

Wrappers de classes

É possível fornecer uma sintaxe melhor para sua própria biblioteca ou criar um wrapper para uma biblioteca existente. Esse é um cenário mais avançado quando comparado com os dois anteriores e requer uma compreensão mais profunda das especificações do DLR.

Para casos simples, é possível usar a classe DynamicObject. Nessa classe, você pode misturar declaração estática de métodos e propriedades com expedição dinâmica. Portanto, é possível armazenar um objeto para o qual você deseja fornecer uma sintaxe melhor em uma propriedade de classe, mas tratar todas as operações com esse objeto por meio de uma expedição dinâmica.

Como um exemplo, examine a classe DynamicString na Figura 1 que encapsula uma cadeia de caracteres e exibe os nomes de todos os métodos antes de mesmo de chamar esses métodos por meio de reflexão.

Figura 1 DynamicString

public class DynamicString : DynamicObject {
  string str;

  public DynamicString(string str) {
    this.str = str;
  }

  public override bool TryInvokeMember(
    InvokeMemberBinder binder, object[] args, 
    out object result) {

    Console.WriteLine("Calling method: {0}", binder.Name);

    try {
      result = typeof(string).InvokeMember(
        binder.Name,
        BindingFlags.InvokeMethod |
        BindingFlags.Public |
        BindingFlags.Instance,
        null, str, args);
      return true;
    }
    catch {
      result = null;
      return false;
    }
  }
}

Para instanciar essa classe, você deve usar a palavra-chave dynamic:

dynamic dStr = new DynamicString("Test");
Console.WriteLine(dStr.ToUpper()); 
Console.ReadLine();

Naturalmente, esse exemplo específico é forçado e não é realmente eficiente. Mas se tiver uma API que já conte intensamente com reflexão, você poderá encapsular todas as chamadas por meio de reflexão, conforme mostrado aqui, para que os usuários finais de sua API não as vejam.

Para obter mais exemplos, consulte a documentação do MSDN (msdn.microsoft.com/library/system.dynamic.dynamicobject) e o “Recurso dinâmico no C# 4.0: Criando wrappers com DynamicObject” no blog de Perguntas frequentes do C# (bit.ly/dgS3od).

Como mencionei, a classe DynamicObject fornecida pelo DLR. DynamicObject ou ExpandoObject é tudo o que você precisa para produzir um objeto dinâmico. No entanto, alguns objetos dinâmicos têm uma lógica de associação complicada para acessar membros ou invocar métodos. Esses objetos precisam implementar a interface IDynamicMetaObjectProvider e fornecer sua própria expedição dinâmica. Esse é um cenário avançado e os interessados podem ler o artigo “Implementando interfaces dinâmicas” de Bill Wagner (msdn.microsoft.com/vcsharp/ff800651) e “Guia de introdução ao DLR como um autor de biblioteca” de Alex Turner e Bill Chiles (dlr.codeplex.com).

Aplicativos passíveis de script

Scripts são uma maneira avançada de fornecer extensibilidade para seu aplicativo. O Microsoft Office pode funcionar como um bom exemplo aqui: várias macros, complementos e plug-ins existem devido ao Visual Basic for Applications (VBA). E agora o DLR permite que você crie aplicativos passíveis de script porque fornece um conjunto comum de APIs de hospedagem para linguagens.

Por exemplo, você pode criar um aplicativo onde os próprios usuários podem adicionar funcionalidade sem solicitar novos recursos do produto principal, como adicionar novos caracteres e mapas a um jogo ou novos gráficos a um aplicativo de negócios.

É necessário usar a versão de software livre do DLR no dlr.codeplex.com em vez da usada pelo .NET Framework 4 porque, no momento, as APIs de script e de hospedagem estão disponíveis apenas na versão de software livre. Além disso, supõe-se que você não criará scripts por meio do C#, mas sim por meio das linguagens dinâmicas do .NET, como o IronPython ou o IronRuby. No entanto, qualquer linguagem pode dar suporte a essas APIs, mesmo uma que não seja implementada além do DLR.

Para obter detalhes sobre como usar essa funcionalidade, assista à apresentação “Usando linguagens dinâmicas para criar aplicativos passíveis de script” de Dino Viehland no PDC09 (microsoftpdc.com/2009/FT30).

Identificando objetos dinâmicos

Como você pode distinguir objetos dinâmicos de outros objetos? Uma maneira fácil é usar recursos IDE internos. Você pode focalizar o cursor do mouse sobre o objeto para ver o tipo de sua declaração ou verificar se o IntelliSense está disponível (consulte a Figura 2).

Figura 2 Objeto dinâmico no Visual Studio

Em tempo de execução, as coisas se tornam mais complicadas. Você não pode verificar se a variável foi declarada pela palavra-chave dynamic — o tipo do tempo de execução do objeto dinâmico é o tipo do valor que ele armazena e você não pode obter sua declaração de tipo estático. É a mesma coisa que você declarar sua variável como objeto: Em tempo de execução, você pode obter apenas um tipo do valor que a variável mantém. Você não pode saber se essa variável foi declarada originalmente como objeto.

O que você pode identificar em tempo de execução é se um objeto está vindo do DLR. Isso pode ser importante porque objetos de tipos como ExpandoObject e DynamicObject podem alterar seu comportamento em tempo de execução — por exemplo, adicionar e excluir propriedades e métodos.

Além disso, você não pode usar métodos de reflexão padrão para obter informações sobre esses objetos. Se você adicionar uma propriedade a uma instância da classe ExpandoObject, você não poderá obter essa propriedade a partir de reflexão:

dynamic expando = new ExpandoObject();
expando.SampleProperty = "This property was added at run time";
PropertyInfo dynamicProperty = 
  expando.GetType().GetProperty("SampleProperty");
// dynamicProperty is null.

O lado positivo é que, no .NET Framework 4, todos os objetos que podem adicionar e remover membros dinamicamente devem implementar uma interface específica: System.Dynamic.IDynamicMetaObjectProvider. As classes DynamicObject e ExpandoObject também implementam essa interface. No entanto, isso não significa que qualquer objeto declarado com o uso da palavra-chave dynamix implemente essa interface:

dynamic expando = new ExpandoObject();
Console.WriteLine(expando is IDynamicMetaObjectProvider);
// True

dynamic test = "test";
Console.WriteLine(test is IDynamicMetaObjectProvider);
// False

Portanto, se você estiver usando a palavra-chave dynamic com reflexão, lembre-se de que a reflexão não funciona para propriedades e métodos adicionados dinamicamente, e pode ser uma boa ideia verificar se o objeto no qual você está refletindo implementa a interface IDynamicMetaObjectProvider.

O recurso dinâmico e a interoperabilidade COM

O cenário de interoperabilidade COM que a equipe do C# focalizou especificamente no C# versão 4 foi a programação em relação a aplicativos do Microsoft Office, como o Word e o Excel. O objetivo era tornar essa tarefa tão fácil e natural no C# como sempre foi no Visual Basic. Isso também faz parte da estratégia de coevolução do Visual Basic e do C#, onde as duas linguagens focalizam a paridade do recurso e emprestam as melhores e mais produtivas soluções uma da outra.

Se estiver interessado nos detalhes, leia o artigo “Coevolução do C# e do VB” no blog do Visual Studio de Scott Wiltamuth (bit.ly/bFUpxG).

A Figura 3 mostra o código do C# 4 que adiciona um valor à primeira célula da planilha do Excel e, em seguida, aplica o método AutoFit à primeira coluna. Os comentários sob cada linha mostram equivalentes do C# 3.0 e anterior.

Figura 3 Script do Excel com o C#

// Add this line to the beginning of the file:
// using Excel = Microsoft.Office.Interop.Excel;

var excelApp = new Excel.Application();

excelApp.Workbooks.Add();
// excelApp.Workbooks.Add(Type.Missing);

excelApp.Visible = true;

Excel.Range targetRange = excelApp.Range["A1"];
// Excel.Range targetRange = excelApp.get_Range("A1", Type.Missing);

targetRange.Value = "Name";
// targetRange.set_Value(Type.Missing, "Name");

targetRange.Columns[1].AutoFit();
// ((Excel.Range)targetRange.Columns[1, Type.Missing]).AutoFit();

O mais interessante nesse exemplo é que você não pode ver a palavra-chave dynamic em nenhum lugar do código. Na verdade, ela é usada em apenas uma linha aqui:

targetRange.Columns[1].AutoFit();
// ((Excel.Range)targetRange.Columns[1, Type.Missing]).AutoFit();

No C# versão 3.0, targetRange.Columns[1, Type.Missing] retorna objeto, e é por isso que a conversão em Excel.Range é necessária. Mas no C# 4 e no Visual Studio 2010 essas chamadas são convertidas silenciosamente em chamadas dinâmicas. Portanto, o tipo de targetRange.Columns[1] no C# 4 é realmente dinâmico.

Outro destaque é que as melhorias da interoperabilidade COM no C# 4 não são apenas sobre o recurso dinâmico. Em todas as outras linhas, uma sintaxe melhor é obtida devido a outros novos recursos, como propriedades indexadas e parâmetros nomeados e opcionais. É possível localizar uma boa visão geral desses novos recursos no artigo da MSDN Magazine “Novos recursos do C# no.NET Framework 4” de Chris Burrows (msdn.microsoft.com/magazine/ff796223).

Onde posso obter mais informações?

Espero que este artigo tenha respondido a maior parte de suas perguntas sobre a palavra-chave dynamic no C# 4, mas tenho certeza de que algumas continuam sem resposta. Se você tiver comentários, dúvidas ou sugestões, visite dlr.codeplex.com/discussions e faça suas perguntas. Alguém já deve ter feito uma pergunta sobre o problema ou você pode criar uma nova discussão. Temos uma comunidade ativa e damos boas-vindas a novos membros.

Alexandra Rusina é gerente de programa da equipe do Silverlight. Anteriormente, ela trabalhava como escritora técnica de programação da equipe de Linguagens do Visual durante o lançamento do Visual Studio 2010. Ela também publica regularmente no blog de Perguntas frequentes do C# (blogs.msdn.com/b/csharpfaq/).

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Bill Chiles