Jakie są prawdziwe zalety ExpandoObject?

587

ExpandoObject klasa dodawanych do .NET 4 pozwala dowolnie ustawić właściwości na obiekt w czasie wykonywania.

Czy ma to jakąś przewagę nad używaniem Dictionary<string, object>, a nawet Hashtable ? O ile mi wiadomo, jest to nic innego jak tabela haszująca, do której można uzyskać dostęp z nieco bardziej zwięzłą składnią.

Na przykład dlaczego:

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

Naprawdę lepsze lub zasadniczo inne niż:

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

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

Jakie rzeczywiste korzyści uzyskuje się, stosując ExpandoObject zamiast zwykłego słownika, poza tym, że nie jest oczywiste, że używasz typu, który zostanie określony w czasie wykonywania.

Reed Copsey
źródło

Odpowiedzi:

689

Ponieważ napisałem artykuł MSDN, o którym mówisz, chyba muszę na to odpowiedzieć.

Po pierwsze, przewidziałem to pytanie i dlatego napisałem post na blogu, który pokazuje mniej lub bardziej rzeczywisty przypadek użycia ExpandoObject: Dynamic w C # 4.0: Przedstawiamy ExpandoObject .

Krótko mówiąc, ExpandoObject może pomóc w tworzeniu złożonych obiektów hierarchicznych. Na przykład wyobraź sobie, że masz słownik w słowniku:

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

Im głębsza jest hierarchia, tym bardziej brzydki jest kod. Dzięki ExpandoObject pozostaje elegancki i czytelny.

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

Po drugie, jak już wspomniano, ExpandoObject implementuje interfejs INotifyPropertyChanged, który daje większą kontrolę nad właściwościami niż słownik.

Na koniec możesz dodać zdarzenia do ExpandoObject jak tutaj:

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

Pamiętaj też, że nic nie stoi na przeszkodzie, aby przyjmować argumenty zdarzeń w sposób dynamiczny. Innymi słowy, zamiast używać EventHandlermożesz użyć, EventHandler<dynamic>co spowodowałoby, że byłby to drugi argument programu obsługi dynamic.

Alexandra Rusina
źródło
53
Ciekawy. Dzięki za informacje dotyczące wydarzeń. To było dla mnie nowe.
Reed Copsey,
16
@AlexandraRusina, skąd wie, że to wydarzenie, kiedy mówisz d.MyEvent = null;, czy nie?
Shimmy Weitzhandler,
20
Może czegoś mi brakuje, ale to nie jest wydarzenie - to prosta właściwość typu delegata.
Sergey Berezovskiy,
7
Pierwszy blok można napisać przy użyciu anonimowych typów: var expando = new { Address = new { State = "WA" } }; Console.WriteLine(expando.Address.State);uważam to za bardziej czytelne, ale ymmv. Biorąc pod uwagę, że jest on wpisany statycznie, jest bardziej przydatny w tym kontekście.
nawfal
13
@nawfal to nie w porządku - anonimowy różni się od Expando. Tworzysz anonimowy typ, który nie może następnie dodać dowolnych właściwości do.
Dr Blowhard
75

Jedną zaletą są wiążące scenariusze. Siatki danych i siatki właściwości będą pobierać właściwości dynamiczne za pośrednictwem systemu TypeDescriptor. Ponadto powiązanie danych WPF zrozumie właściwości dynamiczne, więc formanty WPF mogą łatwiej powiązać obiekt ExpandoObject niż słownik.

Współdziałanie z dynamicznymi językami, które będą oczekiwać właściwości DLR zamiast wpisów słownika, może również być rozważane w niektórych scenariuszach.

itowlson
źródło
6
To wydaje się, że wiązania z danymi do obiektów dynamicznych jest uszkodzony . Użytkownik eisenbergeffect zgłaszający jest tutaj na SO i koordynator caliburn.micro. @AlexandraRusina możesz komentować stan błędu i status „Nie naprawię”
surfmuggle
2
Dla ciekawskich jestem obecnie w stanie powiązać List<dynamic>i IEnumerable<dynamic>używać WPF4
Graham Bass
47

Prawdziwą korzyścią dla mnie jest całkowicie łatwe wiązanie danych od XAML:

public dynamic SomeData { get; set; }

...

SomeData.WhatEver = "Yo Man!";

...

 <TextBlock Text="{Binding SomeData.WhatEver}" />
bjull
źródło
28

Interakcja z innymi językami oparta DLRjest na pierwszym miejscu, o którym mogę myśleć. Nie możesz ich przekazać, Dictionary<string, object>bo to nie jest IDynamicMetaObjectProvider. Kolejną dodatkową zaletą jest to, że implementuje, INotifyPropertyChangedco oznacza, że ​​w świecie WPF w zakresie wiązania danych ma także dodatkowe zalety, które nie Dictionary<K,V>są w stanie zapewnić.

Drew Marsh
źródło
19

Chodzi o wygodę programisty. Mogę sobie wyobrazić pisanie szybkich i brudnych programów z tym obiektem.

ChaosPandion
źródło
9
@JOT. Hendrix, nie zapominaj, że on także powiedział „brudny”. Intellisense ma jednak swoją wadę, ale ułatwia debugowanie i wykrywanie błędów. Osobiście nadal wolę statyczne typy dynamiczne, chyba że mam do czynienia z dziwnym (i zawsze rzadkim) przypadkiem.
Phil,
+1 dla wygody. Uważam jednak, że anonimowe typy mogą być równie wygodne jak zwykła torba na nieruchomości, a po prostu lepsze ze względu na jej statyczność.
nawfal
1
Nie chciałbym używać go w kodzie produkcyjnym, ale jest to bardzo wygodne w kodzie testowym i może sprawić, że będzie wyglądać bardzo pięknie.
Tobias
14

Myślę, że przyniesie to korzyści składniowe, ponieważ nie będziesz już „fałszował” dynamicznie dodawanych właściwości za pomocą słownika.

Myślę, że i interakcja z dynamicznymi językami.

gn22
źródło
11

Jest to przykład świetnego artykułu MSDN na temat używania ExpandoObject do tworzenia dynamicznych typów ad-hoc dla przychodzących danych strukturalnych (np. XML, Json).

Możemy również przypisać delegata do właściwości dynamicznej 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);

Dzięki temu możemy wprowadzić logikę do obiektu dynamicznego w czasie wykonywania. Dlatego wraz z wyrażeniami lambda, zamknięciami, słowem kluczowym dynamicznym i klasą DynamicObject możemy wprowadzić pewne elementy programowania funkcjonalnego do naszego kodu C #, który znamy z języków dynamicznych, takich jak JavaScript lub PHP.

sgnsajgon
źródło
4

W niektórych przypadkach jest to przydatne. Użyję go na przykład do powłoki modułowej. Każdy moduł definiuje własne okno dialogowe Konfiguracja powiązane z jego ustawieniami. Dostarczam mu ExpandoObject, ponieważ jest to tekst danych i zapisuję wartości w mojej konfiguracji Storage. W ten sposób moduł zapisujący okno dialogowe konfiguracji musi po prostu powiązać się z wartością, która zostanie automatycznie utworzona i zapisana. (I dostarczone do modułu do korzystania z tych ustawień oczywiście)

Jest po prostu łatwiejszy w użyciu niż słownik. Ale każdy powinien mieć świadomość, że wewnętrznie jest to tylko słownik.

To jest jak cukier syntaktyczny LINQ, ale czasem to ułatwia.

Aby odpowiedzieć bezpośrednio na pytanie: łatwiej jest pisać i czytać. Ale technicznie zasadniczo jest to Dictionary<string,object>(Możesz nawet rzucić go w jedno, aby wyświetlić wartości).

n1LWeb
źródło
-1
var obj = new Dictionary<string, object>;
...
Console.WriteLine(obj["MyString"]);

Myślę, że to działa tylko dlatego, że wszystko ma funkcję ToString (), w przeciwnym razie musisz znać typ, który był, i rzutować „obiekt” na ten typ.


Niektóre z nich są przydatne częściej niż inne, staram się być dokładny.

  1. Dostęp do kolekcji może być znacznie bardziej naturalny, w tym przypadku jest to „słownik”, przy użyciu bardziej bezpośredniej notacji kropkowej.

  2. Wygląda na to, że można to wykorzystać jako naprawdę niezłą Tuple. Nadal możesz nazywać swoich członków „Przedmiotem 1”, „Przedmiotem 2” itd. ... ale teraz nie musisz, jest to również zmienne, w przeciwieństwie do Tuple. Ma to ogromną wadę polegającą na braku wsparcia dla technologii Intellisense.

  3. Możesz czuć się niekomfortowo z „nazwami członków jako ciągami”, podobnie jak w przypadku słownika, możesz poczuć, że jest to zbyt podobne do „wykonywania ciągów” i może prowadzić do zakodowania konwencji nazewnictwa i radzenia sobie z pracą z morfemami i sylaby, gdy kod próbuje zrozumieć, jak używać członków :-P

  4. Czy możesz przypisać wartość do samego ExpandoObject lub tylko do jego członków? Porównaj i kontrastuj z dynamicznym / dynamicznym [], użyj tych, które najlepiej odpowiadają Twoim potrzebom.

  5. Nie sądzę, żeby dynamiczny / dynamiczny [] działał w pętli foreach, musisz użyć var, ale prawdopodobnie możesz użyć ExpandoObject.

  6. Nie możesz używać dynamiki jako elementu danych w klasie, być może dlatego, że jest to coś w rodzaju słowa kluczowego, mam nadzieję, że możesz to zrobić dzięki ExpandoObject.

  7. Spodziewam się, że „jest” ExpandoObject, może być użyteczne do oznaczania bardzo ogólnych rzeczy osobno, z kodem, który różnicuje się w zależności od typów, w których używa się wielu dynamicznych rzeczy.


Bądź miły, jeśli możesz przejść wiele poziomów jednocześnie.

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

To nie jest najlepszy możliwy przykład, wyobraź sobie eleganckie zastosowania odpowiednie we własnych projektach.

Szkoda, że ​​niektóre z nich nie mogą zbudować kodu i przesunąć wyniki na inteligencję. Nie jestem jednak pewien, jak to by działało.

Bądź miły, jeśli mogą mieć wartość tak samo jak członkowie.

var fifteen = new ExpandoObject();
fifteen = 15;
fifteen.tens = 1;
fifteen.units = 5;
fifteen.ToString() = "fifteen";
etc...
alan2here
źródło
-3

Po valueTuples do czego służy klasa ExpandoObject? ten 6-liniowy kod z ExpandoObject:

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

można zapisać w jednym wierszu z krotkami:

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

oprócz składni krotek masz silne wnioskowanie o typie i wsparcie intlisense

Inż. M.Hamdy
źródło
1
Twoje przykłady nie są identyczne w tym sensie, że przy krotce wartości nie możesz napisać Tc = 5; po zakończeniu zdefiniuj T. Dzięki ExpandoObject możesz to zrobić, ponieważ jest dynamiczny. Twój przykład z krotką wartości jest bardzo identyczny z deklarowaniem typu anonimowego. Np .: var T2 = nowy {x = 1, y = 2, z = nowy {a = 3, b = 4}};
LxL
Dlaczego muszę pisać Tc = 5 bez jej definiowania? ExpandoObject jest użyteczny tylko w przypadku obiektów COM, które nie są defiendami w .net. W przeciwnym razie nigdy nie używam tego ExpandoObject, ponieważ jest on brudny i zawiera błędy zarówno w czasie projektowania, jak i w czasie wykonywania.
inż. M.Hamdy
1
Co powiesz na to, aby najpierw przypisać Z do (a: 3, b: 4), a następnie chciałbyś, aby z miał dodatkową właściwość c? Czy możesz to zrobić z krotką wartości?
LxL
Chodzi mi o to, że nie można porównywać ExpandoObject z krotką wartości, ponieważ są one przeznaczone do różnych celów. Porównując swoją drogę, odrzuciłeś funkcjonalność, dla której zaprojektowano ExpandoObject, czyli strukturę dynamiczną.
LxL