¿Cuáles son los verdaderos beneficios de ExpandoObject?

587

La clase ExpandoObject que se agrega a .NET 4 le permite establecer arbitrariamente propiedades en un objeto en tiempo de ejecución.

¿Hay alguna ventaja sobre esto sobre el uso de Dictionary<string, object>, o realmente incluso un Hashtable ? Por lo que puedo decir, esto no es más que una tabla hash a la que puedes acceder con una sintaxis un poco más sucinta.

Por ejemplo, por qué es esto:

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

Realmente mejor, o sustancialmente diferente, que:

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

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

Qué ventajas reales se obtienen al usar ExpandoObject en lugar de simplemente usar un tipo de diccionario arbitrario, además de no ser obvio que está usando un tipo que se determinará en tiempo de ejecución.

Reed Copsey
fuente

Respuestas:

689

Desde que escribí el artículo de MSDN al que te refieres, creo que tengo que responder a este.

Primero, anticipé esta pregunta y es por eso que escribí una publicación de blog que muestra un caso de uso más o menos real para ExpandoObject: Dynamic en C # 4.0: Presentación del ExpandoObject .

En breve, ExpandoObject puede ayudarlo a crear objetos jerárquicos complejos. Por ejemplo, imagine que tiene un diccionario dentro de un diccionario:

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

Cuanto más profunda es la jerarquía, más feo es el código. Con ExpandoObject se mantiene elegante y legible.

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

En segundo lugar, como ya se señaló, ExpandoObject implementa la interfaz INotifyPropertyChanged que le brinda más control sobre las propiedades que un diccionario.

Finalmente, puede agregar eventos a ExpandoObject como aquí:

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

Además, tenga en cuenta que nada le impide aceptar argumentos de eventos de forma dinámica. En otras palabras, en lugar de usar EventHandler, puede usar lo EventHandler<dynamic>que haría que el segundo argumento del controlador sea dynamic.

Alexandra Rusina
fuente
53
Interesante. Gracias por la información re: eventos. Esa fue una nueva para mí.
Reed Copsey
16
@AlexandraRusina, ¿cómo sabe que es un evento cuando dices d.MyEvent = null;, o no?
Shimmy Weitzhandler
20
Tal vez me estoy perdiendo algo, pero esto no es un evento, es una propiedad simple de tipo delegado.
Sergey Berezovskiy
77
El primer bloque se puede escribir usando tipos anónimos: var expando = new { Address = new { State = "WA" } }; Console.WriteLine(expando.Address.State);encuentro esto más legible pero ymmv. Y dado que está estáticamente escrito, es más útil en este contexto.
nawfal
13
@nawfal no está bien: un anónimo es diferente a un Expando. Está creando un tipo anónimo, que no puede agregar propiedades arbitrarias.
Dr Blowhard
75

Una ventaja es para los escenarios vinculantes. Las cuadrículas de datos y las cuadrículas de propiedades recogerán las propiedades dinámicas a través del sistema TypeDescriptor. Además, el enlace de datos WPF comprenderá las propiedades dinámicas, por lo que los controles WPF pueden vincularse a un Objeto de Expando más fácilmente que un diccionario.

La interoperabilidad con lenguajes dinámicos, que esperarán propiedades DLR en lugar de entradas de diccionario, también puede ser una consideración en algunos escenarios.

itowlson
fuente
66
Se parece que el enlace de datos de objetos dinámicos se rompe . El usuario informante eisenbergeffect está aquí en SO y coordinador de caliburn.micro. @AlexandraRusina puede comentar sobre el estado del error y el estado "no van a resolver"
surfmuggle
2
Para aquellos curiosos, actualmente puedo vincularme List<dynamic>y IEnumerable<dynamic>usar WPF4
Graham Bass
47

El beneficio real para mí es el enlace de datos totalmente sin esfuerzo de XAML:

public dynamic SomeData { get; set; }

...

SomeData.WhatEver = "Yo Man!";

...

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

La interoperabilidad con otros idiomas fundados en el DLRes la razón # 1 en la que puedo pensar. No puedes pasarlos Dictionary<string, object>como no es un IDynamicMetaObjectProvider. Otro beneficio adicional es que implementa lo INotifyPropertyChangedque significa que en el mundo de enlace de datos de WPF también tiene beneficios adicionales más allá de lo que Dictionary<K,V>puede proporcionarle.

Drew Marsh
fuente
19

Se trata de la conveniencia del programador. Me imagino escribiendo programas rápidos y sucios con este objeto.

ChaosPandion
fuente
99
@J. Hendrix, no olvides que también dijo "sucio". Intellisense tiene su lado negativo, sin embargo, facilita la depuración y la detección de errores. Personalmente, todavía prefiero los tipos estáticos a los dinámicos, a menos que tenga que lidiar con un caso extraño (y siempre raro).
Phil
+1 por conveniencia. Sin embargo, creo que los tipos anónimos pueden ser igualmente convenientes como una simple bolsa de propiedades y mejor aún por su estática.
nawfal
1
No me gustaría usarlo en el código de producción, pero es muy conveniente en el código de prueba y puede hacer que se vea muy hermoso.
Tobias
14

Creo que tendrá un beneficio sintáctico, ya que ya no estarás "falsificando" propiedades agregadas dinámicamente usando un diccionario.

Eso, e interoperar con lenguajes dinámicos, creo.

gn22
fuente
11

Es un ejemplo de un excelente artículo de MSDN sobre el uso de ExpandoObject para crear tipos dinámicos ad-hoc para datos estructurados entrantes (es decir, XML, Json).

También podemos asignar delegado a la propiedad dinámica de 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);

Por lo tanto, nos permite inyectar algo de lógica en un objeto dinámico en tiempo de ejecución. Por lo tanto, junto con expresiones lambda, cierres, palabras clave dinámicas y clase DynamicObject , podemos introducir algunos elementos de programación funcional en nuestro código C #, que conocemos de lenguajes dinámicos como JavaScript o PHP.

sgnsajgon
fuente
4

Hay algunos casos donde esto es útil. Lo usaré para un shell modularizado, por ejemplo. Cada módulo define su propio cuadro de diálogo de configuración enlazado a sus configuraciones. Le proporciono un ExpandoObject como Datacontext y guardo los valores en mi configuración de almacenamiento. De esta forma, el escritor del Diálogo de configuración solo tiene que vincularse a un valor y se crea y guarda automáticamente. (Y proporcionado al módulo para usar estas configuraciones, por supuesto)

Es simplemente más fácil de usar que un diccionario. Pero todos deben tener en cuenta que internamente es solo un Diccionario.

Es como LINQ solo azúcar sintáctico, pero a veces hace las cosas más fáciles.

Entonces, para responder su pregunta directamente: es más fácil de escribir y más fácil de leer. Pero técnicamente es esencialmente un Dictionary<string,object>(Incluso puede convertirlo en uno para enumerar los valores).

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

Creo que eso solo funciona porque todo tiene un ToString (), de lo contrario, tendría que saber el tipo que era y emitir el 'objeto' a ese tipo.


Algunos de estos son útiles más a menudo que otros, estoy tratando de ser minucioso.

  1. Puede ser mucho más natural acceder a una colección, en este caso lo que efectivamente es un "diccionario", utilizando la notación de puntos más directa.

  2. Parece que esto podría usarse como una Tupla realmente agradable. Todavía puede llamar a sus miembros "Elemento1", "Elemento2", etc. ... pero ahora no tiene que hacerlo, también es mutable, a diferencia de una Tupla. Esto tiene el gran inconveniente de la falta de soporte inteligente.

  3. Puede sentirse incómodo con los "nombres de miembros como cadenas", como se siente con el diccionario, puede sentir que es muy parecido a "ejecutar cadenas", y puede llevar a codificar convenciones de nombres y trabajar con morfemas y las sílabas cuando el código está intentando entender cómo usar los miembros :-P

  4. ¿Se puede asignar un valor a un ExpandoObject en sí o solo a sus miembros? Compare y contraste con dinámico / dinámico [], use el que mejor se adapte a sus necesidades.

  5. No creo que dynamic / dynamic [] funcione en un bucle foreach, debe usar var, pero posiblemente puede usar ExpandoObject.

  6. No puede usar Dynamic como miembro de datos en una clase, tal vez porque es al menos algo así como una palabra clave, con suerte puede hacerlo con ExpandoObject.

  7. Supongo que "es" un Objeto de Expando, podría ser útil para etiquetar cosas muy genéricas, con un código que se diferencia en función de los tipos en los que se utilizan muchas cosas dinámicas.


Sé amable si pudieras profundizar en varios niveles a la vez.

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

Ese no es el mejor ejemplo posible, imagine usos elegantes según corresponda en sus propios proyectos.

Es una pena que no pueda hacer que el código construya algunos de estos y empuje los resultados a intellisense. Sin embargo, no estoy seguro de cómo funcionaría esto.

Sé amable si pudieran tener un valor tan bien como los miembros.

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

Después de valueTuples, ¿de qué sirve la clase ExpandoObject? este código de 6 líneas con ExpandoObject:

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

se puede escribir en una línea con tuplas:

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

Además con la sintaxis de tupla, tiene una fuerte inferencia de tipos y soporte intlisense

Ing. M.Hamdy
fuente
1
Sus ejemplos no son idénticos en el sentido de que con la tupla de valor, no puede escribir Tc = 5; después de terminar, defina T. Con ExpandoObject puede hacerlo porque es dinámico. Su ejemplo con la tupla de valor es muy idéntico a la declaración de tipo anónimo. Por ejemplo: var T2 = nuevo {x = 1, y = 2, z = nuevo {a = 3, b = 4}};
LxL
¿Por qué necesito escribir Tc = 5 sin definirlo? ExpandoObject solo es útil cuando se trata de objetos COM que no están definidos en .net. De lo contrario, nunca uso este ExpandoObject, porque está sucio y tiene errores tanto en tiempo de diseño como en tiempo de ejecución.
Ing. M.Hamdy
1
¿Qué tal si tiene z al principio asignado a (a: 3, b: 4) y luego desea que z tenga una propiedad c adicional? ¿Puedes hacerlo con tupla de valor?
LxL
Entonces, mi punto es que no puede comparar ExpandoObject con la tupla de valor porque están diseñados para diferentes propósitos. Al comparar su manera, descartó la funcionalidad para la que está diseñado ExpandoObject, que es la estructura dinámica.
LxL