dynamic是运行时确定类型的“放行开关”,绕过编译检查,将类型解析推迟到运行时,调用失败抛RuntimeBinderException;var是编译期类型推断的语法糖,本质仍是静态类型;object是所有类型的基类,需显式转型才能访问子类型成员。

dynamic 是运行时才确定类型的“放行开关”
当你明确知道某个对象的结构在编译期不可知(比如 COM 对象、JSON 解析结果、反射调用返回值),又不想写一长串 GetType().GetMethod(...).Invoke(...),dynamic 就是那个让你写 obj.SomeMethod() 并让 CLR 在运行时去查方法是否存在、参数是否匹配的“信任授权”。它绕过编译器检查,把类型解析推迟到运行时——所以调用失败会抛 RuntimeBinderException,而不是编译错误。
常见踩坑点:
- IDE 不提供智能提示,拼错方法名或属性名只能等运行时报
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'xxx' does not contain a definition for 'yyy' - 性能略低:每次成员访问都触发 DLR(Dynamic Language Runtime)绑定,比静态调用多几层查找
- 不能用于泛型类型推导:比如
List<dynamic></dynamic>合法,但SomeMethod<t>(dynamicValue)</t>会编译失败,因为T需要编译期确定
var 是编译器帮你“猜类型”的语法糖,本质仍是静态类型
var 声明的变量不是动态类型,而是由初始化表达式**在编译期推断出具体类型**,然后生成完全等价的显式声明。它只是省键盘,不改语义。
例如:
var list = new List<string>(); // 编译后等价于 List<string> list = new List<string>(); var str = "hello"; // 等价于 string str = "hello";
关键限制:
- 必须在声明时初始化(否则编译器无法推断)
- 不能用于类字段(字段没初始化表达式,编译器无从猜起)
- 不能赋值为
null(除非后面有可推断类型,如var x = null as string;) - 不能用于方法签名(参数/返回值类型必须明确写出)
object 是所有类型的基类,但需要显式转型才能用成员
object 是静态类型系统里的“最顶层容器”,任何类型都能隐式转成它,但它本身只暴露 ToString()、Equals()、GetHashCode() 和 GetType() 这几个方法。想调用子类型特有成员,必须强制转型。
典型场景:
- 通用缓存(如
IDictionary<string object></string>存不同类型的值) - 旧版 API 兼容(比如 WinForms 的
Tag属性是object) - 反射中接收
PropertyInfo.GetValue()返回值
风险点:
- 转型失败抛
InvalidCastException(比dynamic的RuntimeBinderException更早暴露,但也更难提前发现) - boxing/unboxing 开销(对值类型频繁装箱会影响性能)
- 代码可读性差:一堆
(MyType)obj让逻辑被类型转换淹没
选哪个?看类型信息什么时候能确定
判断依据就一条:**你写代码时,是否能 100% 确定这个变量后续会以什么结构被使用?**
- 能确定 → 用具体类型(
string、List<int></int>)或var(如果初始化表达式足够清晰) - 编译期不确定、但运行时结构固定且你愿意承担动态调用开销 → 用
dynamic(如处理 ExpandoObject、Newtonsoft.Json.Linq.JObject) - 只是临时当“容器”用、后续一定会转型、且不想引入 DLR → 用
object(尤其在老框架或跨层传递弱类型数据时)
最容易被忽略的是:dynamic 一旦传入方法参数,该方法体内对它的所有操作都会走动态绑定——哪怕你只是想读个属性,也失去了编译期检查和 IDE 支持。这种“传染性”得提前想清楚。


















