Quali sono i veri vantaggi di ExpandoObject?


587

La classe ExpandoObject che viene aggiunta a .NET 4 consente di impostare arbitrariamente le proprietà su un oggetto in fase di runtime.

Ci sono dei vantaggi rispetto all'utilizzo di una Dictionary<string, object>, o addirittura di una Hashtable ? Per quanto posso dire, questo non è altro che una tabella hash a cui è possibile accedere con una sintassi leggermente più concisa.

Ad esempio, perché è questo:

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

Davvero migliore, o sostanzialmente diverso, rispetto a:

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

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

Quali vantaggi reali si ottengono utilizzando ExpandoObject invece di utilizzare un tipo di dizionario arbitrario, oltre a non essere ovvio che si sta utilizzando un tipo che verrà determinato in fase di esecuzione.

Risposte:


689

Da quando ho scritto l'articolo di MSDN a cui ti riferisci, suppongo di dover rispondere a questo.

Innanzitutto, ho anticipato questa domanda ed è per questo che ho scritto un post sul blog che mostra un caso d'uso più o meno reale per ExpandoObject: Dynamic in C # 4.0: Presentazione di ExpandoObject .

In breve, ExpandoObject può aiutarti a creare oggetti gerarchici complessi. Ad esempio, immagina di avere un dizionario all'interno di un dizionario:

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"]);

Più profonda è la gerarchia, più brutto è il codice. Con ExpandoObject rimane elegante e leggibile.

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

In secondo luogo, come è già stato sottolineato, ExpandoObject implementa l'interfaccia INotifyPropertyChanged che offre un maggiore controllo sulle proprietà rispetto a un dizionario.

Infine, puoi aggiungere eventi a ExpandoObject come qui:

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);
   }
}

Inoltre, tieni presente che nulla ti impedisce di accettare argomenti di eventi in modo dinamico. In altre parole, invece di utilizzare EventHandler, è possibile utilizzare EventHandler<dynamic>ciò che provocherebbe il secondo argomento del gestore dynamic.


53
Interessante. Grazie per le informazioni relative a: eventi. Quello era nuovo per me.
Reed Copsey,

16
@AlexandraRusina, come fa a sapere che è un evento quando dici d.MyEvent = null;, o no?
Shimmy Weitzhandler,

20
Forse mi manca qualcosa, ma questo non è un evento: questa è una semplice proprietà di tipo delegato.
Sergey Berezovskiy,

7
Il primo blocco può essere scritto usando tipi anonimi: var expando = new { Address = new { State = "WA" } }; Console.WriteLine(expando.Address.State);lo trovo più leggibile ma ymmv. E dato che è tipicamente statico, è più utile in questo contesto.
nawfal,

13
@nawfal non è giusto: un anonimo è diverso da un Expando. Stai creando un tipo anonimo, che non può quindi aggiungere proprietà arbitrarie a.
Dott. Blowhard,

75

Un vantaggio è per gli scenari vincolanti. Le griglie dati e le griglie delle proprietà acquisiranno le proprietà dinamiche tramite il sistema TypeDescriptor. Inoltre, l'associazione dei dati WPF comprenderà le proprietà dinamiche, quindi i controlli WPF possono associarsi a un ExpandoObject più facilmente di un dizionario.

L'interoperabilità con linguaggi dinamici, che prevede proprietà DLR anziché voci di dizionario, può anche essere considerata in alcuni scenari.


6
E sembra che l'associazione dati per oggetti dinamici è rotto . L'utente segnalante eisenbergeffect è qui su SO e coordinatore di caliburn.micro. @AlexandraRusina puoi commentare lo stato del bug e lo stato "Won't fix"
surfmuggle

2
Per i curiosi, sono attualmente in grado di collegarmi List<dynamic>e IEnumerable<dynamic>utilizzare WPF4
Graham Bass

47

Il vero vantaggio per me è l'associazione di dati totalmente semplice da XAML:

public dynamic SomeData { get; set; }

...

SomeData.WhatEver = "Yo Man!";

...

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

28

Interagisci con altre lingue basate sul DLRmotivo numero 1 a cui riesco a pensare. Non puoi passarli a Dictionary<string, object>in quanto non è un IDynamicMetaObjectProvider. Un altro vantaggio aggiunto è che implementa, il INotifyPropertyChangedche significa che nel mondo di database di WPF ha anche benefici aggiuntivi oltre a ciò che Dictionary<K,V>può fornirti.


19

Si tratta della comodità del programmatore. Posso immaginare di scrivere programmi veloci e sporchi con questo oggetto.


9
@J. Hendrix, non dimenticare che ha anche detto "sporco". Intellisense ha il suo rovescio della medaglia, tuttavia, semplifica il debug e la cattura di bug. Personalmente preferisco ancora i tipi statici rispetto a quelli dinamici a meno che non mi occupi di un caso strano (e sempre raro).
Phil

+1 per comodità. Tuttavia trovo che i tipi anonimi possano essere ugualmente convenienti come una semplice borsa di proprietà e semplicemente migliori per la sua staticità.
nawfal,

1
Non vorrei usarlo nel codice di produzione, ma è molto conveniente nel codice di prova e può renderlo molto bello.
Tobias,

14

Penso che avrà un vantaggio sintattico, dal momento che non "fingerai" proprietà aggiunte dinamicamente usando un dizionario.

Questo, e interagire con linguaggi dinamici, penso.


11

È un esempio del grande articolo MSDN sull'uso di ExpandoObject per la creazione di tipi dinamici ad-hoc per dati strutturati in entrata (ad es. XML, Json).

Possiamo anche assegnare un delegato alla proprietà dinamica di 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);

Quindi ci consente di iniettare parte della logica nell'oggetto dinamico in fase di esecuzione. Pertanto, insieme a espressioni lambda, chiusure, parole chiave dinamiche e classe DynamicObject , possiamo introdurre alcuni elementi di programmazione funzionale nel nostro codice C #, che conosciamo da linguaggi dinamici come JavaScript o PHP.


4

Ci sono alcuni casi in cui questo è utile. Lo userò ad esempio per una shell modulare. Ogni modulo definisce la propria finestra di configurazione associata alle proprie impostazioni. Fornisco un ExpandoObject in quanto Datacontext e salvo i valori nella mia memoria di configurazione. In questo modo il writer della finestra di dialogo di configurazione deve solo associarsi a un valore e viene automaticamente creato e salvato. (E fornito al modulo per l'utilizzo di queste impostazioni ovviamente)

E 'semplicemente più facile da usare di un dizionario. Ma tutti dovrebbero essere consapevoli che internamente è solo un dizionario.

È come LINQ solo zucchero sintattico, ma a volte rende le cose più facili.

Quindi, per rispondere direttamente alla tua domanda: è più facile da scrivere e più facile da leggere. Ma tecnicamente è essenzialmente un Dictionary<string,object>(puoi anche lanciarlo in uno per elencare i valori).


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

Penso che funzioni solo perché tutto ha un ToString (), altrimenti dovresti conoscere il tipo che era e lanciare "l'oggetto" su quel tipo.


Alcuni di questi sono utili più spesso di altri, sto cercando di essere accurato.

  1. Può essere molto più naturale accedere a una raccolta, in questo caso ciò che è effettivamente un "dizionario", usando la notazione punto più diretta.

  2. Sembra che questo possa essere usato come una Tupla davvero carina. Puoi ancora chiamare i tuoi membri "Item1", "Item2" ecc ... ma ora non è necessario, è anche mutabile, a differenza di una Tupla. Questo ha l'enorme inconveniente della mancanza di supporto intellisense.

  3. Potresti sentirti a disagio con i "nomi dei membri come stringhe", come lo è il dizionario, potresti pensare che sia troppo simile a "eseguire stringhe", e potrebbe portare alla codifica delle convenzioni di denominazione e alla gestione del lavoro con i morfemi e sillabe quando il codice sta cercando di capire come usare i membri :-P

  4. Puoi assegnare un valore a un ExpandoObject stesso o solo ai suoi membri? Confronta e contrapponi a dinamico / dinamico [], usa quello che più si adatta alle tue esigenze.

  5. Non credo che dinamico / dinamico [] funzioni in un ciclo foreach, devi usare var, ma probabilmente puoi usare ExpandoObject.

  6. Non è possibile utilizzare la dinamica come membro di dati in una classe, forse perché è almeno una specie di parola chiave, si spera che con ExpandoObject sia possibile.

  7. Mi aspetto che "sia" un ExpandoObject, potrebbe essere utile per etichettare le cose molto generiche, con il codice che si differenzia in base ai tipi in cui vengono utilizzate molte cose dinamiche.


Sii gentile se riesci a eseguire il drill down su più livelli contemporaneamente.

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

Non è il miglior esempio possibile, immagina usi eleganti come appropriati nei tuoi progetti.

È un peccato che tu non possa avere il codice per costruirne alcuni e spingere i risultati su intellisense. Non sono sicuro di come funzioni.

Sii gentile se potessero avere un valore così come i membri.

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

-3

Dopo valueTuples, a che serve la classe ExpandoObject? questo codice a 6 righe con ExpandoObject:

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

può essere scritto in una riga con le tuple:

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

inoltre con la sintassi tupla hai una forte inferenza di tipo e supporto per l'intlisense


1
I tuoi esempi non sono identici nel senso che con il valore tupla non puoi scrivere Tc = 5; dopo aver terminato definire T. Con ExpandoObject puoi farlo perché è dinamico. Il tuo esempio con valore tupla è molto identico a dichiarare il tipo anonimo. Ad esempio: var T2 = new {x = 1, y = 2, z = new {a = 3, b = 4}};
LxL

Perché devo scrivere Tc = 5 senza definirlo? ExpandoObject è utile solo quando si ha a che fare con oggetti COM che non sono definiti in .net. Altrimenti, non uso mai ExpandoObject, perché è sporco e difettoso sia in fase di progettazione che in fase di esecuzione.
Ing. M.Hamdy,

1
Che ne dici di avere z inizialmente assegnato a (a: 3, b: 4) e poi ti piacerebbe che z avesse ulteriori proprietà c? Riesci a farlo con valore tupla?
LxL

Quindi il mio punto è che non puoi confrontare ExpandoObject con valore tupla perché sono progettati per scopi diversi. Confrontando la tua strada, hai eliminato la funzionalità per cui è stato progettato ExpandoObject, che è una struttura dinamica.
LxL
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.