【.NET Core】泛型(Generics)详解

2023-12-14 23:44
文章标签 详解 core net 泛型 generics

本文主要是介绍【.NET Core】泛型(Generics)详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【.NET Core】泛型(Generics)详解

文章目录

  • 【.NET Core】泛型(Generics)详解
    • 一、概述
    • 二、泛型类型参数
    • 三、泛型中类型参数的约束
      • 3.1 where T:struct
      • 3.2 where T:class
      • 3.3 where T:class?
      • 3.4 where T:notnull
      • 3.5 where T:default
      • 3.6 where T:unmanaged
      • 3.7 where T:new()
      • 3.8 where T:<基类名>
      • 3.9 where T:<基类名>?
      • 3.10 where T:<接口名称>
      • 3.11 where T:<接口名称>?
      • 3.12 where T:U
      • 3.13 对参数应用多个约束
      • 3.14 约束多个参数
    • 四、泛型类
    • 五、泛型接口
    • 六、泛型方法
      • 6.1 泛型方法约束
      • 6.2 泛型方法重载
    • 七、泛型委托
    • 八、运行时中的泛型
      • 8.1 值类型
      • 8.2 引用类型

一、概述

泛型是为所存储或使用的一个或多个类型具有占位符(类型形参)的类、结构、接口和方法。泛型集合类可以将类型形参用作其存储的对象的占位符;类型形参程序为字段的类型或其方法的参数类型。泛型方法可将其类型形参用作其返回值的类型或用作其形参之一的类型。

为了方便理解,我们用ArrayList为例,在.NET Framework1.0中,ArrayList元素属于Object类型。添加到集合的任何元素都会以静默方式转换为Object。自此过程中会发生装箱拆箱的过程,在装箱和拆箱的类型转换过程中,会影响性能。这个是因为在编译的时候无法确认数据的类型,数据的类型只能在运行阶段确定,这个过程就导致性能消耗。为了解决这个问题微软在NET Framework 2.0中首次引入了这个泛型,它本质上是一个"代码模板",让开发人定义类型安全的数据结构,这样就避免了在装箱和拆箱过程性能损失,或在运行中的异常。

下面我们演示一下非泛型和泛型性能的差异。

List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
Stopwatch s = Stopwatch.StartNew();
ListGeneric.Sort();
s.Stop();
Console.WriteLine($"Generic Sort: {ListGeneric}  \n Time taken: {s.Elapsed.TotalMilliseconds}ms");Stopwatch s2 = Stopwatch.StartNew();
ListNonGeneric.Sort();
s2.Stop();
Console.WriteLine($"Non-Generic Sort: {ListNonGeneric}  \n Time taken: {s2.Elapsed.TotalMilliseconds}ms");
Console.ReadLine();

运行结果

Generic Sort: System.Collections.Generic.List`1[System.Int32]
Time taken: 0.0119ms
Non-Generic Sort: System.Collections.ArrayList
Time taken: 0.2944ms

从运行结果我们可以看出装箱和拆箱的过程中性能损失挺大。

二、泛型类型参数

类型参数是在其创建泛型类型的一个实例时,客户端指定的特定类型的占位符。泛型类在定义以后,无法直接使用,必须指定真正的类型后,才能使用。每个类型必须通过指定尖括号内的类型参数来声明并实例化构造类型。此特定的类型一定是编译器可识别的任何类型。

实例如下:

GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();

在GenericList的每个实例中,类中出现的每个T在运行时均会被替换为类型参数。通过这种替换,通过使用单个类定义创建了三个单独的类型安全的有效对象

三、泛型中类型参数的约束

约束告知编译器类型参数必须具备的功能。在没有任何约束的情况下,类型参数可以是任何类型。编译器只能预设为System.Object的成员,System.Object类型是任何类型的基类。如果在使用泛型时,不能满足约束的类型,编译器将会发生错误。通过使用where关键字指定约束。

下面列出了各种类型的约束

3.1 where T:struct

类型参数必须是不可为null的值类型,由于所有值类型都具有可访问的无参数构造函数,因此struct约束表示 new()约束,并且不能与new()约束一起使用。struct 约束也不能与 unmanaged 约束结合使用。

public class GenericsStructCLS<T> where T : struct
{
}

3.2 where T:class

类型参数必须是引用类型,此约束还应用于任何类、接口、委托或数组类型。在可为null的上下文中,T必须是不可为null的引用类型。

public class GenericsClassCLS<T> where T : class
{
}

3.3 where T:class?

类型参数必须是为null或不可为null的引用类型。此约束应该用于任何类,接口、委托或数组类型。

public class GenericsClassNullCLS<T> where T : class?
{
}

3.4 where T:notnull

类型参数必须是不可为null的类型。参数可以是不可为null的引用类型,也可以是不可为null的值类型。

public class GenericsClassNotNullCLS<T> where T : notnull
{
}

3.5 where T:default

重写方法或提供显示接口实现时,如果需要指定不受约束的类型参数,此约束可解决歧义。default约束表示基方法,但不包含classstruct约束。

public class GenericsClassDefault<T> where T:default
{
}

default 约束表示基方法,但不包含 class 或 struct 约束

3.6 where T:unmanaged

类型参数必须是不可为null的非托管类型。unmanaged约束表示structe约束,且不能与struct约束或new()约束结合使用。

public class GenericsClassUnmanagedCLS<T> where T : unmanaged
{
}

3.7 where T:new()

类型参数必须具有公共无参数构造函数。 与其他约束一起使用时,new() 约束必须最后指定。 new() 约束不能与 structunmanaged 约束结合使用。

public class GenericsClassNewCLS<T> where T : new()
{//类方法
}

3.8 where T:<基类名>

类型参数必须是指定的基类或派生自指定的基类。在可为null的上下文中,T必须是从指定基类派生的不可为null的引用类型。

public class Base{}
public class GenericsClassBaseCLS<T> where T : Base 
{//类方法
}

3.9 where T:<基类名>?

类型参数必须是指定的基类或派生自指定的基类。 在可为 null 的上下文中,T 可以是从指定基类派生的可为 null 或不可为 null 的类型。

public class Base{}
public class GenericsClassBaseCLS<T> where T : Base? 
{//类方法
}

3.10 where T:<接口名称>

类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在的可为 null 的上下文中,T 必须是实现指定接口的不可为 null 的类型。

public interface IBase { }
public class GenericsClass<T> where T : IBase 
{//类方法
}

3.11 where T:<接口名称>?

类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在可为 null 的上下文中,T 可以是可为 null 的引用类型、不可为 null 的引用类型或值类型。 T 不能是可为 null 的值类型。

public interface IBase { }
public class GenericsClass<T> where T : IBase? 
{//类方法
}

3.12 where T:U

T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 在可为 null 的上下文中,如果 U 是不可为 null 的引用类型,T 必须是不可为 null 的引用类型。 如果 U 是可为 null 的引用类型,则 T 可以是可为 null 的引用类型,也可以是不可为 null 的引用类型。

public class BaseClass<T> { }
public class UTClass<T> where T: BaseClass<T> 
{//类方法
}

3.13 对参数应用多个约束

public class UTClass<T> where T: BaseClass<T> ,IBase,new()
{
}

3.14 约束多个参数

不但可以对参数应用多个约束,也可以对多个参数应用多个约束。

class Base { }
class Test<T, U>where U : structwhere T : Base, new()
{//类方法
}

四、泛型类

泛型类封装不特定于特定类型的操作。泛型类最常见用法是用于链接列表,哈希表、堆栈、队列和树等集合。无论存储数据的类型如何,添加项和从集合删除项等操作的执行方式基本相同。

创建自己的泛型类时,需要考虑以下重要注意事项:

  • 要将哪些类型泛化为类型参数
  • 如何给泛型类添加参数约束
  • 是否将泛型行为分解基类和子类
  • 实现一个泛型接口还是多个泛型接口
class BaseNode { }
class BaseNodeGeneric<T> { }
class NodeConcrete<T> : BaseNode { }
class NodeClosed<T> : BaseNodeGeneric<int> { }
class NodeOpen<T> : BaseNodeGeneric<T> { }

泛型类的特性

  1. 非泛型类可继承自封闭式构造基类,但不可继承自开放式构造类或类型参数;
  2. 泛型类继承自开放构造类型的泛型类必须保持参数相同;
  3. 泛型类型可使用多个类型参数和约束;
  4. 开放式构造和封闭式构造类型可用作方法参数;

五、泛型接口

为避免对值类型执行装箱和拆箱操作,最好对泛型类使用泛型接口。.NET类库定义多个泛型接口,以便用于System.Collections.Generic命名空间中的集合类。

泛型接口提供与非泛型接口对应的类型安全接口,用于实现排序比较,相等比较以及泛型集合类型所共享的功能。

public class GenericList<T>:System.Collections.Generic.IEnumerable<T>
{//方法体
}

泛型接口可将多个接口指定微单个类型上的约束。

class Stack<T> where T:System.IComparable<T>,IEnumerable<T>
{//方法体
}

一个接口可定义多个类型参数:

interface IDictionary<K,V>
{//方法体
}

泛型类既可实现泛型接口或封闭式构造接口。

interface IBaseInterface1<T> { }
interface IBaseInterface2<T,U> { }
class SampleClass1<T> : IBaseInterface1<T> { }
class SampleClass2<T> : IBaseInterface2<T, string> { }

从C# 11开始,接口可以声明static abstractstatic virtual成员。声明任一static abstractstatic virtual成员的接口几乎始终是泛型接口。编译器必须在编译时解析对 static virtualstatic abstract 方法的调用。 接口中声明的 static virtualstatic abstract 方法没有类似于类中声明的 virtualabstract 方法的运行时调度机制。 相反,编译器使用编译时可用的类型信息。 这些成员通常是在泛型接口中声明的。

六、泛型方法

泛型方法是通过类型参数声明的方法。如下所示:

public  void Swap<T>(ref T ins, ref T ot) 
{T temp;temp=ins;ins = ot;ot=temp;
}

如果定义一个具有与包含类相同的类型参数的泛型方法,编译器会生成警告CS0693( 警CS0693类型参数“T”与外部类型“GenericsMethodClass<T>”中的类型参数同名 )。如果需要使用类型参数调用泛型类方法所具备的灵活性,可以考虑为此方法的类型参数提供另一标识符。

class GenericList<T>
{//CS0693.void SampleMethod<T>(){}
}
class GenericList2<T>
{//No warning.void SampleMethod<U>(){}
}

6.1 泛型方法约束

void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>
{T temp;if (lhs.CompareTo(rhs) > 0){temp = lhs;lhs = rhs;rhs = temp;}
}

6.2 泛型方法重载

void DoWork(){}
void DoWork<T>(){}
void DoWork<T,U>(){}

七、泛型委托

委托可以定义它自己的类型参数。引用泛型委托的代码可以指定类型参数以创建封闭式构造类型。

示例如下:

public delegate T DelMetho<T>(T item);
public static int Notify(int a) { return a; }
public static string Notify1(string b) { return b; }
DelMetho<int> metho = new DelMetho<int>(Notify);
DelMetho<string> method = new DelMetho<string>(Notify1);

C#2.0版具有一种称为方法组转换的新功能,使用于具体委托类型和泛型委托类型,能简化语法编写:

public delegate T DelMetho<T>(T item);
public static int Notify(int a) { return a; }
public static string Notify1(string b) { return b; }
DelMetho<int> metho =Notify;

八、运行时中的泛型

泛型类型或方法编译为MSIL时,它包含将其标识为具有类型参数的元数据。MSIL根据所提供的类型参数是值类型还是引用类型而有不同。

8.1 值类型

使用值类型作为参数首次构造泛型类型时,运行时创建专用的泛型类型,MSIL内的适当位置替换提供的一个或多个参数。为每个用参数的唯一值类型一次创建专用化泛型类型。

8.2 引用类型

引用类型,泛型的作用方式略有不同。首先使用任意引用类型构造泛型类型时,运行时创建一个专用化泛型类型,用对象引用替换 MSIL 中的参数。 之后,每次使用引用类型作为参数实例化已构造的类型时,无论何种类型,运行时皆重新使用先前创建的专用版泛型类型。 原因可能在于所有引用大小相同。

这篇关于【.NET Core】泛型(Generics)详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Linux之platform平台设备驱动详解

《Linux之platform平台设备驱动详解》Linux设备驱动模型中,Platform总线作为虚拟总线统一管理无物理总线依赖的嵌入式设备,通过platform_driver和platform_de... 目录platform驱动注册platform设备注册设备树Platform驱动和设备的关系总结在 l

Olingo分析和实践之EDM 辅助序列化器详解(最佳实践)

《Olingo分析和实践之EDM辅助序列化器详解(最佳实践)》EDM辅助序列化器是ApacheOlingoOData框架中无需完整EDM模型的智能序列化工具,通过运行时类型推断实现灵活数据转换,适用... 目录概念与定义什么是 EDM 辅助序列化器?核心概念设计目标核心特点1. EDM 信息可选2. 智能类

Olingo分析和实践之ODataImpl详细分析(重要方法详解)

《Olingo分析和实践之ODataImpl详细分析(重要方法详解)》ODataImpl.java是ApacheOlingoOData框架的核心工厂类,负责创建序列化器、反序列化器和处理器等组件,... 目录概述主要职责类结构与继承关系核心功能分析1. 序列化器管理2. 反序列化器管理3. 处理器管理重要方

从入门到精通详解LangChain加载HTML内容的全攻略

《从入门到精通详解LangChain加载HTML内容的全攻略》这篇文章主要为大家详细介绍了如何用LangChain优雅地处理HTML内容,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录引言:当大语言模型遇见html一、HTML加载器为什么需要专门的HTML加载器核心加载器对比表二

Python使用openpyxl读取Excel的操作详解

《Python使用openpyxl读取Excel的操作详解》本文介绍了使用Python的openpyxl库进行Excel文件的创建、读写、数据操作、工作簿与工作表管理,包括创建工作簿、加载工作簿、操作... 目录1 概述1.1 图示1.2 安装第三方库2 工作簿 workbook2.1 创建:Workboo

Python实现中文文本处理与分析程序的示例详解

《Python实现中文文本处理与分析程序的示例详解》在当今信息爆炸的时代,文本数据的处理与分析成为了数据科学领域的重要课题,本文将使用Python开发一款基于Python的中文文本处理与分析程序,希望... 目录一、程序概述二、主要功能解析2.1 文件操作2.2 基础分析2.3 高级分析2.4 可视化2.5

Java实现预览与打印功能详解

《Java实现预览与打印功能详解》在Java中,打印功能主要依赖java.awt.print包,该包提供了与打印相关的一些关键类,比如PrinterJob和PageFormat,它们构成... 目录Java 打印系统概述打印预览与设置使用 PageFormat 和 PrinterJob 类设置页面格式与纸张

MySQL 8 中的一个强大功能 JSON_TABLE示例详解

《MySQL8中的一个强大功能JSON_TABLE示例详解》JSON_TABLE是MySQL8中引入的一个强大功能,它允许用户将JSON数据转换为关系表格式,从而可以更方便地在SQL查询中处理J... 目录基本语法示例示例查询解释应用场景不适用场景1. ‌jsON 数据结构过于复杂或动态变化‌2. ‌性能要

Python实现终端清屏的几种方式详解

《Python实现终端清屏的几种方式详解》在使用Python进行终端交互式编程时,我们经常需要清空当前终端屏幕的内容,本文为大家整理了几种常见的实现方法,有需要的小伙伴可以参考下... 目录方法一:使用 `os` 模块调用系统命令方法二:使用 `subprocess` 模块执行命令方法三:打印多个换行符模拟

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、