Różnice między ExpandoObject, DynamicObject i dynamic

170

Jakie są różnice między System.Dynamic.ExpandoObject, System.Dynamic.DynamicObjecti dynamic?

W jakich sytuacjach używasz tych typów?

M4N
źródło

Odpowiedzi:

154

Słowo dynamickluczowe służy do deklarowania zmiennych, które powinny być wiązane późno.
Jeśli chcesz użyć późnego wiązania, dla dowolnego typu rzeczywistego lub wyobrażonego, użyj dynamicsłowa kluczowego, a kompilator zajmie się resztą.

Gdy używasz dynamicsłowa kluczowego do interakcji z normalnym wystąpieniem, DLR wykonuje wywołania z późnym wiązaniem do normalnych metod wystąpienia.

IDynamicMetaObjectProviderInterfejs pozwala klasa przejąć kontrolę nad jego późno związana zachowania.
Kiedy używasz dynamicsłowa kluczowego do interakcji z IDynamicMetaObjectProviderimplementacją, DLR wywołuje IDynamicMetaObjectProvidermetody, a sam obiekt decyduje, co zrobić.

ExpandoObjectI DynamicObjectzajęcia są implementacje IDynamicMetaObjectProvider.

ExpandoObjectto prosta klasa, która umożliwia dodawanie członków do instancji i używanie ich jako dynamicsojuszników.
DynamicObjectto bardziej zaawansowana implementacja, którą można odziedziczyć w celu łatwego zapewnienia dostosowanego zachowania.

SLaks
źródło
2
Jakie byłoby dobre miejsce, aby dowiedzieć się więcej na ten temat? Nie API, ale dlaczego za API? np. Dlaczego ExpandoObject nie pochodzi od DynamicObject, który wygląda na typ bazowy defacto dla programowania opartego na „braku metody” w Ruby.
Gishu,
4
Czy mógłbyś dodać kilka przykładów użycia, jeśli to możliwe? Na przykład, w jaki sposób powinienem używać DynamicObject i jakie są z tego korzyści?
2014
10
Świetne odpowiedzi bez takich przykładów są jak ciasto bez kremu na wierzchu.
Teoman shipahi
68

Postaram się udzielić jaśniejszej odpowiedzi na to pytanie, aby jasno wyjaśnić, jakie są różnice między dynamiką ExpandoObjecta DynamicObject.

Bardzo szybko dynamicto słowo kluczowe. To nie jest typ jako taki. Jest to słowo kluczowe, które mówi kompilatorowi, aby ignorował sprawdzanie typu statycznego w czasie projektowania i zamiast tego używał późnego wiązania w czasie wykonywania. Więc nie będziemy spędzać dużo czasu dynamicw dalszej części tej odpowiedzi.

ExpandoObjecti DynamicObjectrzeczywiście są typami. NA POWIERZCHNI wyglądają bardzo podobnie do siebie. Obie klasy implementują IDynamicMetaObjectProvider. Jednak kop głębiej, a przekonasz się, że wcale NIE są podobne.

DynamicObject jest częściową implementacją, która IDynamicMetaObjectProviderma być wyłącznie punktem wyjścia dla programistów do implementacji własnych niestandardowych typów obsługujących dynamiczne wysyłanie z niestandardowym podstawowym zachowaniem pamięci i pobierania, aby dynamiczna wysyłka działała.

  1. DynamicObject nie można skonstruować bezpośrednio.
  2. MUSISZ rozszerzyć DynamicObject, aby mieć jakikolwiek użytek dla Ciebie jako programisty.
  3. Po rozszerzeniu DynamicObject możesz teraz zapewnić NIESTANDARDOWE zachowanie dotyczące sposobu, w jaki chcesz, aby dynamiczne wysyłanie rozwiązywało dane przechowywane wewnętrznie w podstawowej reprezentacji danych w czasie wykonywania.
  4. ExpandoObject przechowuje podstawowe dane w słowniku itp. Jeśli zaimplementujesz DynamicObject, możesz przechowywać dane gdziekolwiek i jak chcesz. (np. sposób pobierania i ustawiania danych przy wysyłce zależy wyłącznie od Ciebie).

Krótko mówiąc, użyj DynamicObject, gdy chcesz stworzyć własne typy, które mogą być używane z DLR i pracować z dowolnymi NIESTANDARDOWYMI zachowaniami, jakie chcesz.

Przykład: Wyobraź sobie, że chciałbyś mieć typ dynamiczny, który zwraca niestandardową wartość domyślną za każdym razem, gdy podejmowana jest próba pobrania na elemencie, który NIE istnieje (tj. Nie został dodany w czasie wykonywania). Domyślnie powie: „Przepraszam, w tym słoiku nie ma ciasteczek!”. Jeśli chcesz dynamicznego obiektu, który zachowuje się w ten sposób, musisz kontrolować, co się dzieje, gdy pole nie zostanie znalezione. ExpandoObject nie pozwoli ci tego zrobić. Musisz więc utworzyć własny typ z unikalnym dynamicznym zachowaniem rozdzielczości elementu członkowskiego (wysyłką) i użyć go zamiast gotowego ExpandoObject.

Możesz utworzyć typ w następujący sposób: (Uwaga, poniższy kod jest tylko ilustracją i może nie działać. Aby dowiedzieć się, jak prawidłowo używać DynamicObject, istnieje wiele artykułów i samouczków w innych miejscach).

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

Teraz moglibyśmy użyć tej wyimaginowanej klasy, którą właśnie utworzyliśmy jako typ dynamiczny, który ma bardzo niestandardowe zachowanie, jeśli pole nie istnieje.

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!")

ExpandoObjectjest PEŁNĄ implementacją IDynamicMetaObjectProvider, w której zespół .NET Framework podjął wszystkie te decyzje za Ciebie. Jest to przydatne, jeśli nie potrzebujesz żadnego niestandardowego zachowania i uważasz, że ExpandoObject działa dla Ciebie wystarczająco dobrze (w 90% przypadków ExpandoObjectjest wystarczająco dobry). Na przykład zobacz poniższe, a dla ExpandoObject projektanci zdecydowali się zgłosić wyjątek, jeśli dynamiczny element członkowski nie istnieje.

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) { ... }

Podsumowując, ExpandoObjectjest to po prostu jeden z wstępnie wybranych sposobów rozszerzenia DynamicObject o pewne dynamiczne zachowania wysyłania, które prawdopodobnie będą dla Ciebie odpowiednie , ale mogą nie zależeć od Twoich konkretnych potrzeb.

Natomiast DyanmicObjectjest to pomocnik BaseType, który sprawia, że ​​implementowanie własnych typów z unikalnymi dynamicznymi zachowaniami jest proste i łatwe.

Przydatny samouczek, na którym opiera się większość powyższego przykładowego źródła.

Ayo I
źródło
Bardzo dobre wyjaśnienie. Tylko jedna poprawka techniczna: ExpandoObject nie dziedziczy po DynamicObject.
Mike Rosoft
Mała poprawka na przykładzie dla DynamicObject: podczas zastępowania TryGetMember, jeśli zwrócisz false, RuntimeBinderExceptionzostanie wyrzucony podczas próby dostępu do nieistniejącej właściwości. Aby fragment faktycznie działał, należy wrócić true.
lluchmk
36

Zgodnie ze specyfikacją języka C # dynamicjest to deklaracja typu. Oznacza dynamic xto, że zmienna xma typ dynamic.

DynamicObjectjest typem, który ułatwia implementację, IDynamicMetaObjectProvidera tym samym zastępuje specyficzne zachowanie wiązania dla typu.

ExpandoObjectjest typem, który działa jak worek mienia. Oznacza to, że możesz dodawać właściwości, metody i tak dalej do dynamicznych instancji tego typu w czasie wykonywania.

Brian Rasmussen
źródło
25
dynamicnie jest rzeczywistym typem ... jest to tylko wskazówka, aby powiedzieć kompilatorowi, aby używał późnego wiązania dla tej zmiennej. dynamiczmienne są faktycznie deklarowane jak objectw MSIL
Thomas Levesque,
1
@Thomas: z punktu widzenia kompilatora jest to typ, ale masz rację, że reprezentacja środowiska wykonawczego jest reprezentacją Object. W kilku prezentacjach MS można znaleźć stwierdzenie „wpisane statycznie jako dynamiczne”.
Brian Rasmussen,
3
@Thomas: a specyfikacja języka mówi: „C # 4.0 wprowadza nowy typ statyczny o nazwie dynamiczny”.
Brian Rasmussen,
rzeczywiście ... Ale myślę, że to mylące, aby traktować je jako swego rodzaju, ponieważ nie ma związku z typów takich jak dziedziczenie DynamicObject lub ExpandoObject
Thomas Levesque
3
@NathanA Jestem z tobą tutaj. Jednak specyfikacja języka nazywa to typem, więc to właśnie zamierzam.
Brian Rasmussen
0

Powyższy przykład DynamicObjectnie wyjaśnia wyraźnie różnicy, ponieważ w zasadzie implementuje funkcjonalność, która jest już dostarczona przez ExpandoObject.

W dwóch wymienionych poniżej linkach jest bardzo jasne, że za pomocą DynamicObjectmożna zachować / zmienić rzeczywisty typ ( XElementw przykładzie użytym w poniższych linkach) i lepszą kontrolę właściwości i metod.

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
źródło