Dynamic binding defers binding—the process of resolving types, members, and operations—from compile time to runtime. Dynamic binding is useful when at compile time you know that a certain function, member, or operation exists, but the compiler does not. This commonly occurs when you are interoperating with dynamic languages (such as IronPython) and COM, and in scenarios when you might otherwise use reflection.
A dynamic type is declared with the contextual keyword dynamic
:
dynamic d = GetSomeObject(); d.Quack();
A dynamic type tells the compiler to relax. We expect the runtime
type of d
to have a Quack
method. We just can’t prove it statically.
Since d
is dynamic, the compiler defers
binding Quack
to d
until runtime. To understand what this means
requires distinguishing between
static binding and dynamic binding.
The canonical binding example is mapping a name to a specific function
when compiling an expression. To compile the following expression, the
compiler needs to find the implementation of the method named Quack
:
d.Quack();
Let’s suppose the static type of d
is Duck
:
Duck d = ... d.Quack();
In the simplest case, the compiler does the binding by looking for
a parameterless method named Quack
on
Duck
. Failing that, the compiler
extends its search to methods taking optional parameters, methods on
base classes of Duck
, and extension
methods that take Duck
as its first parameter. If no match is
found, you’ll get a compilation error. Regardless of what method gets
bound, the bottom line is that the binding is done by the compiler, and
the binding utterly depends on statically knowing the types of the
operands (in this case, d
). This
makes it static binding.
Now let’s change the static type of d
to object
:
object d = ... d.Quack();
Calling Quack
gives us a
compilation error, because although the value stored in d
can contain a method called Quack
, the compiler cannot know it since the
only information it has is the type of the variable, which in this case
is object
. But let’s now change the
static type of d
to dynamic
:
dynamic d = ... d.Quack();
A dynamic
type is like object
—it’s equally nondescriptive about a
type. The difference is that it lets you use it in ways that aren’t
known at compile time. A dynamic object binds at runtime based on its
runtime type, not its compile-time type. When the compiler sees a
dynamically bound expression (which in general is an expression that
contains any value of type dynamic
),
it merely packages up the expression such that the binding can be done
later at runtime.
At runtime, if a dynamic object
implements
IDynamic
Meta
Object
Provider
,
that interface is used to perform the binding. If not, binding occurs in
almost the same way as it would have had the compiler known the dynamic
object’s runtime type. These two alternatives are called
custom binding and language binding.
Custom binding occurs when a dynamic object implements IDynamicMetaObjectProvider
(IDMOP). Although
you can implement IDMOP on types that you write in C#, and that is
useful to do, the more common case is that you have acquired an IDMOP
object from a dynamic language that is implemented in .NET on the
Dynamic Language Runtime (DLR), such as IronPython or IronRuby. Objects
from those languages implicitly implement IDMOP as a means to directly
control the meanings of operations performed on them. Here’s a simple
example:
using System; using System.Dynamic; public class Test { static void Main() { dynamic d = new Duck(); d.Quack(); // Quack was called d.Waddle(); // Waddle was called } } public class Duck : DynamicObject { public override bool TryInvokeMember ( InvokeMemberBinder binder, object[] args, out object result) { Console.WriteLine (binder.Name + " was called"); result = null; return true; } }
The Duck
class doesn’t actually
have a Quack
method. Instead, it uses
custom binding to intercept and interpret all method calls.
We discuss custom binders in greater detail in Chapter 19 of C# 4.0 in a Nutshell (O’Reilly).
Language binding occurs when a dynamic object does not implement IDynamicMetaObjectProvider
. Language binding
is useful when working around imperfectly designed types or inherent limitations in the .NET type
system. A typical problem with using numeric types is that they have no
common interface. We have seen that methods can be bound dynamically;
the same is true for operators:
static dynamic Mean (dynamic x, dynamic y) { return (x + y) / 2; } static void Main() { int x = 3, y = 4; Console.WriteLine (Mean (x, y)); }
The benefit is obvious—you don’t have to duplicate code for each numeric type. However, you lose static type safety, risking runtime exceptions rather than compile-time errors.
Dynamic binding circumvents static type safety, but not runtime type safety. Unlike with reflection, you cannot circumvent member accessibility rules with dynamic binding.
By design, language runtime binding behaves as similarly as
possible to static binding, had the runtime types of the dynamic objects
been known at compile time. In our previous example, the behavior of our
program would be identical if we hardcoded Mean
to work with the int
type. The most notable exception in parity
between static and dynamic binding is for extension methods, which we
discuss in the section Uncallable Functions.
Dynamic binding also incurs a performance hit. Because of the DLR’s caching mechanisms, however, repeated calls to the same dynamic expression are optimized—allowing you to efficiently call dynamic expressions in a loop. This optimization brings the typical overhead for a simple dynamic expression on today’s hardware down to less than 100 ns.
If a member fails to bind, a RuntimeBinderException
is thrown. You can
think of this like a compile-time error at runtime:
dynamic d = 5; d.Hello(); // throws RuntimeBinderException
The exception is thrown because the int
type has no Hello
method.
There is a deep equivalence between the dynamic
and object
types. The runtime treats the following
expression as true
:
typeof (dynamic) == typeof (object)
This principle extends to constructed types and array types:
typeof (List<dynamic>) == typeof (List<object>) typeof (dynamic[]) == typeof (object[])
Like an object reference, a dynamic reference can point to an object of any type (except pointer types):
dynamic x = "hello"; Console.WriteLine (x.GetType().Name); // String x = 123; // No error (despite same variable) Console.WriteLine (x.GetType().Name); // Int32
Structurally, there is no difference between an object reference
and a dynamic reference. A dynamic reference simply enables dynamic
operations on the object it points to. You can convert from object
to dynamic
to perform any dynamic operation you
want on an object
:
object o = new System.Text.StringBuilder(); dynamic d = o; d.Append ("hello"); Console.WriteLine (o); // hello
The dynamic
type has implicit conversions to and from all other types. For
a conversion to succeed, the runtime type of the dynamic object must be
implicitly convertible to the target static type.
The following example throws a RuntimeBinderException
because an int
is not implicitly convertible to a
short
:
int i = 7; dynamic d = i; long l = d; // OK - implicit conversion works short j = d; // throws RuntimeBinderException
The var
and dynamic
types bear a superficial resemblance, but the difference is
deep:
var says, “Let the
compiler figure out the type.” |
dynamic says, “Let the
runtime figure out the type.” |
To illustrate:
dynamic x = "hello"; // Static type isdynamic
var y = "hello"; // Static type isstring
int i = x; // Runtime error int j = y; // Compile-time error
Fields, properties, methods, events, constructors, indexers, operators, and conversions can all be called dynamically.
Trying to consume the result of a dynamic expression with a
void
return type is prohibited—just
as with a statically typed expression. The difference is that the error
occurs at runtime.
Typically, expressions involving dynamic operands are themselves dynamic, since the effect of absent type information is cascading:
dynamic x = 2; var y = x * 3; // Static type of y is dynamic
There are a couple of obvious exceptions to this rule. First, casting a dynamic expression to a static type yields a static expression. Second, constructor invocations always yield static expressions—even when called with dynamic arguments.
In addition, there are a few edge cases where an expression containing a dynamic argument is static, including passing an index to an array and delegate-creation expressions.
The canonical use case for dynamic
involves a dynamic
receiver. This means that a dynamic object is the
receiver of a dynamic function call:
dynamic x = ...; x.Foo (123); // x is the receiver
However, dynamic binding is not limited to receivers: the method arguments are also eligible for dynamic binding. The effect of calling a function with dynamic arguments is to defer overload resolution from compile time to runtime:
class Program { static void Foo (int x) { Console.WriteLine ("1"); } static void Foo (string x) { Console.WriteLine ("2"); } static void Main() { dynamic x = 5; dynamic y = "watermelon"; Foo (x); // 1 Foo (y); // 2 } }
Runtime overload resolution is also called multiple dispatch and is useful in implementing design patterns such as visitor.
If a dynamic receiver is not involved, the compiler can statically perform a basic check to see whether the dynamic call will succeed: it checks that a function with the right name and number of parameters exists. If no candidate is found, you get a compile-time error.
If a function is called with a mixture of dynamic and static arguments, the final choice of method will reflect a mixture of dynamic and static binding decisions:
static void X(object x, object y) {Console.Write("oo");} static void X(object x, string y) {Console.Write("os");} static void X(string x, object y) {Console.Write("so");} static void X(string x, string y) {Console.Write("ss");} static void Main() { object o = "hello"; dynamic d = "goodbye"; X (o, d); // os }
The call to X(o,d)
is
dynamically bound because one of its arguments, d
, is dynamic
. But since o
is statically known, the binding—even though
it occurs dynamically—will make use of that. In this case, overload
resolution will pick the second implementation of X
due to the static type of o
and the runtime type of d
. In other words, the compiler is “as static
as it can possibly be.”
Some functions cannot be called dynamically. You cannot call:
Extension methods (via extension method syntax)
Any member of an interface (via the interface)
Base members hidden by a subclass
This is because dynamic binding requires two pieces of information: the name of the function to call, and the object upon which to call the function. However, in each of the three uncallable scenarios, an additional type is involved, which is known only at compile time. As of C# 4.0, there’s no way to specify these additional types dynamically.
When calling extension methods, that additional type is an
extension class, chosen implicitly by virtue of using
directives in your source code (which
disappear after compilation). When calling members via an interface, the
additional type is communicated via an implicit or explicit cast. (With
explicit implementation, it’s in fact impossible to call a member
without casting to the interface.) A similar situation arises when
calling a hidden base member: you must specify an additional type via
either a cast or the base
keyword—and
that additional type is lost at runtime.