C# 高级教程 - 03 动态编程

C# 在保持强类型静态语言优势的同时,通过 dynamic 关键字引入了动态编程能力。动态编程在特定场景下极具价值,如与动态语言(Python、JavaScript)互操作、处理 JSON/XML 等动态结构、以及绕过静态类型检查实现灵活调用。本文将深入探讨 C# 动态编程的核心机制、DLR 架构及最佳实践。

dynamic 类型入门

1
2
3
4
5
6
7
8
dynamic dyn = 42;
Console.WriteLine(dyn + 10); // 52 —— 运行时决定操作

dyn = "Hello";
Console.WriteLine(dyn.Length); // 5 —— 字符串属性

dyn = new List<int> { 1, 2, 3 };
Console.WriteLine(dyn.Count); // 3 —— List 属性

dynamic 在编译期绕过类型检查,所有成员解析被推迟到运行时。这消除了编译时错误,但可能产生运行时异常。

运行时错误

1
2
3
4
5
6
7
8
9
dynamic obj = "string";
try
{
obj.SomeMethod(); // 运行时抛出 RuntimeBinderException
}
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
// 查看 CallSite 的效果(概念示例)
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)时,ExpandoObjectDictionary<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()); // http://localhost:8080

IDynamicMetaObjectProvider —— 进阶自定义

直接实现此接口可完全控制元数据绑定流程,适用于构建自定义语言的 .NET 实现、ORM 动态查询构造器等高级场景。

1
2
3
4
5
public class DynamicEntity : IDynamicMetaObjectProvider
{
public DynamicMetaObject GetMetaObject(Expression parameter)
=> new EntityMetaObject(parameter, this);
}

完整实现较为复杂,在绝大多数场景中使用 DynamicObject 已经足够。

dynamic 与 COM 互操作

1
2
3
4
5
6
7
8
9
// 传统方式:需要 Office PIA 引用
var excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.Workbooks.Open(@"C:\report.xlsx");

// dynamic 方式:避免 PIA,更简洁
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
// 配合 Newtonsoft.Json / System.Text.Json
dynamic json = Newtonsoft.Json.JsonConvert.DeserializeObject(
@"{""name"":""张三"",""scores"":[90,85,92]}")!;

Console.WriteLine(json.name); // 张三
Console.WriteLine(json.scores[0]); // 90
Console.WriteLine(json.scores.Count); // 3

虽然此用法很方便,但在生产代码中更推荐强类型 DTO 或 JsonDocument/JsonNode

性能考量

1
2
3
4
5
6
7
8
9
10
// 静态调用:快
int AddStatic(int a, int b) => a + b;

// dynamic 调用:首次慢,缓存后接近静态
dynamic AddDynamic(dynamic a, dynamic b) => a + b;

// 性能对比(概略值)
// AddStatic: ~1x
// AddDynamic(非首次): ~2-5x
// AddDynamic(首次): ~20-50x

建议

  • 不在热点路径(循环/高频调用)中使用 dynamic
  • 使用 dynamic 的场景确保类型在运行时一定匹配
  • 结合 using static 和缓存模式减少动态调用开销

何时使用 dynamic

场景 推荐度 说明
COM 互操作 ✅ 强烈推荐 避免 PIA,代码简洁
与动态语言交互 ✅ 强烈推荐 Python/IronPython 集成
JSON 数据探索 ⚠️ 慎用 快速原型可以,生产推荐强类型
泛型类型参数 ❌ 不推荐 应使用 T + 约束替代
API 返回可变结构 ⚠️ 部分场景 考虑 JsonNode 作为替代

总结dynamic 为 C# 提供了静态类型世界中的动态弹性。DLR 的缓存机制让性能损失可控,DynamicObjectExpandoObject 则提供了不同层次的自定义动态行为。合理使用动态编程可以大幅简化某些场景的代码量,但应当谨慎评估静态类型替代方案的可行性。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: