C# 基础之 - 泛型

270 阅读5分钟

什么是泛型

  • 本文已参与「新人创作礼」活动,一起开启掘金创作之路。

泛型是类型安全的集合。
官方:泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。

泛型的三大好处

  1. 类型安全
    • 在ArrayList中是允许将各种类型插入的如(int、string、bool)等等,如果需要转换成int类型,在强制转换的过程中,非int类型会出现运行时异常。在泛型中传入了类型参数,如果使用其他类型编译器会提醒。
  2. 减少重复代码(代码复用)
    • 传统编程来说,我们定义方法,会将类型预先定义好,然后再进行调用,这样会造成如果再新增一个做一样事情的类,那么就需要再新增一个方法。
    public static void PrintIntValue(int number)
    {
        Console.WriteLine($"{number.GetType()}_Value:{number}");
    }
    public static void PrintStringValue(string str)
    {
        Console.WriteLine($"{str.GetType()}_Value:{str}");
    }
    
    • 如果使用泛型会怎么样?
    //在这里使用类型参数T,可以让调用者调用的时候指定类型。
    public static void PrintValue<T>(T paramete)
    {
        Console.WriteLine($"{paramete.GetType()}_Value:{paramete}");
    }
    
    可以看出它确实减少了重复代码!
  3. 性能(减少装箱和拆箱)
    • 还是上面的例子,C# 中所有类型的基类是Object,那么如果我们使用Object类来代替泛型会产生什么问题呢?那就是装箱和拆箱!
      • 装箱:是指将值类型的数据隐式转换成对象类型
      • 拆箱:是指将一个对象类型的数据显式转换成一个值类型数据

image.png

`通过执行结果可得知,装箱、拆箱确实是非常损耗性能的,在平时开发中也要少踩坑!`

泛型方法

定义泛型方法,是指在方法的后面使用<>,T是尚未指定的类型参数

public static void GetT<T>() 
{
    Console.WriteLine(typeof(T));
}

泛型约束

泛型定义中的 where 子句指定对用作泛型类型、方法、委托或本地函数中类型参数的参数类型的约束

public class Get<T> where T : struct{ } // 类型参数T必须为值类型
public class Get<T> where T : class{ } // 类型参数T必须为引用类型
public class Get<T> where T : ITest{ } // 类型参数T必须实现ITest接口
public class Get<T> where T : Test{ } // 类型参数T必须派生自基类Test
........

泛型约束使用 new 关键字指定泛型类或方法声明中的类型参数必须具有公共无参数构造函数。使用 new 约束,则该类型不能为抽象类型。

public static class Get<T> where T : new() { } //如果指定类型参数没有公共无参构造函数编译器会报错

以下表格引用自微软官网,只拉取了部分信息,如需阅读请移步官网泛型概述

约束描述
where T : struct类型参数必须是不可为 null 的值类型。 有关可为 null 的值类型的信息,请参阅可为 null 的值类型。 由于所有值类型都具有可访问的无参数构造函数,因此 struct 约束表示 new() 约束,并且不能与 new() 约束结合使用。 struct 约束也不能与 unmanaged 约束结合使用。
where T : class类型参数必须是引用类型。 此约束还应用于任何类、接口、委托或数组类型。 在 C#8.0 或更高版本中的可为 null 上下文中,T 必须是不可为 null 的引用类型。
where T : class?类型参数必须是可为 null 或不可为 null 的引用类型。 此约束还应用于任何类、接口、委托或数组类型。
where T : notnull类型参数必须是不可为 null 的类型。 参数可以是 C# 8.0 或更高版本中的不可为 null 的引用类型,也可以是不可为 null 的值类型。
where T : default重写方法或提供显式接口实现时,如果需要指定不受约束的类型参数,此约束可解决歧义。 default 约束表示基方法,但不包含 class 或 struct 约束。 有关详细信息,请参阅default约束规范建议。
where T : unmanaged类型参数必须是不可为 null 的非托管类型。 unmanaged 约束表示 struct 约束,且不能与 struct 约束或 new() 约束结合使用。
where T : new()类型参数必须具有公共无参数构造函数。 与其他约束一起使用时,new() 约束必须最后指定。 new() 约束不能与 struct 和 unmanaged 约束结合使用。

泛型接口的协变和抗变

在.NET中参数类型是抗变的,返回类型是协变的

//在泛型类型使用out关键字标注,泛型就是协变的。
interface Animal<out T>
{
    T GetS();
}
//使用in关键字标注,泛型就是抗变的
interface Plant<in T>
{
    void GetS(T t);
}

可以理解为,协变就是父类对象用子类可以替换,子类可以当父类使用,抗变就是子类对象用父类替换。

总结

泛型类和泛型方法兼具可重用性、类型安全性和效率,这是非泛型类和非泛型方法无法实现的。通过使用泛型类型参数 T,可以编写其他客户端代码能够使用的单个类,而不会产生运行时转换或装箱操作的成本或风险。当然本文中也有一些东西没有完全讲到如泛型结构、泛型委托等等,感兴趣的朋友可以阅读相关官网文章更深一步探究。