Quais são os verdadeiros benefícios do ExpandoObject?

587

A classe ExpandoObject sendo adicionada ao .NET 4 permite definir arbitrariamente propriedades em um objeto em tempo de execução.

Existe alguma vantagem nisso ao usar um Dictionary<string, object>, ou mesmo um Hashtable ? Até onde eu sei, isso não passa de uma tabela de hash que você pode acessar com uma sintaxe um pouco mais sucinta.

Por exemplo, por que isso é:

dynamic obj = new ExpandoObject();
obj.MyInt = 3;
obj.MyString = "Foo";
Console.WriteLine(obj.MyString);

Realmente melhor ou substancialmente diferente do que:

var obj = new Dictionary<string, object>();
obj["MyInt"] = 3;
obj["MyString"] = "Foo";

Console.WriteLine(obj["MyString"]);

Quais são as vantagens reais obtidas com o ExpandoObject em vez de apenas usar um tipo de dicionário arbitrário, além de não ser óbvio que você está usando um tipo que será determinado em tempo de execução.

Reed Copsey
fonte

Respostas:

689

Desde que escrevi o artigo do MSDN a que você está se referindo, acho que tenho que responder a este.

Primeiro, eu antecipei essa pergunta e foi por isso que escrevi uma postagem no blog que mostra um caso de uso mais ou menos real para o ExpandoObject: Dynamic no C # 4.0: Apresentando o ExpandoObject .

Em breve, o ExpandoObject pode ajudá-lo a criar objetos hierárquicos complexos. Por exemplo, imagine que você tenha um dicionário dentro de um dicionário:

Dictionary<String, object> dict = new Dictionary<string, object>();
Dictionary<String, object> address = new Dictionary<string,object>();
dict["Address"] = address;
address["State"] = "WA";
Console.WriteLine(((Dictionary<string,object>)dict["Address"])["State"]);

Quanto mais profunda é a hierarquia, mais feio é o código. Com o ExpandoObject, ele permanece elegante e legível.

dynamic expando = new ExpandoObject();
expando.Address = new ExpandoObject();
expando.Address.State = "WA";
Console.WriteLine(expando.Address.State);

Segundo, como já foi apontado, o ExpandoObject implementa a interface INotifyPropertyChanged, que oferece mais controle sobre propriedades do que um dicionário.

Por fim, você pode adicionar eventos ao ExpandoObject como aqui:

class Program
{
   static void Main(string[] args)
   {
       dynamic d = new ExpandoObject();

       // Initialize the event to null (meaning no handlers)
       d.MyEvent = null;

       // Add some handlers
       d.MyEvent += new EventHandler(OnMyEvent);
       d.MyEvent += new EventHandler(OnMyEvent2);

       // Fire the event
       EventHandler e = d.MyEvent;

       e?.Invoke(d, new EventArgs());
   }

   static void OnMyEvent(object sender, EventArgs e)
   {
       Console.WriteLine("OnMyEvent fired by: {0}", sender);
   }

   static void OnMyEvent2(object sender, EventArgs e)
   {
       Console.WriteLine("OnMyEvent2 fired by: {0}", sender);
   }
}

Além disso, lembre-se de que nada o impede de aceitar argumentos de eventos de maneira dinâmica. Em outras palavras, em vez de usar EventHandler, você pode usar o EventHandler<dynamic>que causaria o segundo argumento do manipulador dynamic.

Alexandra Rusina
fonte
53
Interessante. Obrigado pela informação re: events. Isso foi novo para mim.
Reed Copsey #
16
@AlexandraRusina, como ela sabe que é um evento quando você diz d.MyEvent = null;: Ou não?
Shimmy Weitzhandler
20
Talvez esteja faltando alguma coisa, mas isso não é evento - é uma propriedade simples do tipo delegado.
Sergey Berezovskiy
7
O primeiro bloco pode ser escrito usando tipos anônimos: var expando = new { Address = new { State = "WA" } }; Console.WriteLine(expando.Address.State);acho isso mais legível, mas ymmv. E, dado que é digitado estaticamente, é mais útil nesse contexto.
Nawfal
13
@nawfal isso não está certo - um anônimo é diferente de um Expando. Você está criando um tipo anônimo, ao qual não é possível adicionar propriedades arbitrárias.
Dr Blowhard
75

Uma vantagem é para cenários vinculativos. As grades de dados e grades de propriedades capturam as propriedades dinâmicas por meio do sistema TypeDescriptor. Além disso, a ligação de dados do WPF entenderá propriedades dinâmicas, portanto, os controles do WPF podem ser vinculados a um ExpandoObject mais facilmente que a um dicionário.

A interoperabilidade com idiomas dinâmicos, que espera propriedades de DLR em vez de entradas de dicionário, também pode ser considerada em alguns cenários.

Itowlson
fonte
6
Ele parece que ligação de dados para objetos dinâmicos está quebrado . O usuário de relatório eisenbergeffect está aqui no SO e coordenador de caliburn.micro. @AlexandraRusina você pode comentar sobre o estado do bug e o estado "não vai resolver"
surfmuggle
2
Para os curiosos, atualmente sou capaz de vincular List<dynamic>e IEnumerable<dynamic>usar o WPF4
Graham Bass
47

O benefício real para mim é a ligação de dados totalmente sem esforço do XAML:

public dynamic SomeData { get; set; }

...

SomeData.WhatEver = "Yo Man!";

...

 <TextBlock Text="{Binding SomeData.WhatEver}" />
bjull
fonte
28

A interoperabilidade com outras linguagens baseadas na DLRé a razão número 1 em que consigo pensar. Você não pode passar para eles Dictionary<string, object>porque não é IDynamicMetaObjectProvider. Outro benefício adicional é que ele implementa, o INotifyPropertyChangedque significa que, no mundo de ligação de dados do WPF, ele também oferece benefícios além do que Dictionary<K,V>pode lhe proporcionar.

Drew Marsh
fonte
19

É tudo sobre a conveniência do programador. Eu posso imaginar escrever programas rápidos e sujos com esse objeto.

ChaosPandion
fonte
9
@J. Hendrix, não esqueça que ele também disse "sujo". O Intellisense tem sua desvantagem, no entanto, facilita a depuração e a detecção de erros. Pessoalmente, ainda prefiro tipos estáticos do que dinâmicos, a menos que eu lide com um caso estranho (e sempre raro).
Phil
+1 por conveniência. No entanto, acho que tipos anônimos podem ser igualmente convenientes como um simples pacote de propriedades e apenas melhores por sua estática.
Nawfal #
1
Eu não gostaria de usá-lo no código de produção, mas é muito conveniente no código de teste e pode torná-lo muito bonito.
Tobias
14

Eu acho que terá um benefício sintático, já que você não estará mais "fingindo" propriedades adicionadas dinamicamente usando um dicionário.

Isso e interage com linguagens dinâmicas, eu pensaria.

gn22
fonte
11

É um exemplo de excelente artigo do MSDN sobre o uso do ExpandoObject para criar tipos ad-hoc dinâmicos para dados estruturados de entrada (por exemplo, XML, Json).

Também podemos atribuir delegado à propriedade dinâmica do ExpandoObject :

dynamic person = new ExpandoObject();
person.FirstName = "Dino";
person.LastName = "Esposito";

person.GetFullName = (Func<String>)(() => { 
  return String.Format("{0}, {1}", 
    person.LastName, person.FirstName); 
});

var name = person.GetFullName();
Console.WriteLine(name);

Assim, permite injetar alguma lógica no objeto dinâmico em tempo de execução. Portanto, juntamente com expressões lambda, closures, palavra-chave dinâmica e classe DynamicObject , podemos introduzir alguns elementos de programação funcional em nosso código C #, que conhecemos de linguagens dinâmicas como JavaScript ou PHP.

sgnsajgon
fonte
4

Existem alguns casos em que isso é útil. Vou usá-lo para um shell modularizado, por exemplo. Cada módulo define seu próprio Diálogo de Configuração, vinculado às suas configurações. Eu forneço um ExpandoObject como Datacontext e salvo os valores em minha configuração Storage. Dessa forma, o gravador do Diálogo de Configuração precisa vincular-se a um Valor e é automaticamente criado e salvo. (E fornecido ao módulo para usar essas configurações, é claro)

É simplesmente mais fácil de usar do que um dicionário. Mas todos devem estar cientes de que internamente é apenas um dicionário.

É como o LINQ apenas açúcar sintático, mas às vezes facilita as coisas.

Portanto, para responder diretamente à sua pergunta: é mais fácil escrever e ler. Mas tecnicamente é essencialmente um Dictionary<string,object>(você pode até convertê-lo em um para listar os valores).

n1LWeb
fonte
-1
var obj = new Dictionary<string, object>;
...
Console.WriteLine(obj["MyString"]);

Eu acho que só funciona porque tudo tem um ToString (), caso contrário você teria que saber o tipo que era e converter o 'objeto' para esse tipo.


Alguns deles são úteis com mais frequência do que outros, estou tentando ser minucioso.

  1. Pode ser muito mais natural acessar uma coleção, nesse caso o que é efetivamente um "dicionário", usando a notação de ponto mais direta.

  2. Parece que isso poderia ser usado como uma tupla realmente agradável. Você ainda pode chamar seus membros de "Item1", "Item2" etc ... mas agora você não precisa, também é mutável, ao contrário de uma Tupla. Isso tem a enorme desvantagem da falta de suporte do intellisense.

  3. Você pode se sentir desconfortável com "nomes de membros como seqüências de caracteres", como é a sensação no dicionário, pode parecer que é muito parecido com "executar sequências" e pode levar a convenções de nomenclatura a serem codificadas e lidar com o trabalho com morfemas e sílabas quando o código está tentando entender como usar os membros :-P

  4. Você pode atribuir um valor a um ExpandoObject em si ou apenas a seus membros? Compare e contraste com dinâmico / dinâmico [], use o que melhor se adapte às suas necessidades.

  5. Eu não acho que dinâmico / dinâmico [] funcione em um loop foreach, você precisa usar var, mas possivelmente você pode usar ExpandoObject.

  6. Você não pode usar dinâmico como membro de dados em uma classe, talvez porque seja pelo menos como uma palavra-chave, espero que você possa usar o ExpandoObject.

  7. Espero que "seja" um ExpandoObject, possa ser útil rotular coisas muito genéricas, com código que se diferencia com base nos tipos em que há muitas coisas dinâmicas sendo usadas.


Seja legal se puder detalhar vários níveis ao mesmo tempo.

var e = new ExpandoObject();
e.position.x = 5;
etc...

Esse não é o melhor exemplo possível, imagine usos elegantes, conforme apropriado, em seus próprios projetos.

É uma pena que você não possa fazer com que o código construa alguns deles e forneça os resultados ao intellisense. Não tenho certeza de como isso funcionaria.

Seja gentil se eles puderem ter um valor e também membros.

var fifteen = new ExpandoObject();
fifteen = 15;
fifteen.tens = 1;
fifteen.units = 5;
fifteen.ToString() = "fifteen";
etc...
alan2here
fonte
-3

Após valueTuples, para que serve a classe ExpandoObject? este código de 6 linhas com ExpandoObject:

dynamic T = new ExpandoObject();
T.x = 1;
T.y = 2;
T.z = new ExpandoObject();
T.z.a = 3;
T.b= 4;

pode ser escrito em uma linha com tuplas:

var T = (x: 1, y: 2, z: (a: 3, b: 4));

além da sintaxe da tupla, você tem forte inferência de tipo e suporte a intlisense

Eng. M.Hamdy
fonte
1
Seus exemplos não são idênticos no sentido de que, com a tupla de valor, você não pode escrever Tc = 5; depois de terminar, defina T. Com o ExpandoObject, você pode fazê-lo porque é dinâmico. Seu exemplo com tupla de valor é muito idêntico à declaração de tipo anônimo. Por exemplo: var T2 = novo {x = 1, y = 2, z = novo {a = 3, b = 4}};
LxL
Por que preciso escrever Tc = 5 sem defini-lo? ExpandoObject é útil apenas ao lidar com objetos COM que não são desonestos no .net. Caso contrário, nunca utilizarei esse ExpandoObject, pois ele está sujo e com erros no tempo de design e no tempo de execução.
Eng. M.Hamdy
1
Que tal você ter z inicialmente atribuído a (a: 3, b: 4) e depois desejar que z tenha propriedade c adicional? Você pode fazer isso com tupla de valor?
lxl
Portanto, o que quero dizer é que você não pode comparar o ExpandoObject com a tupla de valor, porque elas foram projetadas para diferentes propósitos. Ao comparar o seu caminho, você descartou a funcionalidade para a qual o ExpandoObject foi projetado, que é a estrutura dinâmica.
lxl