.NET 中的泛型

泛型使你可以根据它所针对的精确数据类型定制方法、类、结构或接口。 例如,可以使用Hashtable泛型类来指定键和值的允许类型,而不是使用Dictionary<TKey,TValue>类,该类允许键和值属于任何类型。 泛型的优点之一是提高代码可重用性和类型安全性。

定义和使用泛型

泛型是具有占位符(类型参数)的类、结构、接口和方法,用于存储或使用的一个或多个类型。 泛型集合类可能使用类型参数作为它存储的对象类型的占位符。 类型参数显示为其字段的类型及其方法的参数类型。 泛型方法可能将其类型参数用作其返回值的类型或其正式参数之一的类型。

以下代码演示了一个简单的泛型类定义。

public class SimpleGenericClass<T>
{
    public T Field;
}
Public Class SimpleGenericClass(Of T)
    Public Field As T

End Class

创建泛型类的实例时,可以指定要替换类型参数的实际类型。 这将建立一个新的泛型类,即被称为构造的泛型类,在类型参数出现的任何位置都用您选择的类型进行替换。 结果是一个类型安全类,根据所选类型定制,如以下代码所示。

public static void Main()
{
    SimpleGenericClass<string> g = new SimpleGenericClass<string>();
    g.Field = "A string";
    //...
    Console.WriteLine($"SimpleGenericClass.Field           = \"{g.Field}\"");
    Console.WriteLine($"SimpleGenericClass.Field.GetType() = {g.Field.GetType().FullName}");
}
Public Shared Sub Main()
    Dim g As New SimpleGenericClass(Of String)
    g.Field = "A string"
    '...
    Console.WriteLine("SimpleGenericClass.Field           = ""{0}""", g.Field)
    Console.WriteLine("SimpleGenericClass.Field.GetType() = {0}", g.Field.GetType().FullName)
End Sub

术语

以下术语用于讨论 .NET 中的泛型:

  • 泛型类型定义是一个类、结构或接口声明,该声明充当模板,其可包含或使用的类型占位符。 例如,类 System.Collections.Generic.Dictionary<TKey,TValue> 可以包含两种类型:键和值。 由于泛型类型定义只是模板,因此无法创建作为泛型类型定义的类、结构或接口的实例。

  • 泛型类型参数类型参数是泛型类型或方法定义的占位符。 System.Collections.Generic.Dictionary<TKey,TValue>泛型类型具有两个类型参数:TKeyTValue,它们表示其键和值的类型。

  • 构造的泛型类型构造类型是针对泛型类型定义的泛型类型参数指定类型后的结果。

  • 泛型类型参数是替代泛型类型参数的任何类型。

  • 常规术语 泛型类型 包括构造类型和泛型类型定义。

  • 通过使用泛型类型参数的协变逆变,您可以使用构造类型参数比目标构造类型更派生(协变)或更少派生(逆变)的泛型类型。 协变和逆变统称为 方差。 有关详细信息,请参阅 协变和逆变

  • 约束 是对泛型类型参数施加的限制。 例如,可以将类型参数限制为实现 System.Collections.Generic.IComparer<T> 泛型接口的类型,以确保可以对类型的实例进行排序。 还可以将类型参数限制为具有特定基类、具有无参数构造函数的类型或引用类型或值类型。 泛型类型的用户不能替换不满足约束的类型参数。

  • 泛型方法定义是包含两个参数列表的方法:泛型类型参数列表和正式参数列表。 类型参数可以显示为返回类型或形式参数的类型,如以下代码所示。

    T MyGenericMethod<T>(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
    
    Function MyGenericMethod(Of T)(ByVal arg As T) As T
        Dim temp As T = arg
        '...
        Return temp
    End Function
    

    泛型方法可以出现在泛型或非泛型类型上。 请务必注意,方法不是泛型方法,只是因为它属于泛型类型,甚至因为它具有其类型是封闭类型的泛型参数的正式参数。 仅当方法具有自己的类型参数列表时,该方法才为泛型方法。 在以下代码中,只有方法 G 是泛型方法。

    class A
    {
        T G<T>(T arg)
        {
            T temp = arg;
            //...
            return temp;
        }
    }
    class MyGenericClass<T>
    {
        T M(T arg)
        {
            T temp = arg;
            //...
            return temp;
        }
    }
    
    Class A
        Function G(Of T)(ByVal arg As T) As T
            Dim temp As T = arg
            '...
            Return temp
        End Function
    End Class
    Class MyGenericClass(Of T)
        Function M(ByVal arg As T) As T
            Dim temp As T = arg
            '...
            Return temp
        End Function
    End Class
    

泛型的优点和缺点

使用泛型集合和委托有很多优点:

  • 类型安全性。 泛型将类型安全性的负担从你转移到编译器。 无需编写代码来测试正确的数据类型,因为它是在编译时强制执行的。 对类型转换的需求和运行时错误的可能性会降低。

  • 更少的代码且代码更易复用。 无需从基类型继承并重写成员。 例如,LinkedList<T> 已准备好立即使用。 例如,可以使用以下变量声明创建字符串的链接列表:

    LinkedList<string> llist = new LinkedList<string>();
    
    Dim llist As New LinkedList(Of String)()
    
  • 性能更好。 泛型集合类型通常在存储和操作值类型的数据时表现更优,因为无须对值类型进行装箱处理。

  • 泛型委托启用类型安全回调,而无需创建多个委托类。 例如, Predicate<T> 泛型委托允许你创建一个方法,该方法实现特定类型的搜索条件,并将方法与类型的方法 Array 一起使用,例如 FindFindLastFindAll

  • 泛型简化了动态生成的代码。 将泛型与动态生成的代码一起使用时,无需生成类型。 这会增加使用轻型动态方法(而不是生成整个程序集)的方案数。 有关详细信息,请参阅 How to: Define and Execute Dynamic Methods and DynamicMethod.

以下是泛型的一些限制:

  • 泛型类型可以派生自大多数基类,例如 MarshalByRefObject (和约束可用于要求泛型类型参数派生自基类,例如 MarshalByRefObject)。 但是,.NET 不支持上下文绑定泛型类型。 泛型类型可以派生自 ContextBoundObject,但尝试创建该类型的实例会导致一个 TypeLoadException

  • 枚举不能具有泛型类型参数。 枚举只能偶然成为泛型(例如,因为它嵌套在使用 Visual Basic、C# 或 C++ 定义的泛型类型中)。 有关详细信息,请参阅 通用类型系统中的“枚举”。

  • 轻型动态方法不能是泛型方法。

  • 在 Visual Basic、C# 和 C++中,除非已将类型分配给所有封闭类型的类型参数,否则无法实例化包含在泛型类型中的嵌套类型。 另一种说法是,在反射中,使用这些语言定义的嵌套类型包括其所有封闭类型的类型参数。 这样就可以在嵌套类型的成员定义中使用封闭类型的类型参数。 有关详细信息,请参阅MakeGenericType中的“嵌套类型”。

    注释

    在动态程序集中生成代码或使用 Ilasm.exe (IL Assembler) 定义的嵌套类型不需要包含其封闭类型的类型参数;但是,如果不包含它们,则这些类型参数不在嵌套类的作用域中。

    有关详细信息,请参阅MakeGenericType中的“嵌套类型”。

类库和语言支持

.NET 在以下命名空间中提供了许多泛型集合类:

命名空间中 System 提供了用于实现排序和相等比较的泛型接口,以及事件处理程序、转换和搜索谓词的泛型委托类型。

命名空间 System.Numerics 为数学功能提供通用接口(在 .NET 7 及更高版本中可用)。 有关详细信息,请参阅 泛型数学

对泛型的支持已添加到 System.Reflection 命名空间中,用于检查泛型类型和泛型方法,用于 System.Reflection.Emit 发出包含泛型类型和方法的动态程序集,以及 System.CodeDom 用于生成包含泛型的源图。

公共语言运行时提供新的操作码和前缀,以支持公共中间语言(CIL)中的泛型类型,包括StelemLdelemUnbox_AnyConstrainedReadonly

Visual C++、C# 和 Visual Basic 都完全支持定义和使用泛型。 有关语言支持的详细信息,请参阅 Visual Basic 中的泛型类型泛型简介Visual C++中的泛型概述

嵌套类型和泛型

嵌套在泛型类型中的类型可以取决于封闭泛型类型的类型参数。 公共语言运行时将嵌套类型视为泛型类型,即使它们本身没有泛型类型参数也是如此。 创建嵌套类型的实例时,必须为所有封闭泛型类型指定类型参数。

标题 DESCRIPTION
.NET 中的泛型集合 介绍 .NET 中的泛型集合类和其他泛型类型。
用于处理数组和列表的泛型委托 描述用于数组或集合中元素的转换、搜索谓词以及采取操作的泛型委托。
泛型数学 描述如何一般地执行数学运算。
泛型接口 介绍跨泛型类型系列提供通用功能的泛型接口。
协变和逆变 描述泛型类型参数中的协变和逆变。
常用集合类型 提供有关 .NET 中集合类型的特征和使用方案(包括泛型类型)的摘要信息。
何时使用泛型集合 描述确定何时使用泛型集合类型的常规规则。
如何:使用反射发出定义泛型类型 介绍如何生成包含泛型类型和方法的动态程序集。
Visual Basic 中的泛型类型 介绍适用于 Visual Basic 用户的泛型功能,包括有关使用和定义泛型类型的操作指南主题。
泛型简介 概述如何为 C# 用户定义和使用泛型类型。
Visual C++ 中的泛型概述 介绍C++用户的泛型功能,包括泛型和模板之间的差异。

参考文献