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