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