Dynamic Binding of Static and Instance Methods

March 10th 2014 C#

Take a close look at the following piece of code:

String String = "";

var strTest = "TEST";
Console.Write(String.Format(strTest));

dynamic dynTest = "TEST";
Console.Write(String.Format(dynTest));

Think about it for a minute and try answering the following questions:

  • Is it going to compile without errors?
  • Is it going to run without errors?
  • What will the output be?

Feel free to open Visual Studio and compile the code, but do try answering the questions first.

Here are the correct answers for those of you who believe me without testing it yourself – the code will compile, but it will throw a runtime exception at the last line:

Member 'string.Format(string, params object[])' cannot be accessed with an instance reference; qualify it with a type name instead.

Why does the error happen? Let's take a look at how .NET Reflector decompiles the above code:

string str = "";
string format = "TEST";
Console.Write(string.Format(format, new object[0]));
object obj2 = "TEST";
Console.Write(str.Format((dynamic) obj2));

That makes it much clearer, doesn't it?

  • In the first case Format is called on the string type as expected.
  • In the second case Format is called on the local instance of string. Since there is no instance method on string type named Format and since C# doesn't allow calling static methods with an instance reference, this results in a runtime error.

Obviously, the compiler doesn't bind that second call as one would naively expect. Even worse; Visual Studio interprets the code differently than the compiler: in IDE String in the second call is shown as the type, not the variable.

Of course, the behavior can be explained, no matter how unintuitive it is. C# language specification comes to the rescue. Let's start with a quote from section 7.2: Static and Dynamic Binding:

However, if an expression is a dynamic expression (i.e. has the type dynamic) this indicates that any binding that it participates in should be based on its run-time type (i.e. the actual type of the object it denotes at run-time) rather than the type it has at compile-time. The binding of such an operation is therefore deferred until the time where the operation is to be executed during the running of the program. This is referred to as dynamic binding.

In our example dynTest is a dynamic expression, therefore it makes String.Format binding dynamic as well. This explains, why the exception is thrown at runtime.

Still, why does it work correctly in the first case? While I'm pretty sure the answer to this question can be found in the language specification as well, it is much better described in Eric Lippert's blog post:

So the reason that the compiler does not remove static methods when calling through an instance is because the compiler does not necessarily know that you are calling through an instance. Because there are situations where it is ambiguous whether you're calling through an instance or a type, we defer deciding which you meant until the best method has been selected.

That's the case for the first call in our example. String is ambiguous and could be both a local variable or a type. The compiler correctly selected the static method as the best candidate function member. In the second case the binding has been postponed till runtime, and the best candidate function member hasn't been selected at all. Therefore the compiler decided that String is a local variable name, causing the runtime binding to fail because the best candidate function member is static and can't be called with the instance reference of the local variable. Mystery solved.

If you haven't done so already, do read the above mentioned Eric Lippert's blog post; it is well worth your time and will give an even better insight into binding.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License