C# Dynamic Runtime Binding Explained with Examples
c# dynamic runtime binding: Understand C# dynamic runtime binding: how the dynamic keyword defers member resolution to runtime, the DLR's role, and the performance tra...
C# dynamic runtime binding lets the compiler defer member resolution until execution time. When you declare a variable with the dynamic keyword, the compiler stops verifying that the methods, properties, and operators you use actually exist on the object's type. Instead, the runtime inspects the object's actual type at the call site and decides which member to invoke.
What Changes When You Use the dynamic Keyword
With normal static binding, the compiler resolves every member access against the declared type. If you write order.Total, the compiler checks that Order has a Total property and emits a direct call. Any mismatch produces a compile-time error.
With dynamic, the declared type is effectively object, and the compiler emits a call site that defers resolution to runtime. The same expression order.Total compiles even if Order has no such property. The error, if any, appears when the code runs.
dynamic order = GetOrder(); decimal total = order.Total; // resolved at runtime
The compiler also defers operator resolution. Arithmetic, comparison, and conversion operations on dynamic operands are bound at runtime, which means expressions like dynamicValue + 1 behave according to the runtime type of dynamicValue.
How the Runtime Resolves Members
C# dynamic binding is built on the Dynamic Language Runtime (DLR). When the compiler encounters a dynamic expression, it creates a call site that the DLR uses to find the target member. The DLR first checks whether the object implements IDynamicMetaObjectProvider. If it does, the object itself supplies the binding logic. That is how types like ExpandoObject and DynamicObject define custom behavior.
If the object is a plain .NET type, the DLR falls back to reflection-based binding. It inspects the runtime type, locates the matching method or property, and invokes it. The binder also applies overload resolution, so calling a method with an int argument selects the overload that matches an int parameter.
The DLR caches the binding result at the call site. A second call with the same runtime type reuses the cached binding instead of repeating the reflection lookup. This caching is why repeated dynamic calls are cheaper than the first call, though still not as fast as statically bound calls.
Basic Syntax and Behavior
The dynamic keyword behaves like a type in declarations, but it is not a real type in the CLR sense. At runtime, a dynamic variable is an object reference with a special compiler-generated call site attached to each usage.
public class Customer { public string Name { get; set; } public string Region { get; set; } } dynamic customer = new Customer(); customer.Name = "Ada"; Console.WriteLine(customer.Name);
This compiles and runs correctly because Customer has a Name property. The runtime binder finds the property and performs the assignment. If Customer lacked Name, the assignment would throw a RuntimeBinderException.
Dynamic binding also works with methods:
dynamic calculator = new Calculator(); int result = calculator.Add(3, 4);
The binder selects the Add overload whose parameters match int, int. If no matching overload exists, the call throws at runtime.
Practical Use Cases for Dynamic Binding
The most common reason to reach for dynamic binding is interop with systems that do not expose static types. COM automation objects, such as those in Microsoft Office, are a classic example. Their members are not visible to the C# compiler, so dynamic binding is the practical way to call them without writing large reflection wrappers.
Dynamic binding also simplifies working with data whose shape is known only at runtime. JSON payloads from external APIs, when parsed into ExpandoObject or DynamicObject instances, can be accessed with property syntax instead of dictionary lookups.
dynamic config = JsonConvert.DeserializeObject<ExpandoObject>(json); string host = config.server.host;
In this case the binder walks the nested dynamic objects and resolves server and host at runtime. The same code with a statically typed dictionary would require explicit casting and key checks at every level.
Dynamic binding can also replace reflection when you control the call site and want cleaner syntax. A reflection call like typeof(Order).GetProperty("Total").GetValue(order) becomes order.Total when order is dynamic. The runtime cost is similar, but the code is more readable.
Handling Binding Failures
When the runtime cannot resolve a member, it throws RuntimeBinderException. This can happen for several reasons: the property does not exist, the method name is wrong, the argument types do not match any overload, or the object is null.
dynamic value = GetValue(); try { value.MissingMethod(); } catch (RuntimeBinderException ex) { Console.WriteLine($"Binding failed: {ex.Message}"); }
Because the failure happens at runtime, you lose the compiler's early feedback. A typo in a property name that would be caught at compile time with a static type becomes a runtime exception with dynamic binding. This is the main reason to keep dynamic usage narrow and well tested.
Null values deserve particular attention. Calling a member on a null dynamic variable throws a RuntimeBinderException rather than a NullReferenceException in some binder paths, which can be confusing when debugging. Check for null explicitly before invoking members on dynamic values that may be absent.
Performance and Runtime Cost
Dynamic dispatch is slower than static dispatch. The compiler cannot emit a direct call to the target method, so every dynamic expression goes through the DLR binder and the call site machinery. The first call for a given runtime type pays the full binding cost, including reflection and overload resolution. Subsequent calls reuse the cached binding, but still incur indirection that a statically bound call does not.
The practical impact depends on how often the dynamic path executes. For a COM interop call made once per user action, the overhead is negligible. For a hot loop that performs millions of dynamic property accesses per second, the difference is measurable and usually worth avoiding.
If you need dynamic behavior but the object's type is known to be one of a few fixed types, consider testing the type explicitly and casting:
if (value is Order order) { return order.Total; }
This restores static binding and removes the DLR overhead entirely.
When to Avoid Dynamic Binding
Dynamic binding trades compile-time safety for flexibility. Use it where the flexibility is real: COM interop, dynamic languages, or data structures whose shape is genuinely unknown until runtime. Avoid it where a static type, interface, or generic would express the contract just as well.
A common mistake is using dynamic to work around a missing interface. If every consumer of a method needs the same members, define an interface and accept that interface as the parameter type. The compiler then enforces the contract, and callers remain statically bound.
Dynamic binding also complicates maintenance. Renaming a member on the underlying type does not produce a compile error at dynamic call sites; it produces a runtime failure that may only surface in production. Automated tests that exercise the dynamic paths become the safety net that the compiler would otherwise provide.
When the data is a dictionary or a known serialization shape, prefer typed models or Dictionary<string, object> over dynamic objects. Typed models give you compile-time checks, better editor support, and faster access. Reserve dynamic binding for the cases where a typed model is not available or would require an unreasonable amount of boilerplate.