Skip to content

泛型

泛型(Generics)是一种强大的特性,允许你定义类、接口、委托和方法时使用类型参数,从而提高代码的重用性、类型安全性和性能(避免装箱和取消装箱)。

泛型使得你可以创建具有类型安全的代码而无需在运行时进行类型检查或转换。

泛型类

定义泛型类时,你可以在类名后使用尖括号 <T> 来指定类型参数:

C#
public class Box<T>
{
    private T _content;

    public void SetContent(T content)
    {
        _content = content;
    }

    public T GetContent()
    {
        return _content;
    }
}

//使用
Box<int> intBox = new Box<int>();
intBox.SetContent(123);
int content = intBox.GetContent();

Box<string> stringBox = new Box<string>();
stringBox.SetContent("Hello");
string content1 = stringBox.GetContent();

泛型方法

泛型方法在方法定义时使用类型参数,调用时可以指定具体的类型:

C#
public class Utility
{
    public T[] CreateArray<T>(int length)
    {
        return new T[length];
    }
}

//使用
Utility utility = new Utility();
int[] intArray = utility.CreateArray<int>(10);
string[] stringArray = utility.CreateArray<string>(5);

泛型接口

泛型接口定义时使用类型参数,具体的实现类可以指定具体的类型:

C#
public interface IComparer<T>
{
    int Compare(T x, T y);
}

//实现
public class IntComparer : IComparer<int>
{
    public int Compare(int x, int y)
    {
        return x.CompareTo(y);
    }
}

public class StringComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        return x.CompareTo(y);
    }
}

//使用
IComparer<int> comparer = new IntComparer();
comparer.Compare(1, 2)

IComparer<string> comparer1 = new StringComparer();
comparer1.Compare("1", "2");

泛型约束

泛型约束允许你限制泛型类型参数的类型范围,使得你可以在泛型类或方法中使用特定的类型功能。

常见的约束有: where T : class: 限制泛型类型参数必须是引用类型。

where T : struct: 限制泛型类型参数必须是值类型(非 Nullable)。

where T : new(): 限制泛型类型参数必须有一个无参数构造函数。

where T : BaseClass: 限制泛型类型参数必须是指定基类的子类。

where T : Interface: 限制泛型类型参数必须实现指定接口。

C#
public class Repository<T> where T : class, new()
{
    public T CreateInstance()
    {
        return new T();
    }
}

泛型集合

C# 提供了一组泛型集合类,定义在 System.Collections.Generic 命名空间中,如 List<T>, Dictionary<TKey, TValue>, Queue<T>, Stack<T> 等。

常见的约束有: where T : class: 限制泛型类型参数必须是引用类型。

where T : struct: 限制泛型类型参数必须是值类型(非 Nullable)。

where T : new(): 限制泛型类型参数必须有一个无参数构造函数。

where T : BaseClass: 限制泛型类型参数必须是指定基类的子类。

where T : Interface: 限制泛型类型参数必须实现指定接口。

C#
List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);

foreach (int number in numbers)
{
    Console.WriteLine(number);
}

详情参考集合

泛型委托

泛型委托允许你定义接受泛型参数的方法签名:

C#
public delegate T Transformer<T>(T input);

//使用
Transformer<int> square = x => x * x;
int result = square(5);
Console.WriteLine(result); // 输出 25

详情参考委托与事件

泛型的继承

泛型类/接口可以继承自其他泛型类,也可以作为基类被其他泛型类/接口继承。

在继承泛型类时,子类也需要指定类型参数,或者可以提供自己的类型参数。

类在实现泛型接口时需要指定具体的类型参数。

泛型类继承

C#
// 基类定义
public class BaseClass<T>
{
    public T Value { get; set; }
}

// 继承泛型类
public class DerivedClass<T> : BaseClass<T>
{
    public void PrintValue()
    {
        Console.WriteLine(Value);
    }
}

public class DerivedClass1 : BaseClass<int>
{
    public void PrintValue()
    {
        Console.WriteLine(Value);
    }
}

泛型接口继承

C#
// 基接口定义
public interface IProcessor<T>
{
    void Process(T item);
}

// 继承泛型接口
public interface IAdvancedProcessor<T> : IProcessor<T>
{
    void AdvancedProcess(T item);
}

// 实现泛型接口(需要指定具体的类型参数,泛型类实现除外)
public class Processor : IAdvancedProcessor<int>
{
    public void Process(int item)
    {
        Console.WriteLine("Processing: " + item);
    }

    public void AdvancedProcess(int item)
    {
        Console.WriteLine("Advanced Processing: " + item);
    }
}

泛型约束与继承

泛型约束允许你限制泛型类型参数的范围。这些约束可以与继承一起使用,以确保类型参数满足某些条件。

C#
// 定义一个基类
public class Animal
{
    public void Eat() { }
}

// 定义一个接口
public interface IReadable
{
    void Read();
}

// 泛型类的定义
public class Repository<T> where T : Animal, IReadable, new()
{
    public T CreateInstance()
    {
        return new T(); // T 必须是 Animal 的子类,实现 IReadable 接口,并具有无参数构造函数
    }
}

// 定义一个实现了约束的类
public class Book : Animal, IReadable
{
    public void Read()
    {
        Console.WriteLine("Reading");
    }
}

泛型类和接口可以组合使用,实现更复杂的设计

C#
// 泛型接口
public interface IRepository<T>
{
    void Add(T item);
    T Get(int id);
}

// 泛型类实现泛型接口
public class Repository<T> : IRepository<T>
{
    public void Add(T item)
    {
        // 实现添加逻辑
    }

    public T Get(int id)
    {
        // 实现获取逻辑
        return default;
    }
}

// 使用泛型类
IRepository<string> repo = new Repository<string>();
repo.Add("Item1");
string item = repo.Get(1);

协变与逆变

在C#中,协变和逆变主要用于泛型类型的转换,它们允许我们在特定条件下将一个类型转换为另一个类型。

具体来说,协变和逆变可以在接口和委托中使用,帮助在继承关系中处理泛型类型。

协变(Covariance)

协变允许你使用派生类型代替基类型。它主要用于返回值类型的转换。

换句话说,如果一个泛型接口或委托是协变的,你可以将泛型参数指定为某个基类,然后用派生类的实例来代替。

关键字:

out:用于接口或委托的泛型参数,表示协变。

使用场景:

当你处理返回类型时,协变允许使用派生类型替代基类型。

示例:

C#
public interface ICovariant<out T>
{
    T Get();
}

public class Animal { }
public class Dog : Animal { }

public class DogProvider : ICovariant<Dog>
{
    public Dog Get() => new Dog();
}

public class Program
{
    public static void Main(string[] args)
    {
        ICovariant<Animal> animalProvider = new DogProvider(); // 协变,派生类转换为基类
        Animal animal = animalProvider.Get();
    }
}

逆变(Contravariance)

逆变允许你使用基类型代替派生类型。它主要用于输入参数类型的转换。

换句话说,如果一个泛型接口或委托是逆变的,你可以将泛型参数指定为某个派生类,然后用基类的实例来代替。

关键字:

in:用于接口或委托的泛型参数,表示逆变。

使用场景:

当你处理输入参数时,逆变允许使用基类型替代派生类型。

C#
public interface IContravariant<in T>
{
    void Set(T item);
}

public class Animal { }
public class Dog : Animal { }

public class AnimalHandler : IContravariant<Animal>
{
    public void Set(Animal item) { }
}

public class Program
{
    public static void Main(string[] args)
    {
        IContravariant<Dog> dogHandler = new AnimalHandler(); // 逆变,基类转换为派生类
        dogHandler.Set(new Dog());
    }
}

Document Base on VitePress.