一、泛型的概念与引入
1.1 什么是泛型
- 泛:宽泛的、不确定的
- 型:类型
- 泛型:不确定的类型,延迟声明的类型
1.2 为什么需要泛型
传统方案的问题:
csharp
// 方案1:为每种类型写一个方法 - 代码冗余
public static void ShowInt(int iParameter) { }
public static void ShowString(string sParameter) { }
public static void ShowDateTime(DateTime dtParameter) { }
// 如果有100个类型,需要100个方法?
// 方案2:使用Object类型 - 存在性能和类型安全问题
public static void ShowObject(object oParameter)
{
// 问题1:装箱拆箱导致性能损失
// 问题2:类型不安全,编译时无法检测错误
// 问题3:无法直接访问具体类型的成员
}
泛型方案:
csharp
// 一个方法满足所有类型需求
public static void Show<T>(T tParameter)
{
Console.WriteLine($"Type={tParameter.GetType().Name}");
}
// 调用方式
Show<int>(123); // 显式指定类型
Show(123); // 类型推导,可省略尖括号
Show<string>("hello");
1.3 泛型的优势
- 性能优异:避免装箱拆箱操作
- 代码复用:一个方法/类满足多种类型需求
- 类型安全:编译时类型检查,避免运行时错误
- 可读性强:代码意图清晰
性能对比示例:
csharp
// 1亿次调用性能测试
// 普通方法:最快
// Object方法:慢(装箱拆箱)
// 泛型方法:与普通方法性能相当
二、泛型的声明与原理
2.1 泛型的声明特点
- 延迟声明:声明时使用占位符T,调用时才确定具体类型
- 类型参数:T称为类型参数或类型变量
- 尖括号语法 :
<T>用于声明泛型
2.2 底层原理
csharp
// 在底层,泛型会生成带有反引号的类型
List<T> // 底层:List`1[T]
Dictionary<TKey, TValue> // 底层:Dictionary`2[TKey,TValue]
- 泛型由 .NET Framework 2.0 引入
- 需要编译器 和CLR运行时同时支持
- 泛型不是语法糖,而是框架升级支持的特性
三、泛型的多种应用
3.1 泛型方法
csharp
public static void Show<T>(T tParameter)
{
Console.WriteLine(tParameter.GetType().Name);
}
3.2 泛型接口
csharp
public interface GenericInterface<T>
{
T Show();
}
// 实现方式1:指定具体类型
public class ChildClass : GenericInterface<int>
{
public int Show() { return 0; }
}
// 实现方式2:保持泛型
public class ChildClass<S> : GenericInterface<S>
{
public S Show() { return default(S); }
}
3.3 泛型类
csharp
// 多个类型参数
public class GenericClass<T, S, X, Y>
{
public void Show(T t) { }
}
// 抽象泛型类
public abstract class GenericAbstractClass<T>
{
public void Show(T t) { }
}
3.4 泛型委托
csharp
public delegate T GenericDelegate<T>();
// 使用
GenericDelegate<string> del = () => "hello";
GenericDelegate<int> del2 = () => 123;
四、泛型约束
4.1 为什么需要约束
csharp
// 没有约束时,无法访问类型的特定成员
public static void Show<T>(T t)
{
// t.Id; // 编译错误:T没有Id属性
}
4.2 六种约束类型
1. 基类约束 where T : BaseClass
csharp
public static void ShowBase<T>(T t) where T : People
{
// 可以访问People的成员
Console.WriteLine(t.Id);
Console.WriteLine(t.Name);
}
// 调用
ShowBase<People>(people); // ✓
ShowBase<Chinese>(chinese); // ✓ Chinese继承People
ShowBase<Japanese>(japanese); // ✗ Japanese不继承People
2. 接口约束 where T : IInterface
csharp
public static void ShowInterface<T>(T t) where T : ISports
{
t.Pingpang(); // 可以调用接口方法
}
// 调用
ShowInterface<Chinese>(chinese); // ✓ Chinese实现了ISports
ShowInterface<Japanese>(japanese); // ✓ Japanese实现了ISports
3. 引用类型约束 where T : class
csharp
public static void ShowClass<T>(T t) where T : class
{
// T必须是引用类型(类)
}
ShowClass<People>(people); // ✓
ShowClass<int>(123); // ✗ int是值类型
4. 值类型约束 where T : struct
csharp
public static void ShowStruct<T>(T t) where T : struct
{
T newT = new T(); // 值类型可以直接new
}
ShowStruct<int>(123); // ✓
ShowStruct<DateTime>(DateTime.Now); // ✓
ShowStruct<People>(people); // ✗ People是引用类型
5. 无参构造函数约束 where T : new()
csharp
public static void ShowNew<T>(T t) where T : new()
{
T newT = new T(); // 可以创建实例
}
6. 枚举约束 where T : Enum
csharp
public static void ShowEnum<T>(T t) where T : Enum
{
// T必须是枚举类型
}
ShowEnum<UserType>(UserType.Normal); // ✓
4.3 多重约束
csharp
// 父子关系约束
public static void ShowParent<T, S>(T t, S s) where T : S
{
// T和S要么是同一类型,要么T是S的子类
}
// 多个约束
public static void ShowTest<T, S>(T t, S s)
where T : class
where S : struct
{
}
五、泛型缓存
5.1 普通字典缓存
csharp
public class DictionaryCache
{
private static Dictionary<Type, string> _TypeTimeDictionary = null;
static DictionaryCache()
{
_TypeTimeDictionary = new Dictionary<Type, string>();
}
public static string GetCache<T>()
{
Type type = typeof(T);
if (!_TypeTimeDictionary.ContainsKey(type))
{
_TypeTimeDictionary[type] = $"{typeof(T).FullName}_{DateTime.Now}";
}
return _TypeTimeDictionary[type];
}
}
5.2 泛型缓存
csharp
public class GenericCache<T>
{
private static string _TypeTime = "";
// 静态构造函数:每个T类型只执行一次
static GenericCache()
{
_TypeTime = $"{typeof(T).FullName}_{DateTime.Now}";
}
public static string GetCache()
{
return _TypeTime;
}
}
泛型缓存的特点:
- 每个不同的T类型,会生成一个独立的类副本
- 每个副本有自己的静态字段和静态构造函数
GenericCache<int>和GenericCache<string>是完全不同的类- 应用场景:ORM框架中的类型缓存
六、协变与逆变
6.1 问题引入
csharp
Animal animal = new Cat(); // ✓ 子类可以赋值给父类
List<Animal> list1 = new List<Animal>(); // ✓
List<Animal> list2 = new List<Cat>(); // ✗ 编译错误
// 一只猫是动物,但一堆猫不是一堆动物?
6.2 协变(Covariance)- out
定义:允许使用更具体的派生类型(子类)
csharp
// out关键字:T只能作为返回值,不能作为参数
public interface ICustomerListOut<out T>
{
T Get(); // ✓ 可以作为返回值
// void Show(T t); // ✗ 不能作为参数
}
// 使用
IEnumerable<Animal> list1 = new List<Animal>(); // ✓
IEnumerable<Animal> list2 = new List<Cat>(); // ✓ 协变
Func<Animal> func = new Func<Cat>(() => new Cat()); // ✓
原理:右边是Cat,左边是Animal
- 调用Get()返回Cat,赋值给Animal变量 ✓ 安全
- 如果允许Show(Animal),实际需要Cat ✗ 不安全
6.3 逆变(Contravariance)- in
定义:允许使用更泛化的基类型(父类)
csharp
// in关键字:T只能作为参数,不能作为返回值
public interface ICustomerListIn<in T>
{
void Show(T t); // ✓ 可以作为参数
// T Get(); // ✗ 不能作为返回值
}
// 使用
ICustomerListIn<Cat> list1 = new CustomerListIn<Cat>(); // ✓
ICustomerListIn<Cat> list2 = new CustomerListIn<Animal>(); // ✓ 逆变
Action<Animal> action = new Action<Cat>(c => { }); // ✗
Action<Cat> action2 = new Action<Animal>(a => { }); // ✓ 逆变
原理:右边是Animal,左边是Cat
- 调用Show(Cat),传入Cat,Animal可以接收 ✓ 安全
- 如果允许Get()返回Animal,赋值给Cat ✗ 不安全
6.4 协变+逆变组合
csharp
public interface IMyList<in TIn, out TOut>
{
void Show(TIn t); // in:只能作为参数
TOut Get(); // out:只能作为返回值
TOut Do(TIn t); // 组合使用
}
// 使用
IMyList<Cat, Animal> list1 = new MyList<Cat, Animal>(); // ✓
IMyList<Cat, Animal> list2 = new MyList<Cat, Cat>(); // ✓ 协变
IMyList<Cat, Animal> list3 = new MyList<Animal, Animal>(); // ✓ 逆变
IMyList<Cat, Animal> list4 = new MyList<Animal, Cat>(); // ✓ 协变+逆变
6.5 记忆口诀
- 协变(out):出去的,返回值,子类→父类,"出口放宽"
- 逆变(in):进来的,参数,父类→子类,"入口放宽"
- 限制:只适用于泛型接口和泛型委托
七、实际应用示例
7.1 分页结果类
csharp
public class PageResult<T> where T : class
{
public bool Success { get; set; }
public string Message { get; set; }
public List<T> DataList { get; set; }
}
// 使用
PageResult<User> userPage = new PageResult<User>();
PageResult<Product> productPage = new PageResult<Product>();
7.2 返回默认值
csharp
public S Show<S>()
{
// return new S(); // ✗ S可能没有无参构造函数
return default(S); // ✓ 返回类型的默认值
}
八、关键要点总结
- 泛型不是语法糖,是框架升级支持的特性(.NET 2.0)
- 性能优势:避免装箱拆箱,与普通方法性能相当
- 类型安全:编译时检查,避免运行时错误
- 泛型约束:要么不让进,进来就保证安全
- 泛型缓存:每个类型生成独立副本,常驻内存
- 协变逆变:只针对接口和委托,增加类型转换的灵活性
- 应用场景:集合类、ORM框架、通用工具类、API返回结果封装