C# 编程指南:泛型(Generics)

在 C# 中,泛型(Generics)是一种强大的特性,它允许我们编写可重用的代码,不仅能够在编译时类型安全地处理不同类型的数据,还可以提高代码的性能和可读性。泛型在集合类、算法和数据结构等方面得到广泛应用。 C# 中的泛型通过在定义类、接口、方法等时使用类型参数来实现。类型参数可以是任何有效的类型,包括类、结构、接口、委托和基元类型。使用泛型的主要目的是在编写代码时不指定具体的类型,而是在使用时由调用方指定类型。

部分
1
一个简单的例子
public class GenericClass<T>
{
    private T genericField;

    public GenericClass(T value)
    {
        genericField = value;
    }

    public T GenericMethod(T parameter)
    {
        Console.WriteLine($"Generic field: {genericField}, Parameter: {parameter}");
        return parameter;
    }
}

// 使用泛型类
GenericClass<int> intGenericClass = new GenericClass<int>(10);
int result = intGenericClass.GenericMethod(5); // 输出: Generic field: 10, Parameter: 5

GenericClass<string> stringGenericClass = new GenericClass<string>("Hello");
string result = stringGenericClass.GenericMethod("World"); // 输出: Generic field: Hello, Parameter: World

在上述示例中,GenericClass<T> 是一个泛型类,它接受一个类型参数 T。通过在实例化时指定类型参数,我们可以创建具有不同类型的对象,并在方法中使用这些类型。

部分
2
优点
类型安全
泛型提供了类型安全的编程方式。通过在编译时进行类型检查,可以在编译阶段捕获许多类型相关的错误,而不是在运行时发生异常。这减少了潜在的运行时错误和异常。
代码重用和灵活性
泛型允许在不同的数据类型上重用相同的代码。通过编写通用的算法、数据结构和组件,可以减少代码的重复编写,并提高代码的可维护性和可扩展性。泛型还提供了更灵活的方式来处理不同类型的数据,而无需为每种类型编写专门的代码。
性能优化
泛型避免了类型转换和装箱拆箱的开销,提高了代码的性能。使用泛型集合类和泛型算法可以在不牺牲性能的情况下,处理大量的数据和复杂的操作。
可读性和可维护性
泛型使代码更易于阅读和理解。通过使用泛型类型参数和自描述的代码,可以清晰地表达代码的意图。泛型还提供了更好的可维护性,因为在修改泛型代码时,只需要更改一处代码即可影响多个类型。
部分
3
缺点
增加代码复杂性
使用泛型会增加代码的复杂性,特别是对于初学者来说。泛型代码可能需要更多的类型参数和类型约束,这可能导致代码变得复杂难以理解。
编译时间增加
在编译时,编译器需要生成和检查每个使用不同类型参数的泛型代码的具体实现。这可能导致编译时间增加,特别是当泛型代码被广泛使用时。
不适合所有情况
虽然泛型提供了许多优点,但并不是所有情况下都适合使用泛型。在某些特定的场景中,使用非泛型的代码可能更简单、更直观,并且足够满足需求。
部分
4
泛型约束(Generic Constraints)

泛型约束(Generic Constraints)允许开发人员对泛型类型参数进行限制,以确保类型参数满足特定的条件。这些约束可以应用于泛型类、泛型方法和泛型委托,以提供更多的类型安全性和灵活性。

1
where T : class

该约束要求类型参数 T 必须是引用类型(类、接口、委托、数组或委托)。这意味着泛型类型参数不能是值类型(如 int、double 等)。

public class MyClass<T> where T : class
{
  // 泛型类定义
}
2
where T : struct

该约束要求类型参数 T 必须是值类型(如 int、double、struct 等)。这意味着泛型类型参数不能是引用类型。

3
where T : new()

该约束要求类型参数 T 必须具有无参数的公共构造函数。这允许在泛型代码中创建 T 类型的新实例。

public class MyClass<T> where T : new()
{
  // 泛型类定义
}
4
where T : <base class name>

该约束要求类型参数 T 必须是指定的基类或其派生类。

public class MyBaseClass
{
  // 基类定义
}

public class MyDerivedClass<T> where T : MyBaseClass
{
  // 泛型类定义
}
5
where T : <interface name>

该约束要求类型参数 T 必须实现指定的接口。

public interface IMyInterface
{
  // 接口定义
}

public class MyClass<T> where T : IMyInterface
{
  // 泛型类定义
}
6
where T : U

该约束要求类型参数 T 必须是类型参数 U 或其派生类。这允许在泛型代码中对类型参数进行比较和约束。

public class MyClass<T, U> where T : U
{
  // 泛型类定义
}
7
多个约束

可以同时使用多个约束来限制类型参数的行为。多个约束之间使用逗号分隔。

public class MyClass<T> where T : class, IMyInterface, new()
{
  // 泛型类定义
}
部分
5
协变性(Covariance)和逆变性(Contravariance)

协变性(Covariance)和逆变性(Contravariance)是 C# 中泛型类型参数的特性,允许在一定条件下进行类型的隐式转换。 需要注意的是,协变性和逆变性只适用于引用类型,不适用于值类型(如基本数据类型)。此外,协变性和逆变性只在接口和委托中有效,不适用于类或结构。 协变性和逆变性的引入使得泛型类型参数更加灵活,可以在一定条件下进行类型的隐式转换,提供了更好的代码重用性和互操作性。然而,为了确保类型安全,协变性和逆变性受到一些限制,如只允许在特定的接口和委托中使用,并且只能在满足一定条件的情况下使用。

1
协变性(Covariance)

协变性允许将派生类型(子类型)作为基类型(父类型)使用。换句话说,协变性允许将泛型类型参数替换为其派生类型。在协变性中,可以将返回类型或只读属性的类型声明为派生类型。

interface IAnimal { }
class Cat : IAnimal { }

IEnumerable<Cat> cats = new List<Cat>();
IEnumerable<IAnimal> animals = cats; // 协变性,将派生类型 Cat 赋值给基类型 IAnimal

在上述示例中,IEnumerable<T> 是一个泛型接口,如果类型参数 T 是协变的,那么可以将 IEnumerable<Cat> 赋值给 IEnumerable<IAnimal>,因为 Cat 是 IAnimal 的派生类型。

2
逆变性(Contravariance)

逆变性允许将基类型作为派生类型使用。换句话说,逆变性允许将泛型类型参数替换为其基类型。逆变性通常用于接受参数的方法或可写属性。

interface IComparer<in T>
{
    int Compare(T x, T y);
}

IComparer<IAnimal> animalComparer = new AnimalComparer();
IComparer<Cat> catComparer = animalComparer; // 逆变性,将基类型 IAnimal 赋值给派生类型 Cat

在上述示例中,IComparer<T> 是一个泛型接口,如果类型参数 T 是逆变的,那么可以将 IComparer<IAnimal> 赋值给 IComparer<Cat>,因为 IComparer<IAnimal> 可以比较 Cat 类型的对象。

部分
6
泛型特性(Generic Attributes)

C# 11引入了泛型特性(Generic Attributes)的概念,允许在编写自定义特性时使用泛型类型参数。泛型特性可以在运行时获取和使用类型参数的信息。

在这个功能提供之前,如果需要在特性(Attribute)中获得类型(Type)信息,需要实现类似下方代码:

// Before C# 11:
public class TypeAttribute : Attribute
{
   public TypeAttribute(Type t) => ParamType = t;

   public Type ParamType { get; }
}

并且为了应用该属性,需要使用 typeof 运算符:

[TypeAttribute(typeof(string))]
public string Method() => default;

使用此新功能,可以改为创建泛型属性:

// C# 11 feature:
public class GenericAttribute<T> : Attribute { }

然后指定类型参数以使用该属性:

[GenericAttribute<string>()]
public string Method() => default;

应用属性时,必须提供所有类型参数。 换句话说,泛型类型必须完全构造。

public class GenericType<T>
{
   [GenericAttribute<T>()] // Not allowed! generic attributes must be fully constructed types.
   public string Method() => default;
}
    目录

  • 1.
    一个简单的例子
  • 2.
    优点
  • 3.
    缺点
  • 4.
    泛型约束(Generic Constraints)
  • 5.
    协变性(Covariance)和逆变性(Contravariance)
  • 6.
    泛型特性(Generic Attributes)