Can the “dynamic” type vary safely in a generic co

2020-08-17 18:05发布

问题:

Based on my answer to this question, I want to check something on my understanding of the upcoming dynamic type for C# 4.

In this case, we have a collection that represents fields in a record pulled from an unknown database table. Older code (pre-.Net 4) requires such a collection hold items of type Object. Merits of a such a collection aside, I'm wondering about what happens when you change Object to dynamic.

On the one hand, I expect that since things for dynamic types are all worked out at runtime that everything should be just fine as long as the programmer doesn't make any typos or mistakes about the expected type of a particular item in the collection.

On the other hand, I wonder about the word "all" in the previous sentence. Would the runtime perhaps cache results from the first time a dynamic property is accessed, causing subsequent calls using different types to fail?

回答1:

Here's a relevant bit from Sam's blog that talks briefly about the caching policy.

http://blogs.msdn.com/samng/archive/2008/10/29/dynamic-in-c.aspx

The DLR checks a cache to see if the given action has already been bound against the current set of arguments. So in our example, we would do a type match based on 1, 2, and the runtime type of d. If we have a cache hit, then we return the cached result. If we do not have a cache hit, then the DLR checks to see if the receiver is an IDynamicObject. These guys are essentially objects which know how to take care of their own binding, such as COM IDispatch objects, real dynamic objects such as Ruby or Python ones, or some .NET object that implements the IDynamicObject interface. If it is any of these, then the DLR calls off to the IDO and asks it to bind the action.

Note that the result of invoking the IDO to bind is an expression tree that represents the result of the binding. If it is not an IDO, then the DLR calls into the language binder (in our case, the C# runtime binder) to bind the operation. The C# runtime binder will bind the action, and will return an expression tree representing the result of the bind. Once step 2 or 3 have happened, the resulting expression tree is merged into the caching mechanism so that any subsequent calls can run against the cache instead of being rebound.

However, what Sam doesn't mention is exactly what the cache miss policy is. There are two main cache-miss policies: (1) trigger a cache miss when the argument types change, (2) trigger a cache miss when the argument identities change.

Obviously the former is far more performant; working out when we can cache based solely on type is tricky. A detailed exegesis of how all that logic works would take rather a long time; hopefully I or Chris or Sam will do a blog post on it one of these days.



回答2:

You can think of dynamic as just syntactic sugar for writing all of the method calls using Reflection and MethodInfo.Invoke() - under the hood it doesn't work exactly like that, but you can think of it working that way, with all of the "calling 1000 methods / sec via dynamic => murdered perf" considerations that go with it.



回答3:

As far as the dictionary / list is concerned, it can just see object. dynamic is largely in the eye of the beholder - i.e. the calling code; under the hood it is "object plus a little sugar". So you shouldn't see any problems here.

Proof:

    static void Main()
    {
        Console.WriteLine(IsObject<int>()); // false
        Console.WriteLine(IsObject<object>()); // true
        Console.WriteLine(IsObject<dynamic>()); // true
        Console.WriteLine(IsObject<string>()); // false
    }
    static bool IsObject<T>()
    {
        return typeof(T) == typeof(object);
    }


回答4:

Okay, rather than wait around for an answer I fired up Visual Studio 2010 beta 2, and this test program runs okay:

class Foo
{
    public string foo = "Foo!";
}
class Bar
{
    public int bar = 42;
}

class Program
{
    static void Main(string[] args)
    {
        var test = new List<dynamic>();
        test.Add(new Foo());
        test.Add(new Bar());

        Console.WriteLine(test[0].foo.Substring(0,3));
        Console.WriteLine(test[1].bar.ToString("000"));

        Console.ReadKey(true);
    }
}

I wanted to make sure that not only did I check on properties with different names, but that they also had different types and that I used a features in each type that are incompatible with each other. This seems to suggest that if anything is cached, the runtime is smart enough to know when to use the cache and when not to. I'd still like to hear if anyone knows an edge case where this might not hold, or a more authoritative comment on why it will.