Table of Contents

Late binding

Late binding is the core of the library: calling a member whose name you have as a string, on a target whose type you may not know, in an accessibility context you choose.

The invocation surface

Call Does
Dynamic.InvokeMember(target, name, args) Calls a method returning a value
Dynamic.InvokeMemberAction(target, name, args) Calls a method returning void
Dynamic.InvokeGet(target, name) Reads a property or field
Dynamic.InvokeSet(target, name, value) Writes a property or field
Dynamic.InvokeGetIndex(target, indexes) Reads an indexer
Dynamic.InvokeSetIndex(target, indexesThenValue) Writes an indexer
Dynamic.InvokeConstructor(type, args) Constructs an instance
Dynamic.InvokeBinaryOperator(lhs, op, rhs) Applies a binary operator
Dynamic.InvokeUnaryOperator(op, arg) Applies a unary operator

InvokeMember and InvokeMemberAction are separate because a call site's return type is part of its signature. Asking for a value from a void method fails at bind time rather than quietly returning null.

Accessibility contexts

By default a call binds in the context of the target's own type. InvokeContext changes that, and it is what lets this library reach members dynamic cannot.

Reaching a non-public member:

var context = InvokeContext.CreateContext(target, typeof(TypeThatCanSeeIt));

var value = Dynamic.InvokeMember(context, "InternalMethod", args);

The second argument names the type whose accessibility applies. Pass a type inside the target's assembly and internal members become reachable.

Static members:

var context = InvokeContext.CreateStatic(typeof(SomeType));

var value = Dynamic.InvokeMember(context, "StaticMethod", args);

This is why plain dynamic cannot substitute for Dynamitey in these cases: dynamic always binds in your assembly's context, and there is no way to tell it otherwise.

Named arguments

InvokeArg passes an argument by name:

Dynamic.InvokeMember(target, "Method", new InvokeArg("paramName", value));

InvokeArg.Create is a Func form of the same thing, useful where a factory is more convenient than a constructor. A KeyValuePair<string, object> converts explicitly, so a dictionary of arguments can be projected straight into named arguments.

Discovering what is there

var names = Dynamic.GetMemberNames(target);
var dynamicOnly = Dynamic.GetMemberNames(target, dynamicOnly: true);

dynamicOnly: true restricts the result to members the target reports dynamically — what a DynamicObject or ExpandoObject chooses to expose — rather than everything reflection can see.

Extension methods as instance methods

dynamic query = Dynamic.Linq(new List<int> { 1, 2, 3 });

var count = query.Count();

foreach (var item in (System.Collections.IEnumerable)query)
{
    // 1, 2, 3
}

Dynamic.Linq wraps a sequence so that System.Linq.Enumerable's extension methods can be called as though they were instance members — which is what makes them reachable dynamically at all, since extension methods cannot be dynamically dispatched by the C# binder.

Limits worth knowing

Argument-count ceilings. The generated call-site delegate types cover argument counts up to a fixed ceiling. Beyond it, a different code path runs. This was the cause of a long-standing bug where InvokeConstructor failed above 14 arguments, fixed in 4.0.0.

Trimming and AOT. Every entry point here carries [RequiresUnreferencedCode] and [RequiresDynamicCode]. This library resolves members by name at runtime, which is precisely what a trimmer cannot see and an AOT compiler cannot generate for.