Skip to content

01-泛型(Generics)

泛型允许你定义类型安全的类、接口和方法,而将具体类型延迟到使用时再指定。泛型提高了代码的复用性类型安全性性能


一、为什么需要泛型

非泛型的问题

csharp
// 问题1:需要为每种类型编写重复代码
int CompareInt(int a, int b) { return a.CompareTo(b); }
double CompareDouble(double a, double b) { return a.CompareTo(b); }
string CompareString(string a, string b) { return a.CompareTo(b); }

// 问题2:使用 object 类型有装箱拆箱和类型安全问题
ArrayList list = new ArrayList();
list.Add(123);       // 装箱:int → object
list.Add("string");  // 无类型安全(可以混入不同类型)
int val = (int)list[0]; // 拆箱:object → int,需要强制转换

// 泛型解决方案:一个方法适用所有类型
T Max<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) > 0 ? a : b;
}

泛型 vs 非泛型对比

对比项非泛型(object)泛型
类型安全无(可混入不同类型)有(编译时检查)
值类型性能装箱拆箱,性能差直接操作,无额外开销
代码复用需为每种类型写重复代码一份代码适用所有类型
可读性需强制转换,代码冗余无需转换,代码清晰
IDE 支持无智能提示有完整智能提示

二、泛型方法

基本语法

csharp
// 定义泛型方法
public static T Max<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) > 0 ? a : b;
}

// 使用——编译器自动推断类型
int intMax = Max(10, 20);                    // T = int
double dblMax = Max(3.14, 2.71);             // T = double
string strMax = Max("apple", "banana");      // T = string

泛型方法交换值

csharp
public static void Swap<T>(ref T a, ref T b)
{
    T temp = a;
    a = b;
    b = temp;
}

int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"{x}, {y}");  // 输出:2, 1

string s1 = "Hello", s2 = "World";
Swap(ref s1, ref s2);
Console.WriteLine($"{s1}, {s2}");  // 输出:World, Hello

类型推断规则

csharp
public static void Print<T>(T item)
{
    Console.WriteLine(item);
}

Print(123);                // T = int(自动推断)
Print("Hello");            // T = string
Print(new List<int>());    // T = List<int>

// 无法推断时需显式指定
// Print(null);           // ❌ 编译错误:无法推断类型
Print<string>(null);       // ✅ 显式指定

三、泛型类

csharp
// 定义泛型类
public class Box<T>
{
    private T content;

    public void Put(T item)
    {
        content = item;
    }

    public T Get()
    {
        return content;
    }
}

// 使用
Box<int> intBox = new Box<int>();
intBox.Put(42);
int value = intBox.Get();  // 类型安全,无需转换

Box<string> strBox = new Box<string>();
strBox.Put("Hello");
string text = strBox.Get();

多个类型参数

csharp
public class Pair<TFirst, TSecond>
{
    public TFirst First { get; set; }
    public TSecond Second { get; set; }

    public Pair(TFirst first, TSecond second)
    {
        First = first;
        Second = second;
    }

    public void Display()
    {
        Console.WriteLine($"First: {First}, Second: {Second}");
    }
}

var pair = new Pair<int, string>(1, "一");
pair.Display();  // 输出:First: 1, Second: 一

// 类型参数可以是任何类型
var pair2 = new Pair<string, DateTime>("现在", DateTime.Now);

泛型类的静态成员

csharp
public class GenericCounter<T>
{
    public static int Count = 0;

    public GenericCounter()
    {
        Count++;
    }
}

var intCounter1 = new GenericCounter<int>();
var intCounter2 = new GenericCounter<int>();
var strCounter1 = new GenericCounter<string>();

Console.WriteLine(GenericCounter<int>.Count);    // 2(int 类型的独立计数)
Console.WriteLine(GenericCounter<string>.Count); // 1(string 类型的独立计数)
// ⚠️ 每个封闭类型(int、string)有各自的静态成员

default 关键字

csharp
public class Factory<T>
{
    private T[] items = new T[10];

    public T GetDefault()
    {
        return default(T);  // int→0, string→null, bool→false, 引用类型→null
    }

    public T CreateDefault()
    {
        return default;  // C# 7.1+ 可省略类型参数
    }
}

Console.WriteLine(default(int));      // 0
Console.WriteLine(default(string));   // null
Console.WriteLine(default(bool));     // False
Console.WriteLine(default(DateTime)); // 0001/1/1 0:00:00

四、泛型约束

泛型约束限制了类型参数必须具备的条件。没有约束时,泛型参数只能使用 object 的成员。

约束类型对照表

约束说明示例用途
where T : struct值类型约束数值计算、数学操作
where T : class引用类型约束数据库实体、业务对象
where T : notnull不可为 null 类型需要确保不为 null
where T : new()无参构造函数工厂模式、对象创建
where T : 基类名指定基类或其派生类动物继承体系
where T : 接口名实现指定接口比较、排序、释放
where T : U嵌套类型约束类型参数之间的关系
csharp
// 值类型约束——适合数值处理
public class NumberProcessor<T> where T : struct
{
    public T Value { get; set; }
    // 可以安全使用值类型的特性
}

// 引用类型 + 无参构造函数——工厂模式
public class Factory<T> where T : class, new()
{
    public T Create()
    {
        return new T();  // 可以安全创建实例
    }
}

// 接口约束——保证类型具有特定方法
public class Sorter<T> where T : IComparable<T>
{
    public T[] Sort(T[] array)
    {
        Array.Sort(array);
        return array;
    }
}

// 多约束组合
public class MultiConstraint<T> where T : class, IDisposable, new()
{
    // T 必须是:引用类型 + 实现了 IDisposable + 有无参构造函数
}

// 枚举约束(C# 7.3+)
public class EnumParser<T> where T : struct, Enum
{
    public T Parse(string value)
    {
        return (T)Enum.Parse(typeof(T), value);
    }
}

// 委托约束(C# 7.3+)
public class EventWrapper<T> where T : Delegate
{
    // T 必须是委托类型
}

未约束泛型的限制

csharp
public class BadExample<T>
{
    public void Process(T item)
    {
        // ❌ 不能使用 ==(除非约束为 class)
        // if (item == null) ...

        // ❌ 不能使用 +、-、*、/
        // var result = item + item;

        // ❌ 不能调用特定类型的方法
        // item.ToString() 只能使用 object 的成员
    }
}

五、泛型接口

定义和实现

csharp
// 泛型接口
public interface IRepository<T>
{
    T GetById(int id);
    void Add(T item);
    void Delete(int id);
    IEnumerable<T> GetAll();
}

// 实现泛型接口(指定具体类型)
public class UserRepository : IRepository<User>
{
    private List<User> users = new List<User>();

    public User GetById(int id)
    {
        return users.Find(u => u.Id == id);
    }

    public void Add(User user)
    {
        users.Add(user);
    }

    public void Delete(int id)
    {
        users.RemoveAll(u => u.Id == id);
    }

    public IEnumerable<User> GetAll()
    {
        return users;
    }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

泛型接口与协变/逆变

csharp
// 协变接口(out):T 只能用于返回值
public interface IProducer<out T>
{
    T Produce();
    // void Consume(T item);  // ❌ 编译错误:协变参数不能用于输入
}

// 逆变接口(in):T 只能用于参数
public interface IConsumer<in T>
{
    void Consume(T item);
    // T Produce();  // ❌ 编译错误:逆变参数不能用于输出
}

// 使用
IProducer<Dog> dogProducer = ...;
IProducer<Animal> animalProducer = dogProducer;  // 协变:Dog → Animal

IConsumer<Animal> animalConsumer = ...;
IConsumer<Dog> dogConsumer = animalConsumer;     // 逆变:Animal → Dog

六、泛型委托

csharp
// 系统泛型委托

// Action:无返回值,0~16 个参数
Action greet = () => Console.WriteLine("Hello");
Action<string, int> showInfo = (name, age) =>
    Console.WriteLine($"{name} 今年 {age} 岁");

// Func:有返回值,最后一个是返回值类型
Func<int, int> square = x => x * x;
Func<int, int, int> add = (a, b) => a + b;
Func<string, int> getLength = s => s.Length;

// Predicate:返回 bool,用于条件判断
Predicate<int> isEven = n => n % 2 == 0;
Predicate<string> isNullOrEmpty = string.IsNullOrEmpty;

// Comparison:用于排序比较
Comparison<int> ascending = (a, b) => a.CompareTo(b);
Comparison<int> descending = (a, b) => b.CompareTo(a);

七、协变和逆变

协变(Covariance,out 关键字)

允许将派生类型作为基类型返回。

csharp
// IEnumerable<T> 是协变的
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings;  // ✅ 协变:string → object

// 数组也是协变的(但非类型安全)
string[] strArray = { "a", "b" };
object[] objArray = strArray;  // ✅
objArray[0] = 123;             // ❌ 运行时异常!ArrayTypeMismatchException

逆变(Contravariance,in 关键字)

允许将基类型作为派生类型传入。

csharp
// Action<T> 是逆变的
Action<object> objAction = (obj) => Console.WriteLine(obj);
Action<string> strAction = objAction;  // ✅ 逆变:object → string
strAction("Hello");

// IComparer<T> 也是逆变的
IComparer<object> objComparer = ...;
IComparer<string> strComparer = objComparer;  // ✅

协变/逆变对照表

特性协变(out)逆变(in)
关键字outin
方向T 只能输出(返回值)T 只能输入(参数)
类型转换子类 → 基类基类 → 子类
示例接口IEnumerable<out T>IComparer<in T>
示例委托Func<out T>Action<in T>

八、泛型集合性能对比

csharp
using System.Diagnostics;

// 性能对比:ArrayList vs List<T>
const int count = 10_000_000;

Stopwatch sw = Stopwatch.StartNew();
ArrayList arrayList = new ArrayList();
for (int i = 0; i < count; i++)
    arrayList.Add(i);          // 每次装箱
sw.Stop();
Console.WriteLine($"ArrayList 添加:{sw.ElapsedMilliseconds}ms");

sw.Restart();
List<int> list = new List<int>();
for (int i = 0; i < count; i++)
    list.Add(i);               // 无装箱
sw.Stop();
Console.WriteLine($"List<T> 添加:{sw.ElapsedMilliseconds}ms");

// 读取对比
sw.Restart();
for (int i = 0; i < count; i++)
{
    int val = (int)arrayList[i];  // 每次拆箱
}
sw.Stop();
Console.WriteLine($"ArrayList 读取:{sw.ElapsedMilliseconds}ms");

sw.Restart();
for (int i = 0; i < count; i++)
{
    int val = list[i];            // 无需拆箱
}
sw.Stop();
Console.WriteLine($"List<T> 读取:{sw.ElapsedMilliseconds}ms");

// 结果:List<T> 通常比 ArrayList 快 5-10 倍

九、综合案例

案例一:泛型仓库模式

csharp
public interface IEntity
{
    int Id { get; }
}

public class Product : IEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class Customer : IEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

// 泛型仓库
public class GenericRepository<T> where T : class, IEntity
{
    private readonly Dictionary<int, T> _items = new();

    public void Add(T item) => _items[item.Id] = item;
    public T GetById(int id) => _items.GetValueOrDefault(id);
    public IEnumerable<T> GetAll() => _items.Values;
    public bool Delete(int id) => _items.Remove(id);
    public int Count => _items.Count;
}

// 使用——不同类型复用同一套逻辑
var productRepo = new GenericRepository<Product>();
productRepo.Add(new Product { Id = 1, Name = "手机", Price = 2999 });
productRepo.Add(new Product { Id = 2, Name = "电脑", Price = 5999 });

var customerRepo = new GenericRepository<Customer>();
customerRepo.Add(new Customer { Id = 1, Name = "张三", Email = "zhang@test.com" });
customerRepo.Add(new Customer { Id = 2, Name = "李四", Email = "li@test.com" });

Console.WriteLine($"商品数量:{productRepo.Count}");   // 2
Console.WriteLine($"客户数量:{customerRepo.Count}");  // 2

案例二:泛型缓存器

csharp
public class Cache<TKey, TValue>
{
    private readonly Dictionary<TKey, TValue> _cache = new();
    private readonly Dictionary<TKey, DateTime> _expiry = new();
    private readonly TimeSpan _defaultTtl;

    public Cache(TimeSpan defaultTtl)
    {
        _defaultTtl = defaultTtl;
    }

    public void Set(TKey key, TValue value, TimeSpan? ttl = null)
    {
        _cache[key] = value;
        _expiry[key] = DateTime.Now.Add(ttl ?? _defaultTtl);
    }

    public bool TryGet(TKey key, out TValue value)
    {
        if (_cache.TryGetValue(key, out value) && _expiry[key] > DateTime.Now)
            return true;

        _cache.Remove(key);
        value = default;
        return false;
    }

    public void Clear() => _cache.Clear();
}

// 使用
var cache = new Cache<string, List<int>>(TimeSpan.FromMinutes(5));
cache.Set("scores", new List<int> { 85, 92, 78 });

if (cache.TryGet("scores", out var scores))
{
    Console.WriteLine($"平均分:{scores.Average()}");  // 85
}

案例三:泛型对象映射器

csharp
public static class Mapper
{
    public static TTarget Map<TSource, TTarget>(TSource source)
        where TTarget : new()
    {
        var target = new TTarget();
        var sourceProps = typeof(TSource).GetProperties();
        var targetProps = typeof(TTarget).GetProperties();

        foreach (var sp in sourceProps)
        {
            var tp = Array.Find(targetProps, p => p.Name == sp.Name && p.PropertyType == sp.PropertyType);
            if (tp != null && tp.CanWrite)
            {
                tp.SetValue(target, sp.GetValue(source));
            }
        }

        return target;
    }
}

// 使用
public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

public class UserViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string ExtraInfo { get; set; }
}

var dto = new UserDto { Id = 1, Name = "张三", Email = "z@test.com" };
var vm = Mapper.Map<UserDto, UserViewModel>(dto);
Console.WriteLine($"{vm.Name} - {vm.Email}");  // 张三 - z@test.com

十、泛型常见问题

1. 泛型不能做的事

csharp
public class GenericIssues<T>
{
    // ❌ 不能直接实例化类型参数
    // T item = new T();  // 除非有 new() 约束

    // ❌ 不能使用运算符
    // T result = a + b;

    // ❌ 不能使用 typeof(T) 进行类型比较
    // if (typeof(T) == typeof(int)) ...

    // ❌ 不能将 T 用作特性参数类型
    // [MyAttribute<T>]  // 编译错误

    // ✅ 可以使用 EqualityComparer<T>.Default
    public bool AreEqual(T a, T b)
    {
        return EqualityComparer<T>.Default.Equals(a, b);
    }
}

2. 泛型与继承

csharp
// 开放类型和封闭类型
List<>          // 开放类型(open type):未指定类型参数
List<int>       // 封闭类型(closed type):已指定类型参数

// 开放类型不能实例化
// var list = new List<>();  // ❌ 编译错误

// 泛型类的继承
public class Base<T> { }
public class Derived : Base<int> { }       // ✅ 指定类型参数
// public class Derived2 : Base<T> { }     // ❌ 除非 Derived2 也是泛型
public class Derived3<T> : Base<T> { }     // ✅ 传递类型参数

核心知识点总结

泛型要素速查

要素语法示例
泛型方法Method<T>()Swap<T>(ref T a, ref T b)
泛型类class Class<T>List<T>Dictionary<TKey, TValue>
泛型接口interface IInterface<T>IEnumerable<T>IComparable<T>
泛型委托delegate T Del<T>()Func<T>Action<T>
类型约束where T : 条件where T : class, new()
协变out TIEnumerable<out T>
逆变in TAction<in T>

约束组合规则

组合是否允许示例
class + new()where T : class, new()
struct + new()✅(struct 总是有默认构造)where T : struct
class + struct❌ 互斥
class + 基类where T : class, Animal
接口 + new()where T : IComparable, new()

选择指南

场景选择
需要处理多种类型的同一逻辑泛型方法或泛型类
需要集合存储特定类型List<T>Dictionary<TKey, TValue>
需要数据访问的通用模式泛型接口(如 IRepository<T>
需要类型安全的委托Action<T>Func<T, R>
需要为不同类型定义不同的行为使用约束而非运行时类型检查

注意事项

  1. 泛型是编译时机制——编译器为每个值类型生成专用代码,为所有引用类型共享同一份代码
  2. 避免过度泛化——不是所有方法都需要泛型,必要时才使用
  3. 静态成员按封闭类型独立——List<int>List<string> 的静态成员相互独立
  4. 性能优势在值类型最明显——避免装箱拆箱,引用类型差异较小
  5. 协变/逆变只适用于接口和委托——类不支持协变/逆变
  6. 尽量使用类型推断——编译器通常能推断泛型类型参数,使代码更简洁

Released under the MIT License.