C# 中的动态类型

翻译自 Camilo Reyes 2018年10月15日的文章 《Working with the Dynamic Type in C#》 [1]  

.NET 4 中引入了动态类型。动态对象使您可以处理诸如 JSON 文档之类的结构,这些结构的组成可能要到运行时才能知道。在本文中,Camilo Reyes 解释了如何使用动态类型。

.NET 4.0 中引入的 dynamic 关键字为 C# 编程带来了一个范式转变。对于 C# 程序员来说,强类型系统之上的动态行为可能会让人感到不适 —— 当您在编译过程中失去类型安全性时,这似乎是一种倒退。

动态编程可能使您面临运行时错误。声明一个在执行过程中会发生变化的动态变量是可怕的,当开发人员对数据做出错误的假设时,代码质量就会受到影响。

对 C# 程序员来说,避免代码中的动态行为是合乎逻辑的,具有强类型的经典方法有很多好处。通过类型检查得到的数据类型的良好反馈对于正常运行的程序是至关重要的,一个好的类型系统可以更好地表达意图并减少代码中的歧义。

随着动态语言运行时(Dynamic Language Runtime,DLR)的引入,这对 C# 意味着什么呢?.NET 提供了丰富的类型系统,可用于编写企业级软件。让我们来仔细看看 dynamic 关键字,并探索一下它的功能。

类型层次结构

公共语言运行时(Common Language Runtime,CLR)中的每种类型都继承自 System.Object,现在,请重复阅读这句话,直到将其铭记于心。这意味着 object 类型是整个类型系统的公共父类。当我们研究更神奇的动态行为时,这一事实本身就能为我们提供帮助。这里的想法是开发这种“代码感”,以便于您了解如何驾驭 C# 中的动态类型。

为了演示这一点,您可以编写以下程序:

Console.WriteLine("long inherits from ValueType: " + typeof(long).IsSubclassOf(typeof(ValueType)));

我将忽略 using 语句直到本文结束,以保持对代码示例的专注。然后,我再介绍每个命名空间及其作用。这样我就不必重复说过的话,并提供了一个回顾所有类型的机会。

上面的代码在控制台中的运算结果为 True。.NET 中的 long 类型是值类型,因此它更像是枚举或结构体。ValueType 重写来自 object 类的默认行为。ValueType 的子类在栈(stack)上运行,它们的生命周期较短,效率更高。

要验证 ValueType 是继承自 System.Object 的,请执行以下代码:

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

它的运算结果为 True。这是一条可以追溯到 System.Object 的继承链。对于值类型,链中至少有两个父级。

再看一下从 System.Object 派生的另一个 C# 类型,例如:

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

此代码在控制台中显示为 True。另一种从 object 继承的类型是引用类型,引用类型在堆(heap)上分配并进行垃圾回收,CLR 管理着引用类型,并在必要时从堆中释放它们。

查看下图,您可以直观地看到 CLR 的类型系统:

值类型和引用类型都是 CLR 的基本构建块,这种优雅的类型系统在 .NET 4.0 和动态类型之前就有了。我建议您在使用 C# 中的类型时,在脑海中记住这张图。那么,DLR 是如何适应这张图的呢?

动态语言运行时(DLR)

动态语言运行时(Dynamic Language Runtime, DLR)是处理动态对象的一种便捷方法。比如,假设您有 XML 或 JSON 格式的数据,其中的成员事先并不知道。DLR 允许您使用自然代码来处理对象和访问成员。

对于 C#,这使您可以处理在编译时不知道其类型的库。动态类型消除了自然 API 代码中的万能字符串。这就开启了像 IronPython 一样位于 CLR 之上的动态语言。

可以将 DLR 视为支持三项主要服务:

  • 表达式树,来自 System.Linq.Expressions 命名空间。编译器在运行时生成具有动态语言互操作性的表达式树。动态语言超出了本文的讨论范围,这里就不作介绍了。

  • 调用站点缓存,即缓存动态操作的结果。DLR 缓存像 a + b 之类的操作,并存储 a 和 b 的特征。当执行动态操作时,DLR 将检索先前操作中可用的信息。

  • 动态对象互操作性是可用于访问 DLR 的 C# 类型。这些类型包括 DynamicObject 和 ExpandoObject。可用的类型还有很多,但是在处理动态类型时请注意这两种类型。

要了解 DLR 和 CLR 是如何结合在一起的,请看下图:

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 来控制类型。

让我们来重写三个方法,TryGetMemberTrySetMember 和 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 通过虚方法对动态类型进行精确控制

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

单元测试

对于单元测试,我将使用 xUnit 测试框架。在 .NET Core 中,您可以使用 dotnet new xunit 命令添加一个测试项目。一个显而易见的问题是模拟和验证动态参数,例如,假设您想验证一个方法调用是否具有动态属性。

要使用 Moq 模拟库,您可以通过 NuGet 添加此依赖项,例如:

dotnet add package Moq –-version 4.10.0

假设您有一个接口,其想法是验证它是否被正确的动态对象调用。

public interface IMessageBus
{void Send(dynamic message);
}

忽略该接口的实现。这些实现细节对于编写单元测试不是必需的。下面是被测试的系统:

public class MessageService
{private readonly IMessageBus _messageBus;public MessageService(IMessageBus messageBus){_messageBus = messageBus;}public void SendRawJson<T>(string json){var message = JsonConvert.DeserializeObject<T>(json) as dynamic;_messageBus.Send(message);}
}

您可以使用泛型,这样就可以为序列化程序传入动态类型。然后调用 IMessageBus 并发送动态消息。被测试的方法接受一个 string 参数,并使用 dynamic 类型进行调用。

对于单元测试,请将其封装在 MessageServiceTests 类中。首先初始化 Mock 和被测试的服务:

public class MessageServiceTests
{private readonly Mock<IMessageBus> _messageBus;private readonly MessageService _service;public MessageServiceTests(){_messageBus = new Mock<IMessageBus>();_service = new MessageService(_messageBus.Object);}
}

使用 Moq 库中的 C# 泛型来模拟 IMessageBus,然后使用 Object 属性创建一个模拟实例。在所有的单元测试中私有实例变量都很有用,高可重用性的私有实例增加了类的内聚性。

使用 Moq 验证调用,一种直观的方式是尝试这么做:

_messageBus.Verify(m => m.Send(It.Is<ExpandoObject>(o => o != null && (o as dynamic).a == 1)));

但是,遗憾的是,您将看到这样的错误消息:“表达式树不能包含动态操作。” 这是因为 C# lambda 表达式无法访问 DLR,它期望一个来自 CLR 的类型,这使得此动态参数难以验证。记得您的训练,利用您的“代码感”来解决这个问题。

要处理诸如类型之间不一致的问题,请使用 Callback 方法:

dynamic message = null;_messageBus.Setup(m => m.Send(It.IsAny<ExpandoObject>())).Callback<object>(o => message = o);

请注意,Callback 方法将类型转换为 System.Object。因为所有类型都继承自 object 类型,所以可以将其赋值为 dynamic 类型。C# 可以把此 lambda 表达式中的 object 拆箱成 dynamic message

是时候为 ExpandoObject 类型编写一个漂亮的单元测试了。使用 xUnit 作为测试框架,您将看到带有 Fact 属性的方法。

[Fact]
public void SendsWithExpandoObject()
{// arrangeconst string json = "{\"a\":1}";dynamic message = null;_messageBus.Setup(m => m.Send(It.IsAny<ExpandoObject>())).Callback<object>(o => message = o);// act_service.SendRawJson<ExpandoObject>(json);// assertAssert.NotNull(message);Assert.Equal(1, message.a);
}

使用 DynamicObject 类型进行测试,重用您之前看到的 TypedDynamicJson

[Fact]
public void SendsWithDynamicObject()
{// arrangeconst string json = "{\"a\":1,\"b\":\"1\"}";dynamic message = null;_messageBus.Setup(m => m.Send(It.IsAny<TypedDynamicJson<long>>())).Callback<object>(o => message = o);// act_service.SendRawJson<TypedDynamicJson<long>>(json);// assertAssert.NotNull(message);Assert.Equal(1, message.a);Assert.Equal("a", string.Join(",", message.GetDynamicMemberNames()));
}

使用 C# 泛型,您可以在重用代码的同时转换序列化程序的动态类型。Moq 中的 Callback 方法允许您在两种类型系统之间进行必要的跳转。拥有一个优雅的类型层次结构和一个共同的父类成为了一个救星。

Using 语句

下面的 using 语句是代码示例的一部分:

  • System: CLR 的基础类型,例如 Object 和 Console

  • System.Collections.Generic: 可枚举类型,例如 IDictionary

  • System.Dynamic: DLR 的动态类型,例如 ExpandoObject 和 DynamicObject

  • Newtonsonft.Json: JSON 序列化程序

  • Moq: 模拟库

  • Xunit: 测试框架

总结

C# 动态类型或许看起来令人望而生畏,但它在强类型系统之上有很多好处。DLR 是所有动态操作发生和与 CLR 交互的地方,类型继承使同时处理这两个类型系统变得容易。在 C# 中,动态和静态编程之间并没有对立,这两种类型系统共同协作,以创造性的方式解决动态问题。


相关链接:

  1. https://www.red-gate.com/simple-talk/dotnet/c-programming/working-with-the-dynamic-type-in-c/ Working with the Dynamic Type in C# ↩︎

作者 :Camilo Reyes
译者 :技术译民 
出品 :技术译站(https://ITTranslator.cn/)

END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/305594.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python根据列表绘制柱状图_python把一个列表画柱状图

原博文 2018-10-18 14:28 − https://blog.csdn.net/w113691/article/details/80385534... 相关推荐 2019-12-07 19:38 − 用python画简单的树 代码如下&#xff1a; import turtle as T import random import time # 画樱花的躯干(60,t) def Tree(branch, t): time.sleep(0.00…

基于电子邮件的InfoPath表单发布的注意点 [Infopath 2007]

让我们先来看看InfoPath 2007提供的其他几种发布方案&#xff1a;1. 将InfoPath模板发布到SharePoint Server或者InfoPath Form Server2. 将InfoPath模板制作成一个安装程序3. 将InfoPath模板发布到一个共享网络地址上方案1,3 都必须依赖已有服务器或网络&#xff0c;而方案2 则…

如何在 C# 8 中使用 Index 和 Range

C# 8 中有几个比较好玩的新特性&#xff0c;比如下面的这两个&#xff1a;System.Index 和 System.Range&#xff0c;分别对应着索引和切片操作&#xff0c;这篇文章将会讨论这两个类的使用。System.Index 和 System.Range 结构体 可以用它们在运行时对集合进行 index 和 slice…

Hybrid-APP技术原理

源宝导读&#xff1a;Hybrid-APP技术不仅具有“Native APP的良好交互体验”同时也具备“Web APP跨平台开发的优势”。既然Hybrid-APP有这么多优势&#xff0c;那么究竟什么样的APP才算Hybrid App呢&#xff1f;本文将分享我们的技术研究成果。一、什么是Hybrid-APP狭义的Hybrid…

基于微软ASP.NET AJAX框架开发幻灯片播放网页

一、 简介 最近&#xff0c;微软ASP.NET Ajax 1.0框架以其完整的基于Ajax的web开发方案呈现在web技术人员的前面&#xff0c;凭借与遗留ASP.NET系统的有机整合以及完全面向对象的客户端JavaScript组件模型两大绝杀正在引起越来越多的基于.NET平台的web开发者的关注。本文应该属…

推荐:.Net 5开源免费的内容管理系统

背景介绍内容管理系统&#xff08;content management system&#xff0c;CMS&#xff09;是一种位于WEB 前端&#xff08;Web 服务器&#xff09;和后端办公系统或流程&#xff08;内容创作、编辑&#xff09;之间的软件系统。内容的创作人员、编辑人员、发布人员使用内容管理…

河南信息工程学校计算机组装比赛,计算机技术系承办2019全员化试点项目计算机网络装调赛项...

2019年10月16日&#xff0c;由河南省教育厅主办、河南省职业教研室承办的技能竞赛全员化试点项目“计算机网络装调”赛项在河南信息工程学校开赛&#xff0c;来自全省37所学校的96名选手同台竞技&#xff0c;和谐交流。“计算机网络装调”赛项是2019年河南省中职教育技能竞赛的…

限制IIS站点的内存,避免级联影响

背景 代码写的有问题&#xff0c;会很容易出现内存泄露的问题。应用如果是部署在docker容器里面的&#xff0c;可以限制这个应用的内存。那么&#xff0c;如果是传统的.NET Framework应用&#xff0c;部署在IIS上面呢&#xff1f;老黄曾经遇到过在一台服务器上面&#xff0c;II…

笔记本怎么查看hdmi版本_涨知识丨一文看懂笔记本电脑HDMI接口

上一期文章我们同大家简单介绍了笔记本电脑Type-C接口及其日常用途&#xff0c;今天我们来聊聊笔记本电脑上另一个常备的电脑接口HDMI&#xff0c;它究竟是什么&#xff0c;有哪些用途&#xff0c;我们一一为您揭晓&#xff01;如图所示&#xff0c;有着HDMI标识的接口就是我们…

中小企业CRM评测-用户交互_易客

用户交互<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />如何评测在这个部分我们应用了用户接口易用性方面的几种测试方法&#xff0c;以客观的评估参测产品在用户接口设计方面的素质。物理操作负担主要是由参测产品完成一组既定…

pdf编辑软件adobe acrobat_分享一款PDF编辑和阅读软件Acrobat

Adobe Acrobat介绍是一款非常好用且功能强大的PDF编辑和阅读软件。可以对PDF文件进行查看、添加注释、填写、签名并发送之外&#xff0c;同时还可以使用一些高级工具来创建、编辑、导出和组织PDF&#xff0c;以及将任何内容转换为高质量的PDF&#xff0c;并在任何屏幕上完美呈现…

诈尸了。不瞒您说,老坑从不填,天天开新坑

诈尸了。你的年更 UP 诈尸了。不瞒您说&#xff0c;我其实有好多乱七八糟的东西想往外捯饬捯饬整理整理。写过博客&#xff0c;发过公众号&#xff0c;做过视频。但是这些对我整理分享知识来说都有一个硬伤&#xff1a;慢。而咱们 IT 工作者&#xff08;哦&#xff0c;还有培训…

打开计算机任务栏有桌面没,电脑桌面任务栏不显示打开的窗口怎么办

我们在使用电脑的时刻&#xff0c;会遇到林林总总的问题&#xff0c;有时刻我们就会遇到我们在桌面打开了一个页面&#xff0c;然则电脑桌面下方的任务栏却不显示打开的窗口&#xff0c;那这是怎么回事呢&#xff1f;我们想要任务栏显示打开的窗口又该怎么做呢&#xff1f;今天…

Visual Studio将原生支持WSL 2

喜欢就关注我们吧&#xff01;近日&#xff0c;微软官方宣布旗下的集成开发环境 Visual Studio 将原生支持 WSL 2&#xff0c;这意味着 VS 用户可以在 WSL 2 上进行无缝构建和调试&#xff0c;而无需添加 SSH 连接&#xff0c;提升运行效率。据悉&#xff0c;微软曾在 2019 年将…

python删除符合条件的行_这十道经典Python笔试题,全做对算我输

经常有小伙伴学了Python不知道是否能去找工作&#xff0c;可以来看下这十道题检验你的成果&#xff1a;1、常用的字符串格式化方法有哪些&#xff1f;并说明他们的区别a. 使用%&#xff0c;语法糖print("我叫%s&#xff0c;今年%d岁" % ("oxs", 18)) # 我叫…

springmvc如何使用视图解析器_SpringMVC工作原理

SpringMVC工作原理图&#xff1a;SpringMVC流程1、 用户发送请求至前端控制器DispatcherServlet。2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找)&#xff0c;生成处理器对象及处理器拦截器(…

你有把依赖注入玩坏?

【导读】自从.NET Core给我们呈现了依赖注入&#xff0c;在我们项目中到处充满着依赖注入&#xff0c;虽然一切都已帮我们封装好&#xff0c;但站在巨人的肩膀上&#xff0c;除了凭眺远方&#xff0c;我们也应平铺好脚下的路使用依赖注入不仅仅只是解耦&#xff0c;而且使代码更…

mysql 表与表之间的条件比对_值得收藏 | 一份最完整的MySQL规范

一、数据库命令规范所有数据库对象名称必须使用小写字母并用下划线分割所有数据库对象名称禁止使用MySQL保留关键字(如果表名中包含关键字查询时&#xff0c;需要将其用单引号括起来)数据库对象的命名要能做到见名识意&#xff0c;并且最后不要超过32个字符临时库表必须以tmp_为…

asp.net core服务中的限流

使用了AspNetCoreRateLimit三方库&#xff0c;starup.cs配置如下。using AspNetCoreRateLimit; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Exte…

sublime text html乱码,Sublime Text 2中文显示乱码的解决方法

Sublime Text 2中文显示乱码的解决方法发布时间&#xff1a;2014-05-12 15:30:14 作者&#xff1a;佚名 我要评论这篇文章主要介绍了Sublime Text 2中文显示乱码的解决方法,需要的朋友可以参考下1、安装Sublime Package Control。在Sublime Text 2上用Ctrl&#xff5e;打开…