思维泛化是什么意思

核心提示1.前言往往一些刚接触C#编程的初学者,对于泛型的认识就是直接跳到对泛型集合的使用上,虽然微软为我们提供了很多内置的泛型类型,但是如果我们只是片面的了解调用方式,这会导致我们对泛型盲目的使用。至于为什么要使用泛型,什么情况下定义属于自己的泛

1.介绍

通常,一些初学C#编程的初学者通过直接使用泛型集合来了解泛型。虽然微软已经为我们提供了很多内置的泛型类型,但是如果我们只知道片面的调用它们,就会导致我们盲目的使用泛型。至于为什么要用泛型,什么情况下要定义自己的泛型,它能给程序带来什么好处。梳理这些问题,必须深刻理解泛型的本质,形成泛型编程的思维模式。

接下来我会基于一个基本的例子让泛型在关键时刻脱颖而出,然后通过需求不断进化的例子,让我们深刻体会到泛型的作用。假设。NET没有给我们提供一个存储数据的集合,但是我们需要一个可以用来存储字符串元素的集合。基于这种情况,我们定制了一个集合类来存储字符串:

重复

目前已经创建了string类型的集合,现在又收到了一个新的需求,就是需要一个集合来存储int类型的元素。基于自定义字符串集合的经验,我们可以发现字符串集合的结构和内容与我们即将创建的int集合几乎相同。这意味着我们可以使用江湖上流行的“复制解决方案”,把之前的代码复制一遍,然后稍微修改一下。以下是两个集合类型代码的对比图。

早些年,有一个流行的游戏叫“我们来找茬”。这个游戏的主要玩法是找出两张大致相同的图片之间的细微差别。我们使用的“复制解决方案”促使我们编写的代码形成了一个可以用于这个游戏的场景。"对于上面两张代码截图,你能找出图中的区别吗?"

对于软件开发者来说,他们面临的主要敌人是“变化”。假设以后会有N种类型的元素需要我们去定义和存储,我们是不是要无休止的复制同一个代码?干,请记住这是作为软件开发人员编码的原则,“抄大法”显然违背了这个原则。

3.安全和性能

通过“复制粘贴”的方式,可以明显感觉到自己在做重复的事情。在重复中,我们可以发现集合存储的类型在增加,但是集合的结构和添加元素的方法都是同一个逻辑。简单来说,不同类型的处理的处理逻辑是相似的。基于这个特性,为了满足自定义集合可以应付所有类型存储的要求,我们必须使用一个通用类型作为代表。这时,一句话可以浮现在我们的脑海里:object是所有类型的基类。这意味着我们添加的所有类型都可以隐式转换为对象类型,因此任何类型的元素都可以添加到自定义集合中。让我们试试这个对象类型:

在上面的代码中,我们结合了object是所有类型的基类的特点,修改了集合类型,通过这种方式成功地向集合中添加了不同类型的元素。虽然从使用的角度来看这是完美的,但在获取集合的元素进行赋值时,也使用类型转换的方法。这是因为这种方式存在严重的问题,主要包括以下两个方面:

在类型安全性方面,如果集合的第一个元素是sting类型,但是你客观上认为是int类型,所以在获取的时候就把int类型进行了强制转换。此时代码不会提示错误,可以正常编译,那么这就意味着程序在运行时会产生一个无效类型转换的意外异常。

在性能方面,当一个值类型元素被添加到一个集合中时,必然会有装箱操作;当获取一个元素并将其赋给值类型变量时,将发生相应的取消装箱操作。这种解包打包操作在操作大量元素时会大大损失程序的性能。

到目前为止,我们仍然无法创建一个可以存储任何类型的集合,但我们可以总结一下上述例子的演化过程:如果不同类型的处理逻辑相同,盲目复制会导致代码重复,如果使用object作为解决代码重复的方法,就会出现类型安全和性能的问题。至于如何彻底解决这些问题,这就是本文的主题——泛型。

4.代码模板

C中有两种不同的机制来编写跨类型的可重用代码:继承和泛型。继承的复用性来源于基类,而泛型的复用性是通过带“占位符”的代码模板类型来实现的。继承复用是从面向对象的角度考虑的,泛型复用是从实现特定功能的角度考虑的。与继承相比,泛型不必遵循Richter的替换原则,并且可以提高类型的安全性,减少类型转换导致的解包和打包。

如何理解泛型?泛型本质上相当于一种“代码模板”,可以用不同类型的同一逻辑的一组代码统一实现。“模板”这个概念需要深入理解。比如公司在招聘的时候会和用人单位签订一份劳动合同,这份劳动合同的主要内容对每个人来说几乎都是一样的,只是在极个别的地方有一些差异,比如薪资待遇,姓名等。所以公司不会专门为某个人做一份合同,而是会做一份劳动合同作为模板,其中针对个人的差异会用“划线”来预留,“划线”的值由具体用人单位在签订合同时根据自身情况填写。

对于这种模板方法的使用,公司在制定合同时不必考虑是谁签的合同。因为劳动合同和用户是分开的,公司只需要专注于合同的主要内容。但是在我们的实际编程中,使用泛型的目的其实和公司制定通用的劳动合同模板是一样的。假设你的公司需要雇佣100名员工,你不想给每个人都做一份专属合同?假设在你的代码中,遇到10种类型,它们的操作和处理逻辑都是一样的,你不会想为这10种类型写10种处理方法吧?

5.类型参数

在上面的代码中,我们将集合类型定义为一个泛型类,这个类型中出现的T属于泛型中的type参数。为了达到通用处理的目的,泛型不能把特定的类型作为处理的目标类型,所以要处理的类型用“t”作为类型占位符。

“T”并不是真正的数据类型,它更像是泛型使用的类型蓝图,所以在使用它的时候,泛型类型的消费者必须将一个特定的类型作为“类型参数”传入尖括号,这样才能构造一个具有显式处理类型的泛型实例。所以我们在对外使用泛型的时候,不能用“ArraryListlist = new ArraryList”和“T t=new T”这样的方式实例化泛型类型。另外,“t”本身只是类型参数的名称,它只是代表类型参数的标识,也就是说我们可以用其他字符来命名类型参数。

替换

通过类型参数的使用,我们可以知道泛型类型代码在静态阶段没有确定的类型,那么在程序运行时,它是如何与指定的“类型参数”接口的呢?为了理解这个问题,让我们看看泛型运行时的本质。

编译后的C#程序生成的代码不能被计算机直接执行,但会生成CIL代码并包含在程序集中。如果你想生成计算机可执行的代码,你需要JIT再次编译CIL代码。但是,当一个泛型类型确认了它的具体类型,就在JIT再次编译的时候,如果JIT编译的代码包含泛型内容,它会根据泛型类型的消费者指定的类型参数,将CIL中的泛型代码中的占位符T替换为具体类型,从而明确当前执行的泛型代码是用于哪种类型,其中替换过程在运行时由CLR主导,JIT实际完成操作。这种在运行时被确认的泛型类型也被称为“封闭类型”,而在运行时确认之前的泛型类型被称为“开放类型”。

运行时使用通用占位符替换特定类型的机制,其实和本文劳动合同模板中使用“下划线”的例子有着相同的思路。指定劳动合同模板时,不能为用人单位名称写具体名称。因为模板的目的是使其通用,所以名称带有下划线。公司与特定人员签订合同时,劳动合同模板中的下划线会由用人单位根据自身情况填写。泛型也是如此。我们使用泛型是为了使多种类型的处理具有通用性,所以在定义泛型代码时不能指定具体的类型,而是使用类型参数T,相当于劳动合同模板中的“下划线”。当泛型实际运行时,JIT会根据泛型消费者指定的具体类型替换占位符T。

7.摘要

这篇文章并不特别适合介绍泛型的使用细节。而是通过一个实例根据需求不断进化的过程,一步步深入到泛型中,从而对泛型的使用初衷有更深刻的理解。相比理解泛型的“字”,形成泛型的编程理念和思维尤为重要。在泛型的机制中,我们可以在不同类型的处理逻辑相同的情况下形成一个通用的方案,这样就不用编码一个特定的类型了。一套通用的代码模板可以服务更多的类型,并且可以保证类型安全,提供良好的使用性能。

 
友情链接
鄂ICP备19019357号-22