ChatGPT解决这个技术问题 Extra ChatGPT

Differences between ExpandoObject, DynamicObject and dynamic

What are the differences between System.Dynamic.ExpandoObject, System.Dynamic.DynamicObject and dynamic?

In which situations do you use these types?


R
RJFalconer

The dynamic keyword is used to declare variables that should be late-bound.
If you want to use late binding, for any real or imagined type, you use the dynamic keyword and the compiler does the rest.

When you use the dynamic keyword to interact with a normal instance, the DLR performs late-bound calls to the instance's normal methods.

The IDynamicMetaObjectProvider interface allows a class to take control of its late-bound behavior.
When you use the dynamic keyword to interact with an IDynamicMetaObjectProvider implementation, the DLR calls the IDynamicMetaObjectProvider methods and the object itself decides what to do.

The ExpandoObject and DynamicObject classes are implementations of IDynamicMetaObjectProvider.

ExpandoObject is a simple class which allows you to add members to an instance and use them dynamically.
DynamicObject is a more advanced implementation which can be inherited to easily provide customized behavior.


What would be a good place to learn more about this ? Not the API but the why behind the API ? e.g. Why doesn't ExpandoObject derive from DynamicObject, which looks the defacto base type for ruby's 'method_missing' based programming.
Could you add some usage examples where possible? For instance, how would i use a DynamicObject, and what are the benefits?
Great answers without examples like this are like cake without cream on top.
A
Ayo I

I will try to provide a clearer answer to this question, to explain clearly what the differences are between dynamic, ExpandoObject and DynamicObject.

Very quickly, dynamic is a keyword. It is not a type per-se. It is a keyword that tells the compiler to ignore static type checking at design-time and instead to use late-binding at run-time. So we're not going to spend much time on dynamic in the rest of this answer.

ExpandoObject and DynamicObject are indeed types. On the SURFACE, they look very similar to each other. Both classes implement IDynamicMetaObjectProvider. However, dig deeper and you'll find they're NOT similar at all.

DynamicObject is a partial implementation of IDynamicMetaObjectProvider purely meant to be a starting point for developers to implement their own custom types supporting dynamic dispatch with custom underlying storage and retrieval behavior to make dynamic dispatch work.

DynamicObject can't be constructed directly. You MUST extend DynamicObject for it to have any use to you as the developer. When you extend DynamicObject you are now able to provide CUSTOM behavior regarding how you want dynamic dispatch to resolve to data stored internally in your underlying data representation at run-time. ExpandoObject stores underlying data in a Dictionary, etc. If you implement DynamicObject, you can store data wherever and however you like. (e.g. how you get and set the data on dispatch is entirely up to you).

In short, use DynamicObject when you want to create your OWN types that can be used with the DLR and work with whatever CUSTOM behaviors you'd like.

Example: Imagine that you'd like to have a dynamic type that returns a custom default whenever a get is attempted on a member that does NOT exist (i.e. has not been added at run time). And that default will say, "I'm sorry, there are no cookies in this jar!". If you want a dynamic object that behaves like this, you'll need to control what happens when a field is not found. ExpandoObject will not let you do this. So you'll need to create your own type with unique dynamic member resolution (dispatch) behavior and use that instead of the ready-built ExpandoObject.

You can create a type as follows: (Note, the below code is just for illustration and may not run. To learn about how to properly use DynamicObject, there are many articles and tutorials elsewhere.)

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

Now, we could use this imaginary class we just created as a dynamic type that has a very custom behavior if the field doesn't exist.

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

ExpandoObject is a FULL implementation of IDynamicMetaObjectProvider, where the .NET Framework team has made all of these decisions for you. This is useful if you don't need any custom behavior, and you feel that ExpandoObject works good enough for you (90% of the time, ExpandoObject is good enough). So for example, see the following, and that for ExpandoObject, the designers chose to throw an exception if the dynamic member does not exist.

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

So to summarize, ExpandoObject is simply one pre-chosen way to extend DynamicObject with certain dynamic dispatch behaviors that will probably work for you, but may not depending on your particular needs.

Whereas, DyanmicObject is a helper BaseType that makes implementing your own types with unique dynamic behaviors simple and easy.

A useful tutorial on which much of the example source above is based.


Very good explanation. Just one technical correction: ExpandoObject doesn't inherit from DynamicObject.
A small correction on the example for DynamicObject: when overriding TryGetMember, if you return false a RuntimeBinderException will be thrown when trying to access to non-existing property. For the snippet to actually work you should return true.
If all the StackOverflow's answers would be like yours the world would be a better place. Amazing explanation.
B
Brian Rasmussen

According to the C# language specification dynamic is a type declaration. I.e. dynamic x means the variable x has the type dynamic.

DynamicObject is a type that makes it easy to implement IDynamicMetaObjectProvider and thus override specific binding behavior for the type.

ExpandoObject is a type that acts like a property bag. I.e. you can add properties, methods and so forth to dynamic instances of this type at runtime.


dynamic is not an actual type... it's just a hint to tell the compiler to use late binding for this variable. dynamic variables are actually declared as object in MSIL
@Thomas: from the compiler's point of view it is a type, but you're right that the runtime representation is that of Object. You'll find the statement "statically typed to be dynamic" in several MS presentations.
@Thomas: and the language spec says "C# 4.0 introduces a new static type called dynamic".
indeed... But I think it's confusing to consider it as a type, since there is no inheritance relation with types like DynamicObject or ExpandoObject
@NathanA I'm with you here. However, the language specification calls it a type, so that's what I'm going with.
D
Diego Osornio

The above example of DynamicObject does not tell the difference clearly, because it's basically implementing the functionality which is already provided by ExpandoObject.

In the two links mentioned below, it is very clear that with the help of DynamicObject, it is possible to preserve/change the actual type (XElement in the example used in below links) and better control on properties and methods.

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