427 lines
7.4 KiB
Markdown
427 lines
7.4 KiB
Markdown
# 泛型约束
|
|
概念:
|
|
泛型默认可以接收很多类型
|
|
如果我们希望 `T` 只能是“某一类类型”,就要加==泛型约束==
|
|
|
|
作用:
|
|
1. 限制泛型参数的取值范围
|
|
2. 让编译器提前检查类型是否符合要求
|
|
3. 让我们可以安全地使用某些成员或写法
|
|
4. 比如 `new T()`、调用接口方法、访问父类成员
|
|
|
|
关键字:
|
|
```csharp
|
|
where
|
|
```
|
|
|
|
----
|
|
# 通常用法
|
|
1. 限制泛型必须是引用类型或值类型
|
|
2. 限制泛型必须继承某个类
|
|
3. 限制泛型必须实现某个接口
|
|
4. 限制泛型必须有无参构造函数
|
|
5. 在工具类、工厂类、集合封装、通用算法中很常见
|
|
|
|
简单理解:
|
|
泛型像“留空的类型位置”
|
|
约束就是给这个空位加规则
|
|
不是任何类型都能随便传进来
|
|
|
|
----
|
|
# 语法
|
|
类上的泛型约束:
|
|
```csharp
|
|
class 类名<T> where T : 约束
|
|
{
|
|
}
|
|
```
|
|
|
|
方法上的泛型约束:
|
|
```csharp
|
|
void 方法名<T>() where T : 约束
|
|
{
|
|
}
|
|
```
|
|
|
|
多个泛型参数分别约束:
|
|
```csharp
|
|
class Test<T, K>
|
|
where T : class
|
|
where K : new()
|
|
{
|
|
}
|
|
```
|
|
|
|
多个约束组合写法:
|
|
```csharp
|
|
class Test<T> 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<T> where T : struct
|
|
{
|
|
}
|
|
```
|
|
|
|
正确:
|
|
```csharp
|
|
Test<int> t1 = new Test<int>();
|
|
Test<bool> t2 = new Test<bool>();
|
|
```
|
|
|
|
错误:
|
|
```csharp
|
|
// Test<string> t3 = new Test<string>();
|
|
```
|
|
|
|
说明:
|
|
`int`、`float`、`bool`、`char`、结构体 都属于值类型
|
|
|
|
----
|
|
# 2. class 约束
|
|
作用:
|
|
要求泛型参数必须是==引用类型==
|
|
|
|
```csharp
|
|
class Test<T> where T : class
|
|
{
|
|
}
|
|
```
|
|
|
|
正确:
|
|
```csharp
|
|
Test<string> t1 = new Test<string>();
|
|
Test<object> t2 = new Test<object>();
|
|
```
|
|
|
|
错误:
|
|
```csharp
|
|
// Test<int> t3 = new Test<int>();
|
|
```
|
|
|
|
说明:
|
|
类、数组、字符串、委托等通常都是引用类型
|
|
|
|
----
|
|
# 3. new() 约束
|
|
作用:
|
|
要求泛型参数必须有==公共无参构造函数==
|
|
|
|
```csharp
|
|
class Factory<T> where T : new()
|
|
{
|
|
public T Create()
|
|
{
|
|
return new T();
|
|
}
|
|
}
|
|
```
|
|
|
|
案例:
|
|
```csharp
|
|
class Player
|
|
{
|
|
public Player()
|
|
{
|
|
}
|
|
}
|
|
|
|
Factory<Player> factory = new Factory<Player>();
|
|
Player p = factory.Create();
|
|
```
|
|
|
|
说明:
|
|
如果不加 `where T : new()`,就不能直接写 `new T()`
|
|
|
|
----
|
|
# 4. 父类约束
|
|
作用:
|
|
要求泛型参数必须是==某个类本身或它的子类==
|
|
|
|
```csharp
|
|
class Animal
|
|
{
|
|
public void Eat()
|
|
{
|
|
Console.WriteLine("吃东西");
|
|
}
|
|
}
|
|
|
|
class Dog : Animal
|
|
{
|
|
}
|
|
|
|
class Test<T> where T : Animal
|
|
{
|
|
public void DoSomething(T value)
|
|
{
|
|
value.Eat();
|
|
}
|
|
}
|
|
```
|
|
|
|
使用:
|
|
```csharp
|
|
Test<Animal> t1 = new Test<Animal>();
|
|
Test<Dog> t2 = new Test<Dog>();
|
|
```
|
|
|
|
说明:
|
|
因为 `T` 一定是 `Animal` 或其子类
|
|
所以可以直接把 `T` 当成 `Animal` 来用
|
|
|
|
----
|
|
# 5. 接口约束
|
|
作用:
|
|
要求泛型参数必须==实现某个接口==
|
|
|
|
```csharp
|
|
interface IFly
|
|
{
|
|
void Fly();
|
|
}
|
|
|
|
class Bird : IFly
|
|
{
|
|
public void Fly()
|
|
{
|
|
Console.WriteLine("飞行");
|
|
}
|
|
}
|
|
|
|
class Test<T> where T : IFly
|
|
{
|
|
public void StartFly(T value)
|
|
{
|
|
value.Fly();
|
|
}
|
|
}
|
|
```
|
|
|
|
使用:
|
|
```csharp
|
|
Test<Bird> t = new Test<Bird>();
|
|
t.StartFly(new Bird());
|
|
```
|
|
|
|
说明:
|
|
因为约束保证了 `T` 一定有 `Fly()`
|
|
所以在泛型里可以直接调用接口成员
|
|
|
|
----
|
|
# 6. 泛型参数之间的约束
|
|
作用:
|
|
让一个泛型参数受另一个泛型参数限制
|
|
|
|
```csharp
|
|
class Test<T, K> where T : K
|
|
{
|
|
}
|
|
```
|
|
|
|
理解:
|
|
`T : K` 表示 `T` 必须是 `K` 本身
|
|
或者是 `K` 的派生类型、实现类型
|
|
|
|
案例:
|
|
```csharp
|
|
class Animal
|
|
{
|
|
}
|
|
|
|
class Dog : Animal
|
|
{
|
|
}
|
|
|
|
Test<Dog, Animal> t = new Test<Dog, Animal>();
|
|
```
|
|
|
|
----
|
|
# 组合约束
|
|
泛型约束可以组合使用:
|
|
|
|
```csharp
|
|
class Test<T> 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<T> where T : IRun, new()
|
|
{
|
|
public T Create()
|
|
{
|
|
return new T();
|
|
}
|
|
|
|
public void Start(T obj)
|
|
{
|
|
obj.Run();
|
|
}
|
|
}
|
|
|
|
Manager<Player> manager = new Manager<Player>();
|
|
Player player = manager.Create();
|
|
manager.Start(player);
|
|
```
|
|
|
|
输出:
|
|
```text
|
|
玩家正在移动
|
|
```
|
|
|
|
理解:
|
|
因为 `T` 同时满足 `IRun` 和 `new()`
|
|
所以既能 `new T()`,又能调用 `Run()`
|
|
|
|
----
|
|
# 约束能解决什么问题
|
|
没有约束时:
|
|
```csharp
|
|
class Test<T>
|
|
{
|
|
public void Show(T value)
|
|
{
|
|
// 这里不能随便调用 value 的成员
|
|
}
|
|
}
|
|
```
|
|
|
|
原因:
|
|
编译器不知道 `T` 到底是什么类型
|
|
|
|
加上约束后:
|
|
```csharp
|
|
interface IShow
|
|
{
|
|
void Show();
|
|
}
|
|
|
|
class Test<T> 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<Person> factory = new Factory<Person>();
|
|
```
|
|
|
|
原因:
|
|
`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
|
|
#进阶
|