01-C#.Net-泛型-学习笔记

一、泛型的概念与引入

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 泛型的优势

  1. 性能优异:避免装箱拆箱操作
  2. 代码复用:一个方法/类满足多种类型需求
  3. 类型安全:编译时类型检查,避免运行时错误
  4. 可读性强:代码意图清晰

性能对比示例:

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);     // ✓ 返回类型的默认值
}

八、关键要点总结

  1. 泛型不是语法糖,是框架升级支持的特性(.NET 2.0)
  2. 性能优势:避免装箱拆箱,与普通方法性能相当
  3. 类型安全:编译时检查,避免运行时错误
  4. 泛型约束:要么不让进,进来就保证安全
  5. 泛型缓存:每个类型生成独立副本,常驻内存
  6. 协变逆变:只针对接口和委托,增加类型转换的灵活性
  7. 应用场景:集合类、ORM框架、通用工具类、API返回结果封装
相关推荐
百锦再1 小时前
飞算 JavaAI:我的编程强力助推引擎
java·spring·ai·编程·idea·code·飞算
篮l球场2 小时前
Trie(字典树/前缀树)
开发语言·c#
今儿敲了吗2 小时前
python基础学习笔记第三章
笔记·python·学习
似水明俊德2 小时前
15-C#
android·开发语言·c#
wuyaolong0072 小时前
Spring Boot 3.4 正式发布,结构化日志!
java·spring boot·后端
hua872222 小时前
Golang 构建学习
java·开发语言·学习
Halo_tjn2 小时前
Java 三个修饰符 相关知识点
java·开发语言
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 广东省非遗文化信息管理系统的设计与实现为例,包含答辩的问题和答案
java
番茄去哪了2 小时前
Java基础面试题day01
java·开发语言·后端·javase·八股·面向对象编程