一、动态类型简介
.NET 4 中引入了动态类型。动态对象(dynamic
)使您可以处理诸如 JSON 文档之类的结构内容,这些结构的组成可能要到运行时才能知道【 该类型是一种静态类型,但类型为 dynamic
的对象会跳过静态类型检查。 大多数情况下,该对象就像具有类型 object
一样。 在编译时,将假定类型化为 dynamic
的元素支持任何操作。 因此,不必考虑对象是从 COM API、从动态语言(例如 IronPython)、从 HTML 文档对象模型 (DOM)、从反射还是从程序中的其他位置获取自己的值。 但是,如果代码无效,则在运行时会捕获到错误。】
.NET 4.0 中引入的 dynamic
关键字为 C# 编程带来了一个范式转变。对于 C# 程序员来说,强类型系统之上的动态行为可能会让人感到不适 —— 当您在编译过程中失去类型安全性时,这似乎是一种倒退。【动态编程可能使您面临运行时错误。声明一个在执行过程中会发生变化的动态变量是可怕的,当开发人员对数据做出错误的假设时,代码质量就会受到影响】。
对 C# 程序员来说,避免代码中的动态行为是合乎逻辑的,具有强类型的经典方法有很多好处。通过类型检查得到的数据类型的良好反馈对于正常运行的程序是至关重要的,一个好的类型系统可以更好地表达意图并减少代码中的歧义。
随着动态语言运行时(Dynamic Language Runtime,DLR)的引入,这对 C# 意味着什么呢? .NET 提供了丰富的类型系统,可用于编写企业级软件。让我们来仔细看看 dynamic
关键字,并探索一下它的功能。
二、类型层次结构
公共语言运行时(Common Language Runtime,CLR)中的每种类型都继自 System.Object
,【这意味着 object
类型是整个类型系统的公共父类】。当我们研究更神奇的动态行为时,这一事实本身就能为我们提供帮助。这里的想法是开发这种“代码感”,以便于您了解如何驾驭 C# 中的动态类型。
C#的基础数据类型继承自ValueType;而ValueType
重写来自 object
类的默认行为。ValueType
的子类在栈(stack)上运行,它们的生命周期较短,效率更高。而string则是派生自object。
以下为演示程序:
//1-基础类型 是否继承 ValueType
private static void BaseTypeInheritValueType<T>()
{
string str = $"{typeof(T).Name}\t继承于 ValueType:{typeof(T).IsSubclassOf(typeof(ValueType))}";
Print(str);
}
//2-ValueType 是否继承 System.Object
private static void ValueTypeInheritObject()
{
string str = $"ValueType 继承于 System.Object:{typeof(ValueType).IsSubclassOf(typeof(System.Object))}";
Print(str);
}
//3-string 是否继承 System.Object
private static void StringInheritObject()
{
string str = $"string 继承于 System.Object:{typeof(string).IsSubclassOf(typeof(System.Object))}";
Print(str);
}
//1-byte 是否继承 ValueType
BaseTypeInheritValueType<byte>();
//1-short 是否继承 ValueType
BaseTypeInheritValueType<short>();
//1-int 是否继承 ValueType
BaseTypeInheritValueType<int>();
//2-uint 是否继承 ValueType
BaseTypeInheritValueType<uint>();
//1-Long 是否继承 ValueType
BaseTypeInheritValueType<long>();
//1-float 是否继承 ValueType
BaseTypeInheritValueType<float>();
//1-double 是否继承 ValueType
BaseTypeInheritValueType<double>();
//1-bool 是否继承 ValueType
BaseTypeInheritValueType<bool>();
//1-Enum 是否继承 ValueType
BaseTypeInheritValueType<Enum>();
//1-char 是否继承 ValueType
BaseTypeInheritValueType<char>();
//1-string 是否继承 ValueType
BaseTypeInheritValueType<string>();
值类型和引用类型都是 CLR 的基本构建块,这种优雅的类型系统在 .NET 4.0 和动态类型之前就有了。如下是公共语言运行时(CLR) 的类型系统:
三、动态语言运行时
3.1、动态语言运行时基础
动态语言运行时(Dynamic Language Runtime, DLR)是处理动态对象的一种便捷方法。比如:您有 XML 或 JSON 格式的数据,但是数据其中的成员你并不知道。此时DLR 允许您使用自然代码来处理对象和访问成员。
对于 C#,这使您可以处理在编译时不知道其类型的库。动态类型消除了自然 API 代码中的万能字符串。这就开启了像 IronPython 一样位于 CLR 之上的动态语言。
可以将 DLR 视为支持三项主要服务:
序号 | 内容 | 说明 |
1 | 表达式树 | 表达式树,来自 System.Linq.Expressions 命名空间。编译器在运行时生成具有动态语言互操作性的表达式树 |
2 | 调用站点缓存 | 调用站点缓存,即缓存动态操作的结果。DLR 缓存像 a + b 之类的操作,并存储 a 和 b 的特征。当执行动态操作时,DLR 将检索先前操作中可用的信息。 |
3 | 动态对象互操作性是可用于访问 DLR 的 C# 类型 | 动态对象互操作性是可用于访问 DLR 的 C# 类型。这些类型包括 DynamicObject 和 ExpandoObject 。可用的类型还有很多 |
DLR 和 CLR 的结合图如下:
每种类型都是从 System.Object
派生而来的。嗯,这句话对于 CLR 是适用的,但是对于 DLR 呢?
示例代码如下:
//4-测试DLR 是否继承 System.Object
private static void DLRObjectInheritObject<T>()
{
string str = $"{typeof(T).Name}\t继承于 System.Object:{typeof(T).IsSubclassOf(typeof(System.Object))}";
Print(str);
}
//4-测试DynamicObject 是否继承 System.Object
DLRObjectInheritObject<System.Dynamic.DynamicObject>();
//4-测试ExpandoObject 是否继承 System.Object
DLRObjectInheritObject<System.Dynamic.ExpandoObject>();
运行结果如下:
3.2、动态语言运行时应用场景
动态类型解决的一个问题是,当您有一个不知道其成员的 JSON HTTP 请求时,假设要在 C# 中使用此任意的 JSON。要解决这个问题,请将此 JSON 序列化为 C# 动态类型。
我将使用 Newtonsoft 序列化库,您可以通过 NuGet 添加此依赖项。
您可以使用这个序列化程序来处理 ExpandoObject
和 DynamicObject
。探索每种动态类型给动态编程带来了什么。
3.3、ExpandoObject 动态类型
【ExpandoObject
】
是一种方便的类型,允许设置和检索动态成员。它实现了 IDynamicMetaObjectProvider
,该接口允许在 DLR 中的语言之间共享实例。因为它实现了 IDictionary
和 IEnumerable
,所以它也可以处理 CLR 中的类型。
举例来说,它允许将 ExpandoObject
的实例转换为 IDictionary
,然后像其它任意的 IDictionary
类型一样枚举成员。
要用 ExpandoObject
处理任意 JSON,您可以编写以下程序:
using System;
using System.Collections.Generic;
using System.Text;
namespace Test_Dynamic
{
class PeopleInfo
{
public string Id { get; set; }
public string Name { get; set; }
public string Sex { get; set; }
public int Age { get; set; }
public string TelNumber { get; set; }
public string Address { get; set; }
}//Class_end
}
//获取人员信息
private static PeopleInfo GetPeopleInfo()
{
PeopleInfo peopleInfo = new PeopleInfo();
peopleInfo.Id = Guid.NewGuid().ToString("N");
peopleInfo.Name = "牛奶咖啡";
peopleInfo.Sex = "男";
peopleInfo.Age = 27;
peopleInfo.TelNumber = "13115672345";
peopleInfo.Address = "中华人民共和国测试省测试市测试街道测试号";
return peopleInfo;
}
//将一个对象序列化为Json字符串
private static string GetJsonStrOfObject<T>(T t)
{
string str = Newtonsoft.Json.JsonConvert.SerializeObject(t);
return str;
}
//将一个Json字符串反序列化为动态类型(通过ExpandoObject类型)
private static dynamic GetExpandoObjectResultOfJsonStr(string jsonStr)
{
if (string.IsNullOrEmpty(jsonStr)) return null;
dynamic result = Newtonsoft.Json.JsonConvert.DeserializeObject<System.Dynamic.ExpandoObject>(jsonStr);
return result;
}
//输出ExpandoObject类型的解析内容
private static void PrintExpandoObjectInfo(dynamic dc)
{
string str = $"编号:{dc?.Id}\t姓名:{dc?.Name}\t性别:{dc?.Sex}\t年龄:{dc?.Age}\t" +
$"电话:{dc?.TelNumber}\t住址:{dc?.Address}";
Print(str);
}
//输出ExpandoObject类型的解析内容(IDictionary)
private static void PrintExpandoObjectInfo2(dynamic dc)
{
if (dc == null) return;
foreach (var item in dc as System.Collections.Generic.IDictionary<string, object>??
new System.Collections.Generic.Dictionary<string, object>())
{
string str=$"IDictionary={item.Key}:{item.Value}";
Print(str);
}
}
调用方法示例如下:
//5-ExpandoObject示例
private static void ExpandoObjectSample()
{
//1-将一个PeopleInfo对象序列化为Json字符串
string jsonStr = GetJsonStrOfObject(GetPeopleInfo());
//2-将一个Json字符串反序列化为动态类型(通过ExpandoObject类型)
dynamic dc = GetExpandoObjectResultOfJsonStr(jsonStr);
//3-输出ExpandoObject类型的解析内容
PrintExpandoObjectInfo(dc);
//4-输出ExpandoObject类型的解析内容(IDictionary)
PrintExpandoObjectInfo2(dc);
}
运行结果如下:
请注意:尽管它是一个动态 JSON,但它会绑定到 CLR 中的 C# 类型。由于数字的类型未知,因此序列化程序默认会选择支持的最大类型(比如Age字段就使用的是long类型)。
我们成功地将序列化结果转换成了具有 null 检查的 dynamic
类型,其原因是序列化程序返回来自 CLR的 object
类型。因为 ExpandoObject
继承自 System.Object
,所以可以被拆箱成 DLR 类型。
更奇妙的是,可以用 IDictionary
枚举 dynamic,如下图所示:
//输出ExpandoObject类型的解析内容(IDictionary)
private static void PrintExpandoObjectInfo2(dynamic dc)
{
if (dc == null) return;
foreach (var item in dc as System.Collections.Generic.IDictionary<string, object>??
new System.Collections.Generic.Dictionary<string, object>())
{
string str=$"IDictionary={item.Key}:{item.Value}";
Print(str);
}
}
3.4、DynamicObject 动态类型
【DynamicObject】
提供对动态类型的精确控制。您可以继承该类型并重写动态行为。例如:您可以定义如何设置和获取类型中的动态成员。DynamicObject
允许您通过重写选择实现哪些动态操作。这比实现 IDynamicMetaObjectProvider
的语言实现方式更易访问。它是一个抽象类,需要继承它而不是实例化它。该类有 14 个虚方法,它们定义了类型的动态操作,每个虚方法都允许重写以指定动态行为。
假设您想要精确控制动态 JSON 中的内容。尽管事先不知道其属性,您却可以使用 DynamicObject
来控制类型。
让我们来重写三个方法,TryGetMember
、TrySetMember
和 GetDynamicMemberNames
:
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text;
namespace Test_Dynamic
{
class OpcJsonOfDynamic<T>: System.Dynamic.DynamicObject
{
private readonly IDictionary<string, T> _objProperty;
public OpcJsonOfDynamic()
{
_objProperty = new Dictionary<string,T>();
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
T typeObj;
if (_objProperty.TryGetValue(binder.Name,out typeObj))
{
result = typeObj;
return true;
}
result = null;
return false;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (value.GetType()!=typeof(T))
{
return false;
}
_objProperty[binder.Name]=(T)value;
return true;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return _objProperty.Keys;
}
public IDictionary<string, T> GetKeyValues()
{
return _objProperty;
}
}//Class_end
}
C# 泛型强类型 _objProperty
以泛型的方式驱动成员类型。这意味着其属性类型来自泛型类型 T
。动态 JSON 成员位于字典中,并且仅存储泛型类型。此动态类型允许同一类型的同类成员集合。尽管它允许动态成员集,但您可以强类型其行为。假设您只关心任意 JSON 中的 string
类型:
//获取人员信息
private static PeopleInfo GetPeopleInfo()
{
PeopleInfo peopleInfo = new PeopleInfo();
peopleInfo.Id = Guid.NewGuid().ToString("N");
peopleInfo.Name = "牛奶咖啡";
peopleInfo.Sex = "男";
peopleInfo.Age = 27;
peopleInfo.TelNumber = "13115672345";
peopleInfo.Address = "中华人民共和国测试省测试市测试街道测试号";
return peopleInfo;
}
//输出DynamicObject类型的内容
private static void PrintDynamicObjectInfo(System.Dynamic.DynamicObject dc)
{
if (dc == null) return;
if (dc is OpcJsonOfDynamic<string>)
{
OpcJsonOfDynamic<string> opcJsonOfDynamic = (dc as OpcJsonOfDynamic<string>);
foreach (var item in opcJsonOfDynamic.GetKeyValues())
{
string str = $"键:{item.Key}\t值:{item.Value}";
Print(str);
}
}
}
调用方法示例如下:
//6-DynamicObject示例
private static void DynamicObjectSample()
{
//1-将一个PeopleInfo对象序列化为Json字符串
string jsonStr = GetJsonStrOfObject(GetPeopleInfo());
//2-通过Dynamic对象操作Json
System.Dynamic.DynamicObject dcObj = Newtonsoft.Json.JsonConvert.DeserializeObject<OpcJsonOfDynamic<string>>(jsonStr);
//3-输出dcObj内容
PrintDynamicObjectInfo(dcObj);
}
运行结果如下:
3.5、类型结果
- CLR 和 DLR 中的所有类型都继承自
System.Object
- DLR 是所有动态操作发生的地方
ExpandoObject
实现了 CLR 中诸如IDictionary
的可枚举类型DynamicObject
通过虚方法对动态类型进行精确控制
四、参考文档
Working with the Dynamic Type in C# - Simple Talk (red-gate.com)https://www.red-gate.com/simple-talk/development/dotnet-development/working-with-the-dynamic-type-in-c/使用类型 dynamic - C# 编程指南 | Microsoft Docs
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/types/using-type-dynamic