C# 中的动态类型 (2)

DLR 位于 CLR 之上。回想一下,我说过的每种类型都是从 System.Object 派生而来的。嗯,这句话对于 CLR 是适用的,但是对于 DLR 呢?我们使用下面的程序来测试一下这个理论:

Console.WriteLine("ExpandoObject inherits from System.Object: " + typeof(ExpandoObject).IsSubclassOf(typeof(Object))); Console.WriteLine("DynamicObject inherits from System.Object: " + typeof(DynamicObject).IsSubclassOf(typeof(Object)));

ExpandoObject 和 DynamicObject 在命令行中输出的值都是 True。可以将这两个类视为使用动态类型的基本构建块,它们清楚地描绘了两个运行时是如何结合在一起的。

一个 JSON 序列化程序

动态类型解决的一个问题是,当您有一个不知道其成员的 JSON HTTP 请求时,假设要在 C# 中使用此任意的 JSON。要解决这个问题,请将此 JSON 序列化为 C# 动态类型。

我将使用 Newtonsoft 序列化库,您可以通过 NuGet 添加此依赖项,例如:

dotnet add package Newtonsoft.Json –-version 11.0.2

您可以使用这个序列化程序来处理 ExpandoObject 和 DynamicObject。探索每种动态类型给动态编程带来了什么。

ExpandoObject 动态类型

ExpandoObject 是一种方便的类型,允许设置和检索动态成员。它实现了 IDynamicMetaObjectProvider,该接口允许在 DLR 中的语言之间共享实例。因为它实现了 IDictionary 和 IEnumerable,所以它也可以处理 CLR 中的类型。举例来说,它允许将 ExpandoObject 的实例转换为 IDictionary,然后像其它任意的 IDictionary 类型一样枚举成员。

要用 ExpandoObject 处理任意 JSON,您可以编写以下程序:

var exObj = JsonConvert.DeserializeObject<ExpandoObject>("{\"a\":1}") as dynamic; Console.WriteLine($"exObj.a = {exObj?.a}, type of {exObj?.a.GetType()}"); //exObj.a = 1, type of System.Int64

它将会在控制台打印 1 和 long。请注意,尽管它是一个动态 JSON,但它会绑定到 CLR 中的 C# 类型。由于数字的类型未知,因此序列化程序默认会选择最大的 long 类型。注意,我成功地将序列化结果转换成了具有 null 检查的 dynamic 类型,其原因是序列化程序返回来自 CLR 的 object 类型。因为 ExpandoObject 继承自 System.Object,所以可以被拆箱成 DLR 类型。

更奇妙的是,可以用 IDictionary 枚举 exObj:

foreach (var exObjProp in exObj as IDictionary<string, object> ?? new Dictionary<string, object>()) { Console.WriteLine($"IDictionary = {exObjProp.Key}: {exObjProp.Value}"); }

它在控制台中输出 IDictionary = a: 1。请确保使用 string 和 object 作为键和值的类型。否则,将在转换的过程中抛出 RuntimeBinderException 异常。

DynamicObject 动态类型

DynamicObject 提供对动态类型的精确控制。您可以继承该类型并重写动态行为。例如,您可以定义如何设置和获取类型中的动态成员。DynamicObject 允许您通过重写选择实现哪些动态操作。这比实现 IDynamicMetaObjectProvider 的语言实现方式更易访问。它是一个抽象类,需要继承它而不是实例化它。该类有 14 个虚方法,它们定义了类型的动态操作,每个虚方法都允许重写以指定动态行为。

假设您想要精确控制动态 JSON 中的内容。尽管事先不知道其属性,您却可以使用 DynamicObject 来控制类型。

让我们来重写三个方法,TryGetMember、TrySetMember 和 GetDynamicMemberNames:

public class TypedDynamicJson<T> : DynamicObject { private readonly IDictionary<string, T> _typedProperty; public TypedDynamicJson() { _typedProperty = new Dictionary<string, T>(); } public override bool TryGetMember(GetMemberBinder binder, out object result) { T typedObj; if (_typedProperty.TryGetValue(binder.Name, out typedObj)) { result = typedObj; return true; } result = null; return false; } public override bool TrySetMember(SetMemberBinder binder, object value) { if (value.GetType() != typeof(T)) { return false; } _typedProperty[binder.Name] = (T)value; return true; } public override IEnumerable<string> GetDynamicMemberNames() { return _typedProperty.Keys; } }

C# 泛型强类型 _typedProperty 以泛型的方式驱动成员类型。这意味着其属性类型来自泛型类型 T。动态 JSON 成员位于字典中,并且仅存储泛型类型。此动态类型允许同一类型的同类成员集合。尽管它允许动态成员集,但您可以强类型其行为。假设您只关心任意 JSON 中的 long 类型:

var dynObj = JsonConvert.DeserializeObject<TypedDynamicJson<long>>("{\"a\":1,\"b\":\"1\"}") as dynamic; Console.WriteLine($"dynObj.a = {dynObj?.a}, type of {dynObj?.a.GetType()}"); var members = string.Join(",", dynObj?.GetDynamicMemberNames()); Console.WriteLine($"dynObj member names: {members}");

结果是,您将看到一个值为 1 的属性,因为第二个属性是 string 类型。如果将泛型类型更改为 string,将会获得第二个属性。

类型结果

到目前为止,已经涉及了相当多的领域; 以下是一些亮点:

CLR 和 DLR 中的所有类型都继承自 System.Object

DLR 是所有动态操作发生的地方

ExpandoObject 实现了 CLR 中诸如 IDictionary 的可枚举类型

DynamicObject 通过虚方法对动态类型进行精确控制

看一下在控制台的结果截图:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpyjgz.html