探究C# dynamic动态类型本质
本周在做接口动态传参的时候思考了个问题:如何把一个json字符串,转成C#动态类?
比如由
1 | { |
生成
1 | dynamic obj = new |
解决这个问题前,我们先来了解一下dynamic
动态类型。
动态类型是什么?
首先动态类型是静态类,不是一种称之为“动态”的类型,只不过这个类型的对象会跳过静态类型检查。也就是在编译过程中不报错,但是运行程序将对象初始化之后,它该是什么类型,那么还是什么类型。
看个例子,有两个动态类型obj1
,obj2
1 | dynamic obj1 = new |
运行结果如下
他们输出的结果一样,但你认为他们的返回结果是一样的吗?obj1
是一个类型为AnonymousType<int,int,string,bool>
的匿名类,我们可以很轻松地通过反射的方式遍历其成员变量:
1 | Type t = obj1.GetType(); |
打印如下:
1 | userId: 100 |
而obj2
则是System.Dynamic.ExpandoObject
类型的对象,而且从初始化到对象生命周期结束。始终是这个类型。
我们对obj2
运行同样的代码,发现会报错
1 | Type t = obj2.GetType(); |
报错的原因是obj2
并不包含真正的userId
成员变量,因为其本质是个ExpandoObject
对象,
可见dynamic
关键字并不会改变C#变量在运行时的类型,它仅仅是在编译阶段跳过了静态类型检查。
动态类型的特点是什么?
然而你是可以通过重新赋值改变类型的,当然这是公共语言运行时 (CLR) 提供的动态技术。
1 | dynamic number = 1; |
当我用ILspy反编译工具查看IL源码的时候,竟发现number
变量的类型是object
,也就是整个过程经过了装箱拆箱,经过了从内存栈创建地址引用到堆中区域的改变。dynamic
帮我们完成了这些动作。所以本质上内存中同一个对象不会平白无故从int
类型转换为string
。毕竟C#不能像其他弱类型语言那样使用。
obj1
匿名类的成员变量是只读的。给它赋一个其他类型的值,将会报错;
而给obj2
的成员变量赋其他类型的值,则不会报错。
1 | obj1.userId = "100"; //运行时报错 |
在来看obj2,因为System.Dynamic.ExpandoObject
类型因实现了 IDynamicMetaObjectProvider
因此它能通过.成员变量
的方式访问内容。
又因为System.Dynamic.ExpandoObject
实现了IDictionary<string, object?>
因此可以通过向字典添加KeyValue对象的形式向ExpandoObject
对象添加成员变量,用[key]
方式访问内容。代码如下
1 | foreach (var entry in obj1) |
通过.成员变量
的方式访问内容,可以说这是伪装的成员变量。但稍微一测试,就露馅了。
动态类型如何用?
现在我们来回答“如何把一个json字符串,转成C#动态类”这个问题,答案是做不到。
首先用Newtonsoft.Json
库转换的结果,无论是用JObject.Parse(json)
还是JsonConvert.DeserializeObject(json)
最后返回的结果是JToken
类型的对象,
通过反编译Newtonsoft.Json.dll
,查看JToken
类型,可见它还是一个继承了IDictionary<string, object?>
和IDynamicMetaObjectProvider
的类型,
1 | string json = @"{ |
运行如上同样的代码检查obj2
1 | Type t = obj2.GetType(); |
可以通过这样向obj2
动态添加成员变量,但是始终是字典方式提供的伪对象。
探究C# dynamic动态类型本质