日常更新一些笔记
This commit is contained in:
@@ -0,0 +1,426 @@
|
||||
# 泛型约束
|
||||
概念:
|
||||
泛型默认可以接收很多类型
|
||||
如果我们希望 `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
|
||||
#进阶
|
||||
|
||||
Reference in New Issue
Block a user