# 泛型约束 概念: 泛型默认可以接收很多类型 如果我们希望 `T` 只能是“某一类类型”,就要加==泛型约束== 作用: 1. 限制泛型参数的取值范围 2. 让编译器提前检查类型是否符合要求 3. 让我们可以安全地使用某些成员或写法 4. 比如 `new T()`、调用接口方法、访问父类成员 关键字: ```csharp where ``` ---- # 通常用法 1. 限制泛型必须是引用类型或值类型 2. 限制泛型必须继承某个类 3. 限制泛型必须实现某个接口 4. 限制泛型必须有无参构造函数 5. 在工具类、工厂类、集合封装、通用算法中很常见 简单理解: 泛型像“留空的类型位置” 约束就是给这个空位加规则 不是任何类型都能随便传进来 ---- # 语法 类上的泛型约束: ```csharp class 类名 where T : 约束 { } ``` 方法上的泛型约束: ```csharp void 方法名() where T : 约束 { } ``` 多个泛型参数分别约束: ```csharp class Test where T : class where K : new() { } ``` 多个约束组合写法: ```csharp class Test where T : 父类名, 接口名, new() { } ``` ---- # 常见约束 常见的基础约束有 6 种: | 约束写法 | 含义 | |---|---| | `where T : struct` | `T` 必须是值类型 | | `where T : class` | `T` 必须是引用类型 | | `where T : new()` | `T` 必须有公共无参构造函数 | | `where T : 类名` | `T` 必须是某个类或它的子类 | | `where T : 接口名` | `T` 必须实现某个接口 | | `where T : U` | `T` 必须是另一个泛型参数 `U` 或它的派生类型 | 补充: 现在 C# 里还会见到一些新约束,比如: `class?`、`notnull`、`unmanaged`、`default` 入门和面试里最常见的,还是前面这 6 种 ---- # 1. struct 约束 作用: 要求泛型参数必须是==值类型== ```csharp class Test where T : struct { } ``` 正确: ```csharp Test t1 = new Test(); Test t2 = new Test(); ``` 错误: ```csharp // Test t3 = new Test(); ``` 说明: `int`、`float`、`bool`、`char`、结构体 都属于值类型 ---- # 2. class 约束 作用: 要求泛型参数必须是==引用类型== ```csharp class Test where T : class { } ``` 正确: ```csharp Test t1 = new Test(); Test t2 = new Test(); ``` 错误: ```csharp // Test t3 = new Test(); ``` 说明: 类、数组、字符串、委托等通常都是引用类型 ---- # 3. new() 约束 作用: 要求泛型参数必须有==公共无参构造函数== ```csharp class Factory where T : new() { public T Create() { return new T(); } } ``` 案例: ```csharp class Player { public Player() { } } Factory factory = new Factory(); Player p = factory.Create(); ``` 说明: 如果不加 `where T : new()`,就不能直接写 `new T()` ---- # 4. 父类约束 作用: 要求泛型参数必须是==某个类本身或它的子类== ```csharp class Animal { public void Eat() { Console.WriteLine("吃东西"); } } class Dog : Animal { } class Test where T : Animal { public void DoSomething(T value) { value.Eat(); } } ``` 使用: ```csharp Test t1 = new Test(); Test t2 = new Test(); ``` 说明: 因为 `T` 一定是 `Animal` 或其子类 所以可以直接把 `T` 当成 `Animal` 来用 ---- # 5. 接口约束 作用: 要求泛型参数必须==实现某个接口== ```csharp interface IFly { void Fly(); } class Bird : IFly { public void Fly() { Console.WriteLine("飞行"); } } class Test where T : IFly { public void StartFly(T value) { value.Fly(); } } ``` 使用: ```csharp Test t = new Test(); t.StartFly(new Bird()); ``` 说明: 因为约束保证了 `T` 一定有 `Fly()` 所以在泛型里可以直接调用接口成员 ---- # 6. 泛型参数之间的约束 作用: 让一个泛型参数受另一个泛型参数限制 ```csharp class Test where T : K { } ``` 理解: `T : K` 表示 `T` 必须是 `K` 本身 或者是 `K` 的派生类型、实现类型 案例: ```csharp class Animal { } class Dog : Animal { } Test t = new Test(); ``` ---- # 组合约束 泛型约束可以组合使用: ```csharp class Test where T : Animal, IFly, new() { public T CreateAndUse() { T value = new T(); value.Eat(); value.Fly(); return value; } } ``` 含义: 1. `T` 必须继承 `Animal` 2. `T` 必须实现 `IFly` 3. `T` 必须有公共无参构造函数 ---- # 基础案例 ```csharp using System; interface IRun { void Run(); } class Player : IRun { public Player() { } public void Run() { Console.WriteLine("玩家正在移动"); } } class Manager where T : IRun, new() { public T Create() { return new T(); } public void Start(T obj) { obj.Run(); } } Manager manager = new Manager(); Player player = manager.Create(); manager.Start(player); ``` 输出: ```text 玩家正在移动 ``` 理解: 因为 `T` 同时满足 `IRun` 和 `new()` 所以既能 `new T()`,又能调用 `Run()` ---- # 约束能解决什么问题 没有约束时: ```csharp class Test { public void Show(T value) { // 这里不能随便调用 value 的成员 } } ``` 原因: 编译器不知道 `T` 到底是什么类型 加上约束后: ```csharp interface IShow { void Show(); } class Test where T : IShow { public void Print(T value) { value.Show(); } } ``` 说明: 约束的本质,就是告诉编译器: “这个泛型参数至少具备这些能力” ---- # 注意 1. 泛型约束使用关键字 `where` 2. `new()` 约束表示必须有公共无参构造函数 3. 想写 `new T()`,必须加 `where T : new()` 4. 父类约束和接口约束可以让我们安全调用成员 5. 一个泛型参数可以同时有多个约束 6. 如果有 `new()`,通常要写在约束列表最后 7. `struct` 和 `class` 一般不能同时使用 8. 约束的作用是“限制类型 + 提供可用能力” ---- # 易错点 1. `new()` 不是“可以 new 一切”,而是“必须存在公共无参构造函数” 2. 有参构造函数不算满足 `new()` 3. `where T : 类名` 表示继承这个类,不是“名字像这个类” 4. `where T : 接口名` 表示实现接口,不是普通包含同名方法 5. 没有约束时,不能因为自己“知道”类型就直接调用成员 错误示例: ```csharp class Person { public Person(string name) { } } // Factory factory = new Factory(); ``` 原因: `Person` 没有公共无参构造函数 所以不满足 `new()` ---- # 一句话记忆 ==泛型约束就是给泛型参数加条件,让它只能使用满足要求的类型。== ---- # 面试/复习时怎么说 可以这样答: “泛型约束就是用 `where` 给泛型参数加限制。 比如可以限制它必须是值类型、引用类型、某个父类、某个接口,或者必须有无参构造函数。 这样做的好处是,编译器能提前帮我们检查类型,同时我们在泛型内部也能更安全地 `new T()` 或调用指定成员。” ---- 引用: 1. Microsoft Learn: [Constraints on type parameters](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters) 2. Microsoft Learn: [where (generic type constraint)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint) #泛型约束 #泛型 #CSharp #进阶