Files
My-Notes/笔记/CSharp/进阶/泛型约束.md
T
2026-06-22 22:11:42 +08:00

7.4 KiB

泛型约束

概念: 泛型默认可以接收很多类型 如果我们希望 T 只能是“某一类类型”,就要加==泛型约束==

作用: 1. 限制泛型参数的取值范围 2. 让编译器提前检查类型是否符合要求 3. 让我们可以安全地使用某些成员或写法 4. 比如 new T()、调用接口方法、访问父类成员

关键字:

where

通常用法

  1. 限制泛型必须是引用类型或值类型
  2. 限制泛型必须继承某个类
  3. 限制泛型必须实现某个接口
  4. 限制泛型必须有无参构造函数
  5. 在工具类、工厂类、集合封装、通用算法中很常见

简单理解: 泛型像“留空的类型位置” 约束就是给这个空位加规则 不是任何类型都能随便传进来


语法

类上的泛型约束:

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?notnullunmanageddefault 入门和面试里最常见的,还是前面这 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>();

说明: intfloatboolchar、结构体 都属于值类型


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;
	}
}

含义:

  1. T 必须继承 Animal
  2. T 必须实现 IFly
  3. T 必须有公共无参构造函数

基础案例

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 同时满足 IRunnew() 所以既能 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();
	}
}

说明: 约束的本质,就是告诉编译器: “这个泛型参数至少具备这些能力”


注意

  1. 泛型约束使用关键字 where
  2. new() 约束表示必须有公共无参构造函数
  3. 想写 new T(),必须加 where T : new()
  4. 父类约束和接口约束可以让我们安全调用成员
  5. 一个泛型参数可以同时有多个约束
  6. 如果有 new(),通常要写在约束列表最后
  7. structclass 一般不能同时使用
  8. 约束的作用是“限制类型 + 提供可用能力”

易错点

  1. new() 不是“可以 new 一切”,而是“必须存在公共无参构造函数”
  2. 有参构造函数不算满足 new()
  3. where T : 类名 表示继承这个类,不是“名字像这个类”
  4. where T : 接口名 表示实现接口,不是普通包含同名方法
  5. 没有约束时,不能因为自己“知道”类型就直接调用成员

错误示例:

class Person
{
	public Person(string name)
	{
	}
}

// Factory<Person> factory = new Factory<Person>();

原因: Person 没有公共无参构造函数 所以不满足 new()


一句话记忆

==泛型约束就是给泛型参数加条件,让它只能使用满足要求的类型。==


面试/复习时怎么说

可以这样答:

“泛型约束就是用 where 给泛型参数加限制。
比如可以限制它必须是值类型、引用类型、某个父类、某个接口,或者必须有无参构造函数。
这样做的好处是,编译器能提前帮我们检查类型,同时我们在泛型内部也能更安全地 new T() 或调用指定成员。”


引用:

  1. Microsoft Learn: Constraints on type parameters
  2. Microsoft Learn: where (generic type constraint)

#泛型约束 #泛型 #CSharp #进阶