URLhttps://learnscript.net/zh/programming/object-oriented/generics/
    复制链接转到说明  示例

    泛型,类型参数,泛型约束介绍

    我被代码海扁署名-非商业-禁演绎
    阅读 5:32·字数 1664·发布 

    泛型

    泛型可用于推迟数据类型的确定,这项工作在正常的定义过程中往往需要立即进行。比如,在定义类时,你需要指定字段的数据类型,方法形参的数据类型等。

    为何使用泛型?

    很明显,泛型减少了设计层面的数据类型转换,同时保持了代码的适用性和简便性。

    类型参数

    泛型使用类型参数来代替本应被确定的数据类型,虽然也被称为参数,但类型参数的作用类似于占位符。在对使用了泛型的目标进行实例化或调用时,需要给出类型参数,比如 C# 中常见的泛型类List<T>,将其类型参数T替换为某种具体的数据类型后,实例化才能进行。

    下面的 C# 代码实例化了泛型类List<T>

    *.cs
    // 类型参数 T 被指定为 string
    List<string> nicknames = new();

    泛型方法

    如果你希望定义一个泛型方法,那么该方法需要拥有自己的类型参数,他不能与类或接口的类型参数相同,如果类或接口也运用了泛型的话。

    下面的 C# 类Box拥有一个泛型方法GetFirst,用于寻找第一个数据类型与T一致的元素。

    generics.cs
    // 类 Box,表示存储内容的箱子
    class Box
    {
    	// 字段 items,一个简单的数组
    	object[] items;
    
    // 构造器,可以初始化 items public Box(params object[] i) { items = i ?? Array.Empty<object>(); }
    // 泛型方法 GetFirst,获取数组中第一个类型为 T 的元素 public T? GetFirst<T>() { // 如果元素的类型与 T 一致,则返回 foreach (object item in items) if (item.GetType() == typeof(T)) return (T)item;
    return default; } }

    使用泛型方法GetFirst寻找第一个字符串,整数,浮点数类型的元素。

    *.cs
    // 创建 Box 的实例 box
    Box box = new("第一个字符串", 123, "第二个字符串", 234, 0.123f, 0.234f);
    // 获取 box 中的第一个字符串
    Console.WriteLine(box.GetFirst<string>());
    // 获取 box 中的第一个整数
    Console.WriteLine(box.GetFirst<int>());
    // 获取 box 中的第一个浮点数
    Console.WriteLine(box.GetFirst<float>());
    第一个字符串
    123
    0.123

    泛型约束

    泛型约束主要用于限制泛型的使用,比如,限制类型参数可以指定的数据类型。对类型参数加以约束是理所当然的,因为他给予了开发人员很大的选择范围,没人愿意类型参数被指定为一种无法被处理的数据类型。

    我们为GetFirst方法的类型参数T增加约束,使其只能被指定为引用类型,之前书写的box.GetFirst<int>()box.GetFirst<float>()将产生错误。

    generics.cs
    public T? GetFirst<T>() where T : class
    {
    	// …
    }

    泛型的处理

    对于接下来讲述的内容,我们作出如下假设,语言包含泛型类List<T>,以及引用类型object,值类型intfloat

    在代码编译期间,泛型可能会以异构或同构的方式被处理,这取决于具体的语言编译器。

    异构会为不同的类型参数构建不同的代码,如果你使用了List<int>List<float>,那么编译器需要为他们产生两个不同的类,以将类型参数T分别替换为intfloat

    同构会将泛型的类型参数转换为最宽泛的数据类型,比如,无论你书写List<int>还是List<float>,编译器都会将类型参数T替换为object,因此,相对于异构,同构产生的类只有一个而不是多个。在这种情况下,编译器可能还需要调整某些代码,以完成额外的类型转换,毕竟,在开发人员书写的代码中,List<int>List<float>可以直接关联intfloat类型的值,这些值的类型与编译时出现的object类型存在转换的必要。

    为何采用异构的泛型能够改善效率?

    假如数据类型的转换将消耗大量资源,比如,引用类型与值类型之间的相互转换,那么采用异构的泛型会是一种很好的纾困方案,因为他使得相关的转换不再存在。

    上述中的数据类型转换之所以会发生,是由于设计者希望采用一种宽泛的标准来涵盖所有的可能,比如,使用数组object[]来存储所有数据类型的值,当你将一个整数类型的值存入数组,或将其从数组中取出以进行算术运算时,引用类型object与值类型int之间的转换就会发生。而这样的情况会在使用异构泛型后消失,因为最终生成的数组是int[]而非object[],将int类型的值存入int[]当然不需要任何转换。

    栈,堆

    要想深入了解值类型和引用类型,你可以查看堆和引用一段。

    源码

    generics.cs·codebeatme/programming·GitHub