C# 泛型编程

 2024-03-14 01:09:56  阅读 0

1. 为什么要使用泛型编程?

我们在写程序的时候,经常会遇到两个功能非常相似的模块,只不过一个处理的是int数据,另一个处理的是data,或者其他自定义的数据类型。 但我们没办法,只能单独写多个方法。 由于方法的参数类型不同,因此对每种数据类型进行处理。 有没有办法在方法中传入通用的数据类型,这样就可以合并代码了? 泛型的出现就是专门为了解决这个问题而设计的。 读完本文,您将对泛型有更深入的了解。

我们先简单看一下下面的需求及其解释,稍后我们会详细解释具体的功能。

我们现在需要实现一个栈,它只能处理int数据类型:

泛型方法重写_c 泛型方法_泛型方法C#

上面的代码运行得很好,但是当我们需要栈来保存类型时该怎么办呢? 很多人会想到复制上面的代码,把int改成no。 当然这样做本身并没有什么错,但是一个优秀的程序不会这样做,因为他会想如果以后又需要一个long或者Node类型的栈怎么办? 是不是应该再抄一遍? 优秀的程序作者想使用通用的数据类型来实现这个堆栈:

泛型方法C#_c 泛型方法_泛型方法重写

这个堆栈写得很好。 它非常灵活,可以接收任何数据类型。 可以说是一劳永逸了。 但综合来看,它也并非没有缺陷,主要体现在:

Stack处理值类型时,会发生装箱和折叠操作,这会在托管堆上分配和回收大量变量。 如果数据量很大,性能损失会非常严重。

在处理引用类型时,虽然没有装箱或折叠操作,但会使用数据类型强制转换操作,这增加了处理器的负担。

所以泛型出现了,真正解决了我们的问题。

下面是使用泛型重写上面的栈,使用一个普通的数据类型T作为占位符,等待实例化时被实际类型替换。 让我们看看泛型的威力:

c 泛型方法_泛型方法C#_泛型方法重写

类的写法不变,但引入了通用数据类型T,可以适用于任何数据类型,且类型安全。 该类的调用方法:

泛型方法重写_c 泛型方法_泛型方法C#

泛型方法C#_c 泛型方法_泛型方法重写

这个类与实现的类完全不同:

1.它是类型安全的。如果实例化了int类型的堆栈,则无法处理该类型。

数据,以及其他数据类型。

2、无需装箱折叠盒子。该类实例化时,根据传入的数据类型生成。

原生代码,原生代码数据类型是确定的,所以不需要装箱和折叠。

3. 无需类型转换。

2.如何使用泛型?

泛型的意义在于它代表一种通用类型,可以替代任何数据类型,使类型参数化,从而达到仅用一种方法操作多种类型数据的目的。 泛型将方法实现行为与方法操作的数据类型分开,从而实现代码重用。 下面的代码演示了泛型的作用。

c 泛型方法_泛型方法C#_泛型方法重写

c 泛型方法_泛型方法C#_泛型方法重写

如果我们要比较两个数字,具体代码如下。 如果不引入泛型,我们下面的比较代码必须这样实现:

泛型方法C#_泛型方法重写_c 泛型方法

泛型方法C#_c 泛型方法_泛型方法重写

虽然代码满足要求,但是如果我们想再次添加浮点类型,就需要再次修改代码,非常麻烦。 因此,提供了泛型的特性,使得类型可以参数化。 使用时,如下:

泛型方法重写_c 泛型方法_泛型方法C#

上面我们实现了一个自定义泛型类,其中T是泛型的类型参数,是实现的泛型方法。 代码where是对参数类型的约束,用于使类型参数适用于方法。 这样,当我们调用main函数时,就如下:

泛型方法重写_c 泛型方法_泛型方法C#

泛型不仅可以实现代码重用,还可以减少装箱和拆箱的过程。 仿制药是避免损失的有效方法。 下面我们通过代码来测试使用泛型和不使用泛型的执行时间。

泛型方法重写_泛型方法C#_c 泛型方法

泛型方法重写_泛型方法C#_c 泛型方法

从结果可以看出,泛型数组添加元素的效率比非泛型数组高很多,因为在非泛型Add操作中,参数是类型,传入int时,会发生装箱操作,因此导致性能损失和时间变长。

3. 泛型综合分析 3.1 类型参数

泛型分为开放泛型和封闭泛型。 开放类型是指包含类型参数的泛型类型,但属于未绑定类型; 封闭类型是指为每个类型参数传递的数据类型。 下面的代码演示了该方法是打开还是关闭。

泛型方法重写_泛型方法C#_c 泛型方法

c 泛型方法_泛型方法C#_泛型方法重写

上面的代码使用 t.eters 属性来确定类型对象是否包含尚未被实际类型替换的类型参数。 如果存在则返回true,表示开放类型; 如果不存在,则返回 false,表示封闭类型。 静态数据类型是类型。 如果定义了一个静态字段x,那么无论以后创建该类的多少个实例,或者派生出多个实例,都只会存在一个。 泛型的情况并非如此,每个封闭的泛型都有自己的静态数据。

泛型方法重写_c 泛型方法_泛型方法C#

泛型方法重写_泛型方法C#_c 泛型方法

泛型方法重写_泛型方法C#_c 泛型方法

泛型方法重写_泛型方法C#_c 泛型方法

通用方法

有时候,我们需要缩小泛型T的作用域,直接应用到方法中,这样就可以缩小作用域。 这是一个通用方法。

c 泛型方法_泛型方法重写_泛型方法C#

泛型方法重写_c 泛型方法_泛型方法C#

4.类型参数约束

在我们前面实现的泛型中,我们使用了where T:代码,其中where语句用于使类型继承于接口,从而约束类型参数。 我们来看看泛型的常见约束。

4.1 基类约束

基类约束有两个功能。 首先,它允许在泛型类中对指定基类定义的成员使用自由约束。 通过引发基类约束,编译器将知道所有类型参数都具有由指定基类定义的成员。 基类约束的第二个功能是确保仅使用支持指定基类的类型参数。 这意味着对于任何给定的基类约束,类型参数必须是基类本身或从基类派生的类。 基类约束使用以下形式的 where 子句:

其中 T: 基类名称

其中,T是类型参数的名称,base-class-name是基类的名称。 只能指定一个基类。

4.2 接口限制

接口约束指定某种类型的参数必须实现的接口。 它的两个主要功能与基类约束相同,允许开发人员使用泛型类中的接口成员; 确保只能使用实现特定接口的类型参数。 也就是说,对于任何给定的接口约束,类型参数必须是接口本身或实现该接口的类。 接口约束使用的 where 子句具有以下形式:

其中 T:-名称

其中,T是类型参数的名称,-name是接口的名称。可以

指定多个接口,以逗号分隔。 如果约束同时包含基类和接口,则需要先指定基类列表,然后指定接口列表。

4.3 new()构造函数约束

new() 构造函数约束允许开发人员实例化泛型类型的对象。 new() 约束要求类型参数必须提供无参数公共构造函数。 当使用new()约束时,可以通过调用无参数构造函数来创建对象。 new() 构造函数约束的形式为:

其中 T:new()

使用new()约束时需要注意三点:

1.new()约束可以和其他约束一起使用,但必须位于约束列表的末尾

2. new() 约束仅允许开发人员使用无参数构造函数构造对象。

即使同时存在其他构造函数也是如此。即不允许构造类型参数

函数传递实际参数。

3. new()约束和值类型约束不能同时使用。因为值类型是隐式提供的

提供无参数的公共构造函数。就像定义接口时一样,指定访问类型为

同样,编译器也会报告错误,因为接口必须是。

4.4 引用类型约束

引用类型约束将类型参数限制为引用类型。 引用类型一般是用户定义的类型,包括类、接口、委托、字符串和数组类型。引用类型约束使用class关键字,其一般形式为

其中 T:类

在此 where 子句中,class 关键字指定 T 必须是引用类型。 因此,尝试将值类型与 T 一起使用将导致编译错误。

4.5 值类型约束

值类型约束将类型参数限制为值类型。 值类型派生自 .type。 基元和结构都是值类型。 值类型约束使用关键字,其一般形式为:

其中 T:

在这种形式中,关键字指定 T 必须是值类型。 因此,尝试对 T 使用引用类型将导致编译错误。

4.6 组合约束

同一类型参数可以使用多个约束。 在这种情况下,需要一个以逗号分隔的约束列表。 在列表中,第一个约束必须是引用类型约束、值类型约束或基类约束。 在指定基类约束的同时指定引用类型约束或值类型约束是非法的。 接下来必须是所有接口约束,最后是 new() 约束。 以下为法律声明:

在上面的声明中,用于替换 T 的类型参数必须继承该类,实现该接口,并且具有无参数构造函数。

当使用两个或多个类型参数时,可以使用多个where子句分别为其指定约束。

泛型方法C#_c 泛型方法_泛型方法重写

泛型方法C#_泛型方法重写_c 泛型方法

5. 通用委托

与方法一样,委托也可以是通用的。 由于泛型固有的类型安全性,不兼容的方法不能分配给委托。 声明通用委托的形式是:

ret-类型名称(arg-

列表);

类型参数声明遵循委托的名称。 通用委托的优点是它允许开发人员以类型安全的方式定义通用形式,该形式可用于匹配任何兼容的方法。

6. 通用接口

除了定义泛型类和泛型方法之外,还可以定义泛型接口。 泛型接口的定义与泛型类的定义基本相同。 泛型接口的声明形式为:

使用泛型时,请注意以下限制:

1. 修饰符不能用于泛型方法。

2. 属性、运算符、索引器和事件不能是通用的。 这些项仍然可以在泛型中使用,并且可以使用类的泛型类型参数。

3. 指针类型不能用作类型参数。

4. 如果泛型类包含字段,那么每个构造类型都会有该字段的独立副本。 这意味着相同构造函数类型的所有实例将共享相同的字段。 然而,不同的构造类型使用不同的字符副本。 因此,字段并非由所有构造类型共享。

7. 泛型类中的方法重载

方法重载广泛应用于.Net 中的泛型类中。 由于一般类型T在类编写时并未确定,因此重载时有一些注意事项。 我们通过以下例子来说明这些问题:

c 泛型方法_泛型方法重写_泛型方法C#

泛型方法重写_c 泛型方法_泛型方法C#

上面的类是显而易见的。 如果 T 和 V 都以 int 形式传递,则三个 add 方法将具有相同的签名,但此类仍然可以编译。 是否会造成调用混乱,将在实例化该类并调用add方法时确定。 法官。 请看下面的调用代码:

该Node的实例化导致了三个具有相同签名的add,但是调用成功,因为它首先匹配了第三个add。 但如果删除第三个add,则上述调用代码无法编译,提示方法造成的混乱,因为运行时无法在第一个add和第二个add之间进行选择。

这两行调用代码可以正确编译,因为传入和int使得三个add具有不同的签名,当然可以找到唯一匹配的add方法。

标签: 类型 约束 泛型

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码