# 泛型约束
概念：
	泛型默认可以接收很多类型
	如果我们希望 `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
#进阶
