C# 在保持强类型静态语言优势的同时,通过 dynamic 关键字引入了动态编程能力。动态编程在特定场景下极具价值,如与动态语言(Python、JavaScript)互操作、处理 JSON/XML 等动态结构、以及绕过静态类型检查实现灵活调用。本文将深入探讨 C# 动态编程的核心机制、DLR 架构及最佳实践。
dynamic 类型入门
1 2 3 4 5 6 7 8
| dynamic dyn = 42; Console.WriteLine(dyn + 10);
dyn = "Hello"; Console.WriteLine(dyn.Length);
dyn = new List<int> { 1, 2, 3 }; Console.WriteLine(dyn.Count);
|
dynamic 在编译期绕过类型检查,所有成员解析被推迟到运行时。这消除了编译时错误,但可能产生运行时异常。
运行时错误
1 2 3 4 5 6 7 8 9
| dynamic obj = "string"; try { obj.SomeMethod(); } catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex) { Console.WriteLine($"绑定失败: {ex.Message}"); }
|
DLR 架构
Dynamic Language Runtime(DLR)是 .NET 中为动态语言提供支持的运行时层。
1
| C# dynamic → CallSite → Binder → 运行时解析 → 缓存结果
|
CallSite<T> 缓存调用绑定结果,让相同操作的后续调用性能接近静态调用。
1 2 3 4
| dynamic x = 10; dynamic y = 20; dynamic z = x + y;
|
ExpandoObject —— 动态扩展对象
1 2 3 4 5 6 7 8 9 10 11 12
| dynamic expando = new ExpandoObject(); expando.Name = "张三"; expando.Age = 28; expando.SayHello = new Action(() => Console.WriteLine($"你好,我是{expando.Name}"));
expando.SayHello();
foreach (var kvp in (IDictionary<string, object?>)expando) { Console.WriteLine($"{kvp.Key} = {kvp.Value}"); }
|
应用场景:当需要在运行时动态构建对象(如 Web API 返回动态 DTO)时,ExpandoObject 比 Dictionary<string, object> 更自然。
DynamicObject —— 自定义动态行为
通过继承 DynamicObject,可以控制成员调用、属性读写、索引操作等全部动态行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class DynamicConfig : DynamicObject { private readonly Dictionary<string, object?> _data = new();
public override bool TryGetMember(GetMemberBinder binder, out object? result) { return _data.TryGetValue(binder.Name, out result); }
public override bool TrySetMember(SetMemberBinder binder, object? value) { _data[binder.Name] = value; return true; }
public override bool TryInvokeMember( InvokeMemberBinder binder, object?[]? args, out object? result) { if (_data.TryGetValue(binder.Name, out var value) && value is Delegate del) { result = del.DynamicInvoke(args); return true; } result = null; return false; } }
dynamic config = new DynamicConfig(); config.Server = "localhost"; config.Port = 8080; config.GetUrl = new Func<string>(() => $"http://{config.Server}:{config.Port}");
Console.WriteLine(config.GetUrl());
|
直接实现此接口可完全控制元数据绑定流程,适用于构建自定义语言的 .NET 实现、ORM 动态查询构造器等高级场景。
1 2 3 4 5
| public class DynamicEntity : IDynamicMetaObjectProvider { public DynamicMetaObject GetMetaObject(Expression parameter) => new EntityMetaObject(parameter, this); }
|
完整实现较为复杂,在绝大多数场景中使用 DynamicObject 已经足够。
1 2 3 4 5 6 7 8 9
| var excelApp = new Microsoft.Office.Interop.Excel.Application(); excelApp.Workbooks.Open(@"C:\report.xlsx");
dynamic excel = Activator.CreateInstance( Type.GetTypeFromProgID("Excel.Application")!)!; excel.Workbooks.Open(@"C:\report.xlsx"); excel.Visible = true;
|
dynamic 与 JSON
1 2 3 4 5 6 7
| dynamic json = Newtonsoft.Json.JsonConvert.DeserializeObject( @"{""name"":""张三"",""scores"":[90,85,92]}")!;
Console.WriteLine(json.name); Console.WriteLine(json.scores[0]); Console.WriteLine(json.scores.Count);
|
虽然此用法很方便,但在生产代码中更推荐强类型 DTO 或 JsonDocument/JsonNode。
性能考量
1 2 3 4 5 6 7 8 9 10
| int AddStatic(int a, int b) => a + b;
dynamic AddDynamic(dynamic a, dynamic b) => a + b;
|
建议:
- 不在热点路径(循环/高频调用)中使用
dynamic
- 使用
dynamic 的场景确保类型在运行时一定匹配
- 结合
using static 和缓存模式减少动态调用开销
何时使用 dynamic
| 场景 |
推荐度 |
说明 |
| COM 互操作 |
✅ 强烈推荐 |
避免 PIA,代码简洁 |
| 与动态语言交互 |
✅ 强烈推荐 |
Python/IronPython 集成 |
| JSON 数据探索 |
⚠️ 慎用 |
快速原型可以,生产推荐强类型 |
| 泛型类型参数 |
❌ 不推荐 |
应使用 T + 约束替代 |
| API 返回可变结构 |
⚠️ 部分场景 |
考虑 JsonNode 作为替代 |
总结:dynamic 为 C# 提供了静态类型世界中的动态弹性。DLR 的缓存机制让性能损失可控,DynamicObject 和 ExpandoObject 则提供了不同层次的自定义动态行为。合理使用动态编程可以大幅简化某些场景的代码量,但应当谨慎评估静态类型替代方案的可行性。