dynamic是编译器绕过静态检查的机制,所有成员访问推迟到运行时由DLR绑定,错误抛RuntimeBinderException;必须用dynamic而非object的场景包括COM互操作、动态语言桥接、DOM/JSON泛化解析等。

dynamic 不是运行时类型,它是编译器层面的“绕过静态检查”机制——声明为 dynamic 的变量在编译期不校验成员是否存在、参数是否匹配、返回值是否可赋值,所有这些都推迟到运行时由 DLR(Dynamic Language Runtime)解析并执行。用错地方会直接抛 RuntimeBinderException,而不是给你留调试余地。
什么时候必须用 dynamic 而不是 object
核心区别不在“能不能装任意值”,而在“调用成员时是否走 DLR 绑定”。object 上调用方法会编译失败(除非显式反射),而 dynamic 会把整个调用(含方法名、参数、重载选择)打包成 CallSite,等运行时才查目标对象有没有那个方法。
- 需要跟 COM 对象交互(比如 Excel 互操作),且不想写一长串
Marshal.Invoke或Type.InvokeMember - 接收来自 IronPython / Ruby.NET 等动态语言的返回值,且方法签名在 C# 编译期不可知
- 解析 HTML DOM(通过
mshtml或Microsoft.Office.Interop.Excel),元素属性名和方法名是字符串驱动的 - 做高度泛化的 JSON 反序列化桥接(如
Newtonsoft.Json.Linq.JObject转dynamic后直接点属性)
dynamic 赋值后类型不会变,但运行时值类型会变
dynamic 变量本身类型始终是 dynamic,但它所指向的底层值可以是任意类型。IntelliSense 显示的是变量声明类型,不是值类型:
dynamic d = 42;
Console.WriteLine(d.GetType()); // System.Int32
d = "hello";
Console.WriteLine(d.GetType()); // System.String
d = new[] { 1, 2, 3 };
Console.WriteLine(d.GetType()); // System.Int32[]
注意:var 在这里不能替代 dynamic——var d = 42; 推导出的是 int,后续不能再赋字符串;而 dynamic 允许跨类型赋值,且每次赋值后 .GetType() 返回真实运行时类型。
为什么 dynamic_ec.exampleMethod1(10, 4) 运行时报错而不是编译报错
因为编译器对 dynamic 表达式的任何成员访问(方法、属性、索引器)都生成 CallSite,把调用信息(方法名、实参列表、接收者类型)缓存起来,等到第一次执行时才尝试绑定。DLR 按照如下顺序查找:
- 查目标对象是否有公开实例方法
exampleMethod1,且参数个数、类型能隐式转换匹配 - 查是否有扩展方法适用(需 using 命名空间已引入)
- 查是否实现了
IDynamicMetaObjectProvider,走自定义绑定逻辑 - 全部失败 → 抛
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
所以即使 ExampleClass 里只定义了 exampleMethod1(int i),dynamic_ec.exampleMethod1(10, 4) 仍会走到第 4 步失败,错误信息类似:'ExampleClass' does not contain a definition for 'exampleMethod1'。
容易被忽略的性能与调试陷阱
dynamic 绑定有显著开销:首次调用慢(要构建和缓存 CallSite),后续同签名调用快(命中缓存),但一旦参数类型组合变化(比如从 int 换成 long),又触发新绑定。更麻烦的是调试体验:
- 断点停在
dynamic行时,Locals 窗口只显示dynamic,看不到实际值结构(需鼠标悬停或用QuickWatch输入d.ToString()) - 异常堆栈里看不到原始调用点,而是深埋在
Microsoft.CSharp.RuntimeBinder内部 - 无法用 ReSharper 或 Roslyn 分析器做空引用/成员缺失预警
-
dynamic传入泛型方法时,类型推导失效:DoSomething中(dynamicValue) T会被推为dynamic,而非实际运行时类型
真正难缠的不是语法怎么写,而是你得清楚:这个 dynamic 值从哪来、生命周期内可能是什么类型、谁负责保证它有你要调的方法——这些责任全移交给程序员,编译器不再兜底。











