Dynamic Programming in C# (commonly called dynamic typing or late binding) is a programming paradigm that defers type resolution from compile-time to runtime. Its core purpose is to enable more flexible, adaptive code that can handle scenarios where static type information is unavailable or impractical.
Dynamic Keyword
The dynamic keyword tells the compiler to bypass static type checking and resolve method calls, property access, and operations at runtime. The Dynamic Language Runtime (DLR) handles the dispatch.
using System;
class Program
{
static void Main()
{
// Compiler bypasses type checking - resolves at runtime
dynamic dynamicValue = GetDynamicData();
// These calls are resolved at runtime
Console.WriteLine(dynamicValue.Name); // Accesses property dynamically
Console.WriteLine(dynamicValue.Calculate(5)); // Calls method dynamically
// Runtime binding allows flexibility
dynamicValue.NewProperty = "Dynamically added"; // Adding properties dynamically
Console.WriteLine(dynamicValue.NewProperty);
}
static dynamic GetDynamicData()
{
return new { Name = "John", Calculate = new Func<int, int>(x => x * 2) };
}
}
Performance Impact: Dynamic dispatch incurs runtime overhead compared to static binding. Use judiciously in performance-critical code.
ExpandoObject
ExpandoObject is a dynamic object that allows you to add and remove members (properties, methods, events) at runtime. It implements IDynamicMetaObjectProvider and is particularly useful for working with dynamic data structures.
using System;
using System.Dynamic;
class Program
{
static void Main()
{
// Create a fully dynamic object
dynamic person = new ExpandoObject();
// Add properties dynamically
person.Name = "Alice";
person.Age = 30;
// Add methods dynamically using lambda expressions
person.Introduce = new Action(() =>
{
Console.WriteLine($"Hi, I'm {person.Name}, {person.Age} years old");
});
person.Introduce(); // Calls the dynamically added method
// Cast to IDictionary<string, object> for enumeration
var dict = (System.Collections.Generic.IDictionary<string, object>)person;
foreach (var property in dict)
{
Console.WriteLine($"{property.Key}: {property.Value}");
}
}
}
Interoperability: ExpandoObject is excellent for working with JSON data, dynamic languages, and REST APIs where structure isn’t known at compile time.
Expression Trees
Expression trees represent code as data structures that can be examined, modified, and compiled into executable code at runtime. They enable meta-programming and are fundamental to LINQ providers.
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
// Create expression tree: (x, y) => x + y * 2
ParameterExpression paramX = Expression.Parameter(typeof(int), "x");
ParameterExpression paramY = Expression.Parameter(typeof(int), "y");
// Build the expression tree: y * 2
ConstantExpression constant = Expression.Constant(2, typeof(int));
BinaryExpression multiply = Expression.Multiply(paramY, constant);
// Build final expression: x + (y * 2)
BinaryExpression body = Expression.Add(paramX, multiply);
// Create lambda expression
Expression<Func<int, int, int>> lambda =
Expression.Lambda<Func<int, int, int>>(body, paramX, paramY);
// Compile to executable delegate
Func<int, int, int> compiled = lambda.Compile();
// Execute
int result = compiled(5, 3); // 5 + (3 * 2) = 11
Console.WriteLine($"Result: {result}");
// Advanced: Modify expression tree
BinaryExpression newBody = Expression.Add(paramX, paramY); // x + y
Expression<Func<int, int, int>> modifiedLambda =
Expression.Lambda<Func<int, int, int>>(newBody, paramX, paramY);
Func<int, int, int> modifiedCompiled = modifiedLambda.Compile();
Console.WriteLine($"Modified result: {modifiedCompiled(5, 3)}"); // 8
}
}
LINQ Providers: Expression trees are the foundation of LINQ to SQL, Entity Framework, and other query providers that translate C# queries to SQL or other languages.
Why Dynamic Programming is Important
- Duck Typing Principle - Enables “if it walks like a duck” behavior, promoting interface flexibility
- Open/Closed Principle (SOLID) - Allows systems to be extended with new functionality at runtime
- Runtime Adaptability - Facilitates building systems that adapt to changing requirements without recompilation
Advanced Nuances
Custom Dynamic Object Implementation
Beyond ExpandoObject, you can create custom dynamic objects by implementing IDynamicMetaObjectProvider:
public class CustomDynamicObject : IDynamicMetaObjectProvider
{
private readonly Dictionary<string, object> _properties = new();
public DynamicMetaObject GetMetaObject(Expression parameter)
{
return new CustomMetaObject(parameter, BindingRestrictions.Empty, this);
}
// Custom meta object implementation would handle method calls and property access
}
Expression Tree Manipulation for Query Providers
Advanced usage involves building LINQ providers by intercepting and translating expression trees:
// This is the foundation for Entity Framework, LINQ to SQL, etc.
public class CustomQueryProvider : IQueryProvider
{
public IQueryable CreateQuery(Expression expression)
{
// Analyze expression tree and translate to target language (SQL, etc.)
return new CustomQueryable(this, expression);
}
}
Dynamic dispatch incurs runtime overhead. The DLR uses caching strategies (call site caching) to optimize repeated dynamic calls, but understanding when to use dynamic vs. static approaches is crucial.
Roadmap Context
Within the “Reflection and Metadata” section, Dynamic Programming serves as the bridge between compile-time and runtime type manipulation. It’s a prerequisite for:
- Advanced Reflection Patterns - Dynamic programming provides a more elegant API for runtime type manipulation
- Aspect-Oriented Programming - Expression trees enable runtime code generation
- Dynamic Code Generation - Foundation for runtime compilation and execution
- Advanced Interop Scenarios - Essential for working with COM, dynamic languages, and REST APIs