Dynamic Binding

Dynamic binding defers binding—the process of resolving types, members, and operations—from compile time to runtime. Dynamic binding was introduced in C# 4.0, and 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.

Static Binding Versus 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 IDynamicMetaObjectProvider, 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

Custom binding occurs when a dynamic object implements IDynamicMetaObjectProvider (IDMOP). Although you can implement IDMOP on types that you write in C#, and this 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 20 of C# 5.0 in a Nutshell.

Language Binding

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 when 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.

Note

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.

Note

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.

RuntimeBinderException

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.

Runtime Representation of dynamic

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

Dynamic Conversions

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

var Versus dynamic

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 is dynamic
var y = "hello";      // Static type is string
int i = x;            // Runtime error
int j = y;            // Compile-time error

Dynamic Expressions

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.

Expressions involving dynamic operands are typically 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.

Dynamic Member Overload Resolution

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.”

Uncallable Functions

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# 5.0, there is 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.250.203