C# in Depth

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 ICollection.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(System.Collections.Generic.ICollection)' 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. Also note that a parameter with a type of dynamic is equivalent to a parameter with a type of object as far as the calling code is concerned.

Finally, remember that when the value of a dynamic expression being used for an argument is null, the overload resolution will be roughly equivalent to just passing in the literal null as the argument... there's no other type information available to help the compiler work out what you want, so you can very easily end up with ambiguity.

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.

Generics

Dynamic typing is restricted by generics, in that even though you can convert an expression of type dynamic to any other (non-pointer) type, that doesn't apply when dynamic is a type argument - the type Foo<dynamic> is statically bound, not dynamically bound. Here's an example with LINQ, where we're squaring some numbers. We know the result will really be a list of integers, to we try assigning to a List<int>:

IEnumerable<dynamic> numbers = new dynamic[] { 42 };
List<int> squares = numbers.Select(d => d * d).ToList();

That doesn't work, because the result of the ToList call is actually a List<dynamic>, which will be a List<object> as far as the CLR is concerned. We have to cast each element to int instead, either by casting in the lambda expression or using the Cast method:

IEnumerable<dynamic> numbers = new dynamic[] { 42 };
List<int> squares = numbers.Select(d => d * d).Cast<int>().ToList();

Tasks provide another example of this:

private static int Square(int number)
{
    return number * number;
}
 
static void Main(string[] args)
{
    dynamic number = 42;
 
    // Invalid: no conversion from Task<dynamic> to Task<int>
    Task<int> task = Task.Run(() => Square(number));
 
    Task<int> valid = Task.Run(new Func<int>(() => Square(number)));
    Task<int> alsoValid = Task.Run(() => (int)Square(number));
}

Here the expression Square(number) is of type dynamic, because almost all expressions using dynamic values have a dynamic result type. So the first call to Task.Run has a return type of Task<dynamic> which in turn can't be converted to Task<int>.

WCF, IClientChannel and proxies

WCF uses RealProxy for service proxies. Amazingly enough, RealProxy does work with dynamic, so long as a call to GetType returns the right type. The proxy returned by WCF implements both IClientChannel and the API interface of the service you're proxying - but GetType only returns the API interface. So if you try to call any of the members of IClientChannel via a dynamic value, it fails. This is similar to explicit interface implementation - it's as if the proxy implemented IClientChannel explicitly.

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.