dynamic 和 var、object 到底差在哪?
dynamic不是“更灵活的
var”,也不是“带智能提示的
object”——它是编译器主动放弃类型检查的明确信号。
var是编译期推导,类型一旦确定就不可变(
var x = "a"; x = 123;直接编译报错);
object要访问成员必须显式转换(
((string)obj).Length),IDE 能提示、能重构;
dynamic所有成员访问、方法调用、运算符都跳过编译检查,全靠运行时 DLR 解析,写错
obj.Lengh(拼错)也能过编译,直到执行才抛
RuntimeBinderException。
什么场景真该用 dynamic?
它不是语法糖,而是为特定互操作瓶颈设计的“减压阀”。
✅ 和 COM 对象交互:比如Excel.Application,不用写一长串
Marshal.ReleaseComObject和
InvokeMember; ✅ 处理未知结构 JSON:用
Newtonsoft.Json.Linq.JObject或
System.Text.Json.JsonNode反序列化后,直接
data.users[0].name访问; ✅ 包装反射调用:比如你有一堆
MethodInfo调用逻辑,换成
dynamic obj = target; obj.DoSomething();更直白; ✅ 构建脚本桥接层(如暴露 C# 方法给 Lua/Python 调用),配合
IDynamicMetaObjectProvider实现自定义绑定。
❌ 别用在业务模型层、DTO 传输、循环内高频访问——性能损耗明显(DLR 缓存虽有,但首次解析开销大),且 IDE 完全失能。
怎么安全地用 dynamic?避免 runtime 崩溃
动态不等于随意,几个实操习惯能大幅降低风险:
永远在调用前用obj is string或
obj?.GetType() == typeof(int)做类型守门; 对来自外部的数据(如 API JSON),优先用强类型反序列化;仅当 schema 真正多变、且字段数少时,才退到
dynamic; 配合
ExpandoObject构建可写动态对象时,注意
ExpandoObject本身实现了
IDictionary<string object></string>,可遍历属性名,方便做字段校验; 在调试时,直接在监视窗口输入
obj.GetType()或
((IDynamicMetaObjectProvider)obj).GetMetaObject(Expression.Constant(obj))查看当前绑定状态。
性能到底慢多少?要不要担心?
DLR 的首次调用比静态调用慢 5–10 倍(主要花在元数据查找和缓存构建上),后续同签名调用会命中缓存,差距缩小到 1.2–1.5 倍。
单次调用(如初始化 Excel、解析一个配置项)完全可忽略; 但在for循环里反复调用
item.Name(其中
item是
dynamic)就会明显拖慢; 替代方案:用
Convert.ToString(item.Name)或提前转成具体类型(
string name = item.Name;),让后续访问回归静态路径。
真正容易被忽略的是调试成本——断点停住后,你没法靠鼠标悬停看属性,得手动敲
obj.GetType()、
obj.ToString(),甚至进“快速监视”查
IDynamicMetaObjectProvider实现细节。这不是语法问题,是开发流被打断。
