Diferenças entre ExpandoObject, DynamicObject e dynamic

170

Quais são as diferenças entre System.Dynamic.ExpandoObject,System.Dynamic.DynamicObject e dynamic?

Em quais situações você usa esses tipos?

M4N
fonte

Respostas:

154

A dynamicpalavra-chave é usada para declarar variáveis ​​que devem ser atrasadas.
Se você deseja usar a ligação tardia, para qualquer tipo real ou imaginário, use odynamic palavra chave e o compilador fará o resto.

Quando você usa a dynamicpalavra-chave para interagir com uma instância normal, o DLR executa chamadas com ligação tardia aos métodos normais da instância.

A IDynamicMetaObjectProviderinterface permite que uma classe assuma o controle de seu comportamento de ligação tardia.
Quando você usa a dynamicpalavra-chave para interagir com uma IDynamicMetaObjectProviderimplementação, o DLR chama os IDynamicMetaObjectProvidermétodos e o próprio objeto decide o que fazer.

As classes ExpandoObjecte DynamicObjectsão implementações de IDynamicMetaObjectProvider.

ExpandoObjecté uma classe simples que permite adicionar membros a uma instância e usá-los como dynamicaliados.
DynamicObjecté uma implementação mais avançada que pode ser herdada para fornecer facilmente um comportamento personalizado.

SLaks
fonte
2
Qual seria um bom lugar para aprender mais sobre isso? Não é a API, mas o porquê da API? Por exemplo, por que o ExpandoObject não deriva do DynamicObject, que parece o tipo de base padrão para a programação baseada em 'method_missing' do ruby.
Gishu
4
Você poderia adicionar alguns exemplos de uso sempre que possível? Por exemplo, como eu usaria um DynamicObject e quais são os benefícios?
oɔɯǝɹ
10
Ótimas respostas sem exemplos como esse são como bolo sem creme por cima.
Teoman shipahi
68

Tentarei fornecer uma resposta mais clara a essa pergunta, para explicar claramente quais são as diferenças entre dinâmico ExpandoObjecte DynamicObject.

Muito rapidamente, dynamicé uma palavra-chave. Não é um tipo per se. É uma palavra-chave que instrui o compilador a ignorar a verificação de tipo estático no tempo de design e, em vez disso, usar a ligação tardia no tempo de execução. Portanto, não vamos gastar muito tempodynamic restante desta resposta.

ExpandoObjecte DynamicObjectsão de fato tipos. Na SUPERFÍCIE, eles se parecem muito. Ambas as classes são implementadas IDynamicMetaObjectProvider. No entanto, se aprofundar e você verá que eles não são semelhantes.

O DynamicObject é uma implementação parcial do IDynamicMetaObjectProviderobjetivo puramente de ser o ponto de partida para os desenvolvedores implementarem seus próprios tipos personalizados que suportam o envio dinâmico com comportamento de recuperação e armazenamento subjacente personalizado para fazer o envio dinâmico funcionar.

  1. DynamicObject não pode ser construído diretamente.
  2. Você DEVE estender o DynamicObject para que ele possa ser útil como desenvolvedor.
  3. Quando você estende o DynamicObject, agora é capaz de fornecer um comportamento PERSONALIZADO sobre como deseja que o despacho dinâmico resolva os dados armazenados internamente na sua representação de dados subjacente no tempo de execução.
  4. O ExpandoObject armazena dados subjacentes em um Dicionário, etc. Se você implementar o DynamicObject, poderá armazenar dados onde e como quiser. (por exemplo, como você obtém e define os dados na expedição depende inteiramente de você).

Em resumo, use DynamicObject quando desejar criar seus próprios tipos que podem ser usados ​​com o DLR e trabalhar com qualquer comportamento PERSONALIZADO que você desejar.

Exemplo: imagine que você gostaria de ter um tipo dinâmico que retorne um padrão personalizado sempre que uma tentativa de obtenção de um membro que NÃO existe (ou seja, não foi adicionada em tempo de execução). E esse padrão dirá: "Sinto muito, não há cookies neste frasco!". Se você deseja um objeto dinâmico que se comporte dessa maneira, precisará controlar o que acontece quando um campo não é encontrado. ExpandoObject não permitirá que você faça isso. Portanto, você precisará criar seu próprio tipo com um comportamento exclusivo de resolução dinâmica de despacho (despacho) e usá-lo em vez do software prontoExpandoObject .

Você pode criar um tipo da seguinte maneira: (Observe que o código abaixo é apenas ilustrativo e pode não ser executado. Para saber como usar o DynamicObject corretamente, existem muitos artigos e tutoriais em outros lugares.)

public class MyNoCookiesInTheJarDynamicObject : DynamicObject
{
    Dictionary<string, object> properties = new Dictionary<string, object>();

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (properties.ContainsKey(binder.Name))
        {
            result = properties[binder.Name];
            return true;
        }
        else
        {
            result = "I'm sorry, there are no cookies in this jar!"; //<-- THIS IS OUR 
            CUSTOM "NO COOKIES IN THE JAR" RESPONSE FROM OUR DYNAMIC TYPE WHEN AN UNKNOWN FIELD IS ACCESSED
            return false;
        }
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        properties[binder.Name] = value;
        return true;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        dynamic method = properties[binder.Name];
        result = method(args[0].ToString(), args[1].ToString());
        return true;
    }
}

Agora, poderíamos usar essa classe imaginária que acabamos de criar como um tipo dinâmico que possui um comportamento muito personalizado se o campo não existir.

dynamic d = new MyNoCookiesInTheJarDynamicObject();
var s = d.FieldThatDoesntExist;

//in our contrived example, the below should evaluate to true
Assert.IsTrue(s == "I'm sorry, there are no cookies in this jar!")

ExpandoObjecté uma implementação COMPLETA de IDynamicMetaObjectProvider, onde a equipe do .NET Framework tomou todas essas decisões por você. Isso é útil se você não precisar de nenhum comportamento personalizado e acha que o ExpandoObject funciona bem o suficiente para você (90% do tempo, ExpandoObjecté bom o suficiente). Por exemplo, veja o seguinte e, para o ExpandoObject, os designers optaram por lançar uma exceção se o membro dinâmico não existir.

dynamic d = new ExpandoObject();

/*
The ExpandoObject designers chose that this operation should result in an 
Exception. They did not have to make that choice, null could 
have been returned, for example; or the designers could've returned a "sorry no cookies in the jar" response like in our custom class. However, if you choose to use 
ExpandoObject, you have chosen to go with their particular implementation 
of DynamicObject behavior.
*/

try {
var s = d.FieldThatDoesntExist;
}
catch(RuntimeBinderException) { ... }

Então, para resumir, ExpandoObjecté simplesmente uma maneira pré-escolhida de estender o DynamicObject com certos comportamentos de despacho dinâmico que provavelmente funcionarão para você , mas podem não depender de suas necessidades particulares.

Considerando que, DyanmicObjecté um BaseType auxiliar que simplifica e facilita a implementação de seus próprios tipos com comportamentos dinâmicos exclusivos.

Um tutorial útil no qual grande parte da fonte de exemplo acima é baseada.

Ayo I
fonte
Muito boa explicação. Apenas uma correção técnica: ExpandoObject não herda de DynamicObject.
Mike Rosoft 22/02/19
Uma pequena correção no exemplo para DynamicObject: ao substituir TryGetMember, se você retornar falso, a RuntimeBinderExceptionserá lançada ao tentar acessar a propriedade não existente. Para que o snippet funcione, você deve retornar true.
lluchmk 10/01
36

De acordo com a especificação da linguagem C #, dynamicé uma declaração de tipo. Ou seja, dynamic xsignifica que a variável xtem o tipodynamic .

DynamicObject é um tipo que facilita a implementação IDynamicMetaObjectProvider e, portanto, substitui o comportamento de ligação específico para o tipo.

ExpandoObjecté um tipo que age como uma bolsa de propriedade. Ou seja, você pode adicionar propriedades, métodos e assim por diante a instâncias dinâmicas desse tipo em tempo de execução.

Brian Rasmussen
fonte
25
dynamicnão é um tipo real ... é apenas uma dica para dizer ao compilador para usar a ligação tardia para essa variável. dynamicvariáveis são realmente declarado como objectem MSIL
Thomas Levesque
1
@ Thomas: do ponto de vista do compilador, é um tipo, mas você está certo que a representação em tempo de execução é a de Object. Você encontrará a declaração "digitada estaticamente para ser dinâmica" em várias apresentações da EM.
Brian Rasmussen
3
@ Thomas: e a especificação da linguagem diz "C # 4.0 introduz um novo tipo estático chamado dinâmico".
Brian Rasmussen
na verdade ... Mas eu acho que é confuso para considerá-lo como um tipo, uma vez que não há relação herança com tipos como DynamicObject ou ExpandoObject
Thomas Levesque
3
@ NathanA Estou com você aqui. No entanto, a especificação da linguagem o chama de tipo, e é com isso que eu vou.
Brian Rasmussen
0

O exemplo acima DynamicObjectnão indica claramente a diferença, porque está basicamente implementando a funcionalidade que já é fornecida peloExpandoObject .

Nos dois links mencionados abaixo, é muito claro que, com a ajuda de DynamicObject, é possível preservar / alterar o tipo real ( XElementno exemplo usado nos links abaixo) e melhor controle sobre propriedades e métodos.

https://blogs.msdn.microsoft.com/csharpfaq/2009/09/30/dynamic-in-c-4-0-introducing-the-expandoobject/

https://blogs.msdn.microsoft.com/csharpfaq/2009/10/19/dynamic-in-c-4-0-creating-wrappers-with-dynamicobject/

public class DynamicXMLNode : DynamicObject    
{    
    XElement node;

    public DynamicXMLNode(XElement node)    
    {    
        this.node = node;    
    }

    public DynamicXMLNode()    
    {    
    }

    public DynamicXMLNode(String name)    
    {    
        node = new XElement(name);    
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)    
    {    
        XElement setNode = node.Element(binder.Name);

        if (setNode != null)    
            setNode.SetValue(value);    
        else    
        {    
            if (value.GetType() == typeof(DynamicXMLNode))    
                node.Add(new XElement(binder.Name));    
            else    
                node.Add(new XElement(binder.Name, value));    
        }

        return true;    
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)    
    {    
        XElement getNode = node.Element(binder.Name);

        if (getNode != null)    
        {    
            result = new DynamicXMLNode(getNode);    
            return true;    
        }    
        else    
        {    
            result = null;    
            return false;    
        }    
    }    
}
Deepak Mishra
fonte