.NET 中的泛型

借助泛型,你可以根据要处理的精确数据类型定制方法、类、结构或接口。 例如,不使用允许键和值为任意类型的 Hashtable 类,而使用 Dictionary<TKey,TValue> 泛型类并指定允许的密钥和值类型。 泛型的优点包括:代码的可重用性增加,类型安全性提高。

定义和使用泛型

泛型是为所存储或使用的一个或多个类型具有占位符(类型形参)的类、结构、接口和方法。 泛型集合类可以将类型形参用作其存储的对象类型的占位符;类型形参呈现为其字段的类型和其方法的参数类型。 泛型方法可将其类型形参用作其返回值的类型或用作其形参之一的类型。 以下代码举例说明了一个简单的泛型类定义。

generic<typename T>
public ref class Generics
{
public:
    T Field;
};
public class Generic<T>
{
    public T Field;
}
Public Class Generic(Of T)
    Public Field As T

End Class

创建泛型类的实例时,指定用于替代类型形参的实际类型。 在类型形参出现的每一处位置用选定的类型进行替代,这会建立一个被称为构造泛型类的新泛型类。 你将得到根据你选择的类型而定制的类型安全类,如以下代码所示。

static void Main()
{
    Generics<String^>^ g = gcnew Generics<String^>();
    g->Field = "A string";
    //...
    Console::WriteLine("Generics.Field           = \"{0}\"", g->Field);
    Console::WriteLine("Generics.Field.GetType() = {0}", g->Field->GetType()->FullName);
}
public static void Main()
{
    Generic<string> g = new Generic<string>();
    g.Field = "A string";
    //...
    Console.WriteLine("Generic.Field           = \"{0}\"", g.Field);
    Console.WriteLine("Generic.Field.GetType() = {0}", g.Field.GetType().FullName);
}
Public Shared Sub Main()
    Dim g As New Generic(Of String)
    g.Field = "A string"
    '...
    Console.WriteLine("Generic.Field           = ""{0}""", g.Field)
    Console.WriteLine("Generic.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> 泛型接口的类型,以确保可对该类型的实例进行排序。 此外,你还可以将类型形参限制为具有特定基类、具有无参数构造函数或作为引用类型或值类型的类型。 泛型类型的用户不能替换不满足约束条件的类型实参。

  • 泛型方法定义 是具有两个形参列表的方法:泛型类型形参列表和形参列表。 类型形参可作为返回类型或形参类型出现,如以下代码所示。

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

泛型方法可出现在泛型或非泛型类型中。 值得注意的是,方法不会仅因为它属于泛型类型或甚至因为它有类型为封闭类型泛型参数的形参而成为泛型方法。 只有当方法有属于自己的类型形参列表时才是泛型方法。 在以下代码中,只有方法 G 是泛型方法。

ref class A
{
    generic<typename T>
    T G(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
};
generic<typename T>
ref class Generic
{
    T M(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
};
class A
{
    T G<T>(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
}
class Generic<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 Generic(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 = gcnew LinkedList<String^>();
    
    LinkedList<string> llist = new LinkedList<string>();
    
    Dim llist As New LinkedList(Of String)()
    
  • 性能更好。 泛型集合类型通常能更好地存储和操作值类型,因为无需对值类型进行装箱。

  • 泛型委托可以在无需创建多个委托类的情况下进行类型安全的回调。 例如, Predicate<T> 泛型委托允许你创建一种为特定类型实现你自己的搜索标准的方法并将你的方法与 Array 类型比如 FindFindLastFindAll方法一起使用。

  • 泛型简化动态生成的代码。 使用具有动态生成的代码的泛型时,无需生成类型。 这会增加方案数量,在这些方案中你可以使用轻量动态方法而非生成整个程序集。 有关详细信息,请参阅如何:定义和执行动态方法DynamicMethod

以下是泛型的一些局限:

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

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

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

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

    注意

    通过在动态程序集中触发代码或通过使用 Ilasm.exe (IL 汇编程序) 定义的嵌套类型不需要包括其封闭类型的类型参数;然而,如果不包括,类型参数就不会在嵌套类的范围内。

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

类库和语言支持

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

System 命名空间提供实现排序和等同性比较的泛型接口,还提供事件处理程序、转换和搜索谓词的泛型委托类型。

已将对泛型的支持添加到: System.Reflection 命名空间(以检查泛型类型和泛型方法)、 System.Reflection.Emit (以发出包含泛型类型和方法的动态程序集)和 System.CodeDom (以生成包括泛型的源图)。

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

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

嵌套类型和泛型

嵌套在泛型类型中的类型可取决于封闭泛型类型的类型参数。 公共语言运行时将嵌套类型看作泛型,即使它们不具有自己的泛型类型形参。 创建嵌套类型的实例时,必须指定所有封闭泛型类型的类型实参。

Title 描述
.NET 中的泛型集合 介绍了 .NET 中的泛型集合类和其他泛型类型。
用于控制数组和列表的泛型委托 描述用于转换、搜索谓词以及要对数组或集合中的元素执行的操作的泛型委托。
泛型接口 描述跨泛型类型系列提供通用功能的泛型接口。
协变和逆变 描述泛型类型实参中的协变和逆变。
常用的集合类型 总结了 .NET 中集合类型(包括泛型类型)的特征和使用方案。
何时使用泛型集合 描述用于确定何时使用泛型集合类型的一般规则。
如何:使用反射发出定义泛型类型 解释如何生成包括泛型类型和方法的动态程序集。
Generic Types in Visual Basic 为 Visual Basic 用户描述泛型功能,包括有关使用和定义泛型类型的帮助主题。
泛型介绍 为 C# 用户概述定义和使用泛型类型。
Visual C++ 中的泛型概述 为 C++ 用户描述泛型功能,包括泛型和模板之间的差异。

参考

System.Collections.Generic

System.Collections.ObjectModel

System.Reflection.Emit.OpCodes