1. Introdução
C# é uma linguagem de tipagem forte. Isso significa que, na etapa de compilação, a variável deve ter um tipo definido, e acessar membros de uma classe só é permitido de acordo com esse tipo. Isso é seguro, conveniente e claro para a IDE e para o compilador.
Mas às vezes precisamos acessar objetos cuja estrutura pode ser desconhecida em tempo de compilação — por exemplo, quando:
- Está sendo executado código escrito em outra linguagem .NET (por exemplo IronPython ou IronRuby), onde a tipagem é dinâmica.
- Ao trabalhar com objetos COM (por exemplo, com Microsoft Office via Interop).
- Ao interagir com estruturas de dados dinâmicas: por exemplo, objetos JSON pouco formalizados.
- Não queremos escrever um monte de código boilerplate, e é mais simples “confiar” no objeto e tentar chamar o método pelo nome.
Com a chegada em C# 4.0 da palavra-chave dynamic e da plataforma DLR, surgiu a possibilidade de escrever código que se comporta “como em Python, só que em C#”: a verificação acontece em tempo de execução, não na compilação. Com essa palavra mágica você pode instruir o compilador a ser menos rígido e só checar se existe o método/propriedade quando o programa realmente chegar naquele ponto.
O que é o DLR?
Dynamic Language Runtime (DLR) é uma extensão infraestrutural ao CLR (Common Language Runtime) que permite implementar suporte a linguagens dinâmicas (e comportamento dinâmico em linguagens estáticas). O DLR assume a execução de operações que não podem ser verificadas pelo compilador: por exemplo, chamada de método por nome quando o tipo só ficará conhecido em runtime. Graças ao DLR, o C# ganha um "modo dinâmico".
2. A palavra-chave dynamic: como ela funciona
Diferença em relação a object
Alguns iniciantes pensam que dynamic é só uma forma mais moderna de escrever object e ficar fazendo casts o tempo todo. Não é isso.
- Uma variável do tipo object sempre exige cast explícito para um tipo concreto para chamar métodos (e o compilador verifica isso).
- Uma variável do tipo dynamic dá uma ordem ao compilador: “confia em mim, eu sei que tipo será — vamos resolver em runtime!”. Se o método não existir — haverá uma exceção em tempo de execução.
Exemplo:
// Exemplo com object
object obj = "Privet, C#!";
// obj.ToUpper(); // Erro de compilação! O compilador não sabe que obj é uma string
string s = ((string)obj).ToUpper(); // Só assim
// Exemplo com dynamic
dynamic dyn = "Privet, C#!";
string upper = dyn.ToUpper(); // O compilador não reclama, a verificação será no runtime
Com dynamic você pode acessar propriedades e métodos inexistentes. Se eles não existirem — será lançada uma exceção do tipo RuntimeBinderException.
Uso num exemplo de aplicação
Suponha que no nosso mini-app exista um bloco que trabalha com dados JSON cuja estrutura pode mudar em tempo de execução. Com dynamic dá para acessar propriedades como se elas sempre existissem:
dynamic user = new System.Dynamic.ExpandoObject();
user.Name = "Vasya";
user.Age = 35;
Console.WriteLine($"Imya: {user.Name}, vozrast: {user.Age}");
3. DLR em ação: o que acontece por baixo do capô
Quando uma variável é declarada como dynamic, o compilador C# registra que qualquer acesso a métodos, propriedades, indexadores, operadores etc. nesse objeto ele não pode verificar antecipadamente.
Em vez disso, em cada chamada dinâmica o compilador insere código que faz uma requisição ao “dispatcher” do sistema. O DLR observa: será que existe mesmo aquele método ou propriedade que estamos acessando de forma despreocupada e otimista? Se sim — executa a chamada; se não — lança exceção.
Esquema visual
+------------------------+
| Código com dynamic |
+----------+-------------+
|
v
+----------+-------------+
| O compilador C# insere |
| "consulta ao DLR" |
+----------+-------------+
|
v
+----------+-------------+
| Em runtime o DLR busca |
| método/propriedade |
+----------+-------------+
|
/---------\
| |
encontrado não encontrado
| |
v v
chamada exceção
do método (RuntimeBinderException)
O DLR cuida de buscar métodos e propriedades no objeto — e até faz cache das chamadas dinâmicas mais usadas!
Um pouco de história
O DLR foi criado para dar suporte a linguagens originalmente dinâmicas: IronPython, IronRuby, e também para facilitar a integração com objetos dinâmicos no C#. Hoje em dia é usado em objetos como ExpandoObject, DynamicObject, além de COM e dados dinamicamente serializados.
4. Comparação: static vs dynamic
Vamos comparar como ficam chamadas de propriedades e métodos nas paradigmas estático e dinâmico:
| Método | Declaração | Verificação em compilação | Comportamento em erro |
|---|---|---|---|
|
|
Exige cast para o tipo certo | Erro de compilação ou InvalidCastException |
|
|
Não, tudo em runtime | RuntimeBinderException, se o método não existe |
| tipo estático | |
Verificado pelo compilador | Erro de compilação |
Exemplo para clarear:
// Tipagem estática
MyUser u = new MyUser();
u.PrintInfo(); // Verificado pelo compilador
// dynamic
dynamic du = new MyUser();
du.PrintInfo(); // O compilador não verifica, encontra o método só em runtime
// object
object ou = new MyUser();
// ou.PrintInfo(); // Erro de compilação
((MyUser)ou).PrintInfo(); // Precisa fazer cast explícito
Métodos estáticos e dynamic
dynamic d = "Hello";
Console.WriteLine(d.Length); // 5 — tudo certo
Console.WriteLine(d.FakeMethod()); // RuntimeBinderException em tempo de execução
Mas cuidado! Se você digitar errado ou o nome do método não existir — isso só vai aparecer em tempo de execução. Exemplo clássico: "bug em produção".
5. Exemplos práticos: dinâmica em tarefas reais
Trabalhando com ExpandoObject
using System.Dynamic;
dynamic person = new ExpandoObject();
person.Name = "Sergey";
person.Greet = (Action)(() => Console.WriteLine($"Privet, {person.Name}!"));
person.Greet(); // Vai imprimir: Privet, Sergey!
Aqui adicionamos em tempo de execução a propriedade Name e até um delegado Greet. Muito parecido com trabalhar com objetos em JavaScript.
Binding a estruturas JSON
using System.Dynamic;
using Newtonsoft.Json;
string json = @"{""name"": ""Andrey"", ""age"": 30}";
dynamic obj = JsonConvert.DeserializeObject
(json); Console.WriteLine(obj.name); // Andrey Console.WriteLine(obj.age); // 30
Repare: se você acessar obj.surname — vai dar erro em tempo de execução.
COM-Interop (Automação do Office)
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true; // "Vemos" a propriedade, mesmo que o compilador não conheça o tipo
6. Nuances úteis
DLR, dynamic e uso com outras linguagens .NET
- IronPython/IronRuby: dá pra instanciar objetos dessas linguagens e chamar métodos a partir do C# via dynamic.
- Objetos COM: wrapper automático de métodos e propriedades via dynamic.
- Objetos dinâmicos customizados: implementando a interface IDynamicMetaObjectProvider, você pode criar sua classe com chamadas dinâmicas.
Exemplo de integração com IronPython
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
ScriptEngine engine = Python.CreateEngine();
dynamic py = engine.Execute("class Test: pass\nTest()");
py.x = 42;
Console.WriteLine(py.x); // 42
Como implementar seus próprios tipos dinâmicos
Geralmente é mais fácil herdar de DynamicObject.
using System.Dynamic;
class MyDynamic : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == "Ping")
{
result = "Pong!";
return true;
}
result = null;
return false;
}
}
dynamic d = new MyDynamic();
Console.WriteLine(d.Ping); // Pong!
Console.WriteLine(d.Miss); // null (não lança exceção)
Quando dynamic faz mal
- Reduz performance (chamadas dinâmicas são mais lentas que estáticas).
- Complica debugging e manutenção.
- Perde a maioria das facilidades da IDE: autocomplete, refactoring, find usages.
- Pode levar a erros que só aparecem para o usuário ou no servidor.
Quando realmente usar dynamic
- Integração com bibliotecas externas onde é impossível ou inviável escrever wrappers (COM, Python, JavaScript).
- Manipular estruturas fracamente tipadas (JSON/XML/ExpandoObject).
- Para escrever frameworks, metaprogramação e APIs flexíveis que devem ser dinâmicas por design.
Em todos os outros casos é melhor usar tipos estáticos e aproveitar a ajuda do compilador :-)
7. Erros típicos ao trabalhar com dynamic e DLR
Erro №1: Acessar membros inexistentes.
Chamar método ou propriedade inexistente em um dynamic leva a RuntimeBinderException em runtime, o que é difícil de pegar durante o desenvolvimento.
Erro №2: Ignorar checagem de tipos.
Sem checar tipos antes de chamar (por exemplo, if (obj is IDictionary<string, object>)) você pode ter erros inesperados em runtime.
Erro №3: Usar dynamic em APIs públicas.
Tipos dinâmicos tornam o código mais difícil de manter, já que IDEs e analisadores não conseguem sugerir membros corretamente, reduzindo a legibilidade.
Erro №4: Abusar de dynamic em código de alta performance.
Chamadas dinâmicas são mais lentas que estáticas e aumentam overhead. Use dynamic só quando necessário.
GO TO FULL VERSION