Gotchas in dynamic typing
The dynamic support in C# is great, but there are some tricky aspects which are worth
knowing about, because they can trip you up easily. If you come across any yourself, please
mail me to let me know, and I'll add them to the list.
Explicit interface implementation
Remember that when the execution-time compiler handles a dynamic expression, it considers
the actual type of the object, rather than anything else you might have known about it.
(It's actually slightly more complicated than that, as the object may be of a type that
the calling code doesn't know about. The compiler works out the "Best Accessible Type" and acts
on that, but most of the time you don't need to worry about this.) It acts as if you'd declared
a variable of that type, and performs the normal steps to work out which execution paths to take.
That's usually fine, but it doesn't play well with explicit interface implementation. If you recall,
explicit interface implementation only allows you to call the implemented member via an expression
which is the interface type. Assuming the actual implementation type is accessible, this means the
execution-time compiler won't find the relevant member. Here's the example I give in the book,
with a slight modification:
static void PrintCount(ICollection collection)
{
dynamic d = collection;
Console.WriteLine("Static typing: {0}", collection.Count);
Console.WriteLine("Dynamic typing: {0}", d.Count);
}
...
PrintCount(new int[10]);
We know for sure that the type has a Count property of some kind,
because it implements ICollection - the second line of the method shows
that we know about that property with static typing. However, when we call the method
with an array, it prints the count using the statically-typed property, but then
throws an exception:
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
'System.Array' does not contain a definition for 'Count'
This is because System.Array implements the Count property
explicitly. We'd have the same problem at compile time if we tried to use the
property via an expression of type Array, for example by changing the type
of the collection parameter:
Test.cs(9,60): error CS1061: 'System.Array' does not contain a definition for
'Count' and no extension method 'Count' accepting a first argument of
type 'System.Array' could be found (are you missing a using directive or
an assembly reference?)
The execution-time compiler is doing exactly what we should expect
it to do - it's just somewhat annoying at the same time. This is a sort of
impedance mismatch between the normal C# type system and dynamic typing.
Overloading ambiguity
One way of avoiding the explicit interface implementation issue is to use dynamic
typing to perform execution-time overload resolution to find the most specific
method - which then uses static typing instead. Again, there's an example of
this in the book, trying to find the length of a sequence efficiently. However, there's
a sneaky problem even in the book's example... the compiler won't always know which overload
to choose. It copes with explicit interface implementation... but even the same
simple attempt with an array will fail, unfortunately:
private static int CountImpl<T>(ICollection<T> collection)
{
return collection.Count;
}
private static int CountImpl(ICollection collection)
{
return collection.Count;
}
public static void PrintCount(IEnumerable collection)
{
dynamic d = collection;
int count = CountImpl(d);
Console.WriteLine(count);
}
...
PrintCount(new int[10]);
Here, int[] implements both ICollection and
ICollection<int> - so we get the following error at execution time:
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
The call is ambiguous between the following methods or properties:
'Test.CountImpl<int>(System.Collections.Generic.ICollection<int>)' and
'Test.CountImpl(System.Collections.ICollection)'
There are ways to overcome this, using some of the oddities of
overloading - but it's not a particularly nice thing to have to do.
Where possible, use concrete classes for the overload parameters - that makes it much less likely
that you'll run into issues around ambiguous overloads.
Compound assignment
This issue bit me when I was writing about Summing a sequence dynamically.
The C# language specification describes (in section 7.17.2) compound assignment operators as either
performing the obvious "execute the operator and then assign the result back to the original variable"
(where the result of the operator is implicitly convertible to the operator's return type)
or (for cases where there's no implicit conversion, but there's an explicit
conversion and another couple of conditions hold) it's the same, but with a cast before the assignment.
That's what lets code like this compile and run:
byte b = 10;
b += 20;
Remember that there's no operator +(byte x, byte y) operator - the above code will
promote the operands to int, use the operator +(int x, int y) and then
cast the result back to byte automatically. It's a neat little convenience provided
by the compiler.
Now consider the following dynamic code:
byte b = 10;
dynamic d = b;
d += 20;
Console.WriteLine(d.GetType());
What should the result be here? Well, the value of d before the compound assignment
is definitely byte... so you might expect the C# compiler to do the same thing. Indeed,
it used to work that way, in some of the beta versions of .NET 4. However, apparently this caused some issues
elsewhere, so it was changed before the final release to not perform the cast - and the result is that
d is an int, so the above code prints System.Int32.
I doubt that this particular gotcha will hit many developers, but it's worth being aware of... it
confused the heck out of me when the behaviour changed.
Anonymous types
You can use anonymous types with dynamic typing, and indeed this is fairly common in ASP.NET MVC. However,
you need to be aware that the "real" types used to implement anonymous types (generated by the compiler) are
internal. Dynamic typing obeys the normal rules of access control, so if you want to use an instance of an
anonymous type dynamically from a different assembly, you need to use [InternalsVisibleTo] in order
to grant access from the assembly containing the type to the assembly using it.
Conclusion
None of the behaviour described on this page should be described as a bug in the C# compiler (either the
"normal" one or the embedded version used for dynamic execution. These are simply things you might
not otherwise think about. You definitely need to be careful when using dynamic typing - it's powerful,
but it doesn't always work exactly as you might expect.