C# Dynamic Method Invocation at Runtime
c# dynamic method invocation: Learn how to invoke methods dynamically in C# using reflection, the dynamic keyword, and cached delegates, with practical tradeoffs and p...
You have a method name stored in a string, and you need to call it at runtime. This is the core of C# dynamic method invocation. The method might come from user input, a configuration file, or a plugin system. The challenge is that the compiler cannot verify the method exists or that the arguments match, so you must rely on runtime mechanisms. This article covers the two primary approaches—reflection and the dynamic keyword—and explains when each makes sense, along with performance and maintainability tradeoffs.
Using Reflection to Invoke a Method by Name
Reflection is the most direct way to call a method when you only know its name at runtime. You start with a Type object, call GetMethod to obtain a MethodInfo, and then invoke it with Invoke. Here is a minimal example:
using System.Reflection; public class Calculator { public int Add(int a, int b) => a + b; } var type = typeof(Calculator); var method = type.GetMethod("Add"); var instance = new Calculator(); int result = (int)method.Invoke(instance, new object[] { 3, 4 }); Console.WriteLine(result); // 7
The Invoke method accepts the target instance and an object[] containing the arguments. The return value is also an object, so you must cast it to the expected type. This works, but it has a few important characteristics. First, GetMethod returns null if the method does not exist, so you need a null check. Second, overload resolution is not automatic; if multiple methods share the name, you must specify parameter types using GetMethod(name, new[] { typeof(int), typeof(int) }) or use GetMethods and filter manually. Third, the call is late-bound, so the compiler cannot catch mistakes in method names or argument types.
Reflection is flexible and works with any public method, including static methods. For static methods, pass null as the target instance. The overhead of reflection is significant compared to a direct call, but for occasional invocations it is often acceptable.
The dynamic Keyword for Late-Bound Calls
C# provides the dynamic keyword, which shifts type checking to runtime. When you call a method on a dynamic object, the runtime uses the Dynamic Language Runtime (DLR) to resolve the call. This is simpler than reflection for many cases:
dynamic calculator = new Calculator(); int result = calculator.Add(3, 4); Console.WriteLine(result); // 7
With dynamic, the method name and arguments are resolved at runtime. The syntax is identical to a normal method call, which makes the code easier to read. However, dynamic requires the object to support dynamic dispatch. For plain .NET classes, the DLR falls back to reflection-like semantics, but it also works with objects that implement IDynamicMetaObjectProvider, such as ExpandoObject or objects from dynamic languages like Python.
A major limitation of dynamic is that it does not work with extension methods, and it can be slower than a direct call because of the runtime binding overhead. It also disables IntelliSense and compile-time checking for the affected expressions. If the method does not exist or the arguments are wrong, you get a RuntimeBinderException at runtime.
Comparing Reflection and dynamic for Runtime Invocation
The choice between reflection and dynamic depends on your needs. The following table summarizes the key differences:
| Criterion | Reflection | dynamic |
|---|---|---|
| Syntax | Verbose, explicit | Natural, concise |
| Overload resolution | Manual (specify parameter types) | Automatic at runtime |
| Error type | TargetInvocationException wraps | RuntimeBinderException |
| Performance | Slower, but can be cached | Slower, with DLR overhead |
| Type safety | None at compile time | None at compile time |
| Extension methods | Supported via Invoke | Not supported |
| DLR interop | Limited | Full |
Reflection gives you more control. You can inspect method metadata, handle overloads explicitly, and cache the MethodInfo for repeated calls. dynamic is more readable and works well when the object is known to be dynamic, but it hides the complexity and can be harder to debug.
Performance Considerations and Caching
Reflection is notoriously slow because every Invoke call performs argument boxing, permission checks, and method lookup. If you need to invoke the same method repeatedly, you should cache the MethodInfo or, better, compile a delegate. The Delegate.CreateDelegate method can create a strongly typed delegate from a MethodInfo:
var method = typeof(Calculator).GetMethod("Add"); var func = (Func<Calculator, int, int, int>)Delegate.CreateDelegate( typeof(Func<Calculator, int, int, int>), method); var calc = new Calculator(); int result = func(calc, 3, 4);
This delegate is nearly as fast as a direct method call because the binding happens once. For methods with unknown signatures, you can use expression trees to build a compiled delegate that handles arbitrary argument lists. This is more complex but avoids repeated reflection overhead. The dynamic keyword also caches binding sites internally, so repeated calls on the same dynamic object are faster than the first call, but still slower than a compiled delegate.
When performance is critical, measure the actual impact. In most applications, dynamic invocation is a small fraction of the total work, but in a tight loop it can become a bottleneck. Caching the delegate or using a compiled expression tree is the recommended approach for high-frequency calls.
Handling Overloads, Parameters, and Return Values
Dynamic invocation often breaks when the method has overloads or when arguments need conversion. With reflection, you must supply the exact parameter types to GetMethod. If the method has optional parameters or params arrays, reflection does not apply default values automatically; you must provide all arguments. The dynamic keyword handles overload resolution using the runtime types of the arguments, which is more convenient but can still throw if no overload matches.
Return values are always object in reflection, so you need to cast. If the method returns void, Invoke returns null. For methods that throw exceptions, reflection wraps them in TargetInvocationException; you must inspect the inner exception to get the original error. With dynamic, the original exception is thrown directly, which is often easier to handle.
Consider a method that takes a double but you have an int. Reflection will not convert automatically; you must pass a double. dynamic will perform numeric conversions as defined by the runtime binder, which can be convenient but may lead to unexpected behavior if the conversion is ambiguous.
When Dynamic Invocation Is the Wrong Choice
Dynamic invocation is a powerful tool, but it comes with significant costs. It bypasses compile-time type safety, making your code more fragile. A typo in a method name becomes a runtime error, not a compile error. This is especially risky in large codebases where refactoring tools cannot track dynamic calls. If the set of methods is known in advance, consider using an interface, a delegate, or a dictionary of delegates instead.
For example, if you need to map a string to a method, you can build a Dictionary<string, Func<...>> at startup. This is type-safe, fast, and easy to maintain. Only use dynamic invocation when the method set is truly open-ended, such as in a plugin system where plugins are loaded from external assemblies and their APIs are not known at compile time.
Another alternative is source generators, which can generate strongly typed invocation code at compile time based on metadata. This gives you the flexibility of runtime discovery without the performance and safety penalties. However, source generators require the method set to be known at compile time, so they do not replace reflection for fully dynamic scenarios.
Production Considerations and Error Handling
In production, dynamic invocation can fail in ways that are hard to diagnose. A method might be removed from an assembly, a parameter type might change, or a plugin might be missing a dependency. Always validate the MethodInfo or the dynamic call before relying on it. For reflection, check for null and catch TargetInvocationException to unwrap the inner exception. For dynamic, catch RuntimeBinderException and log the method name and argument types.
Security is another concern. If you invoke methods based on user input, you must ensure the method is safe to call. Reflection can access private methods if you use BindingFlags.NonPublic, which can be a security risk. Restrict the types and methods you allow, and consider using a permission set or a sandbox. The dynamic keyword does not add any security boundary; it only changes how the call is bound.
Finally, consider the maintainability of dynamic code. The reader cannot see what method is called or what arguments are passed. Add clear comments and logging around dynamic invocations to compensate. Prefer explicit code whenever the method set is stable. Dynamic invocation is a tool for specific situations, not a general-purpose pattern.