C# 泛型委托详解
一、什么是泛型委托?
1.1 先回忆普通委托的问题
普通委托只能绑定固定类型的方法。假如你要写一个"打印东西"的委托:
// 普通委托——类型写死了
delegate void Print(string message); // 只能打印 string
delegate void PrintInt(int number); // 只能打印 int
delegate void PrintDouble(double d); // 只能打印 double
// 三个委托几乎一模一样,只是类型不同——代码重复!
Print p1 = msg => Console.WriteLine(msg);
PrintInt p2 = n => Console.WriteLine(n);
PrintDouble p3 = d => Console.WriteLine(d);
泛型委托就是给委托加上"类型参数",一个委托适配多种类型。
// 泛型委托——一个顶三个!
delegate void Print<T>(T item);
Print<string> p1 = msg => Console.WriteLine(msg);
Print<int> p2 = n => Console.WriteLine(n);
Print<double> p3 = d => Console.WriteLine(d);
p1("Hello"); // Hello
p2(123); // 123
p3(3.14); // 3.14
一句话:泛型委托 = 委托 + 泛型,让一个委托模板适配所有类型的参数。
1.2 生活类比
普通委托像是"只能装苹果的篮子",泛型委托像是"可以装任何水果的篮子,装的时候指定装什么"。
普通委托: PrintString → 只能装 string
PrintInt → 只能装 int
PrintDouble → 只能装 double
泛型委托: Print<T> → 装的时候指定
Print<string> → 装 string
Print<int> → 装 int
Print<double> → 装 double
二、自定义泛型委托
2.1 基本定义
// 语法:delegate 返回值 委托名<类型参数>(参数列表);
// 无返回值的泛型委托
delegate void MyAction<T>(T item);
// 有返回值的泛型委托
delegate TResult MyFunc<T, TResult>(T input);
2.2 完整示例
using System;
// 定义泛型委托
delegate T Calculator<T>(T a, T b);
class Program
{
// 匹配的方法(也都是泛型)
static int AddInt(int a, int b) => a + b;
static double AddDouble(double a, double b) => a + b;
static string Concat(string a, string b) => a + b;
static void Main()
{
Calculator<int> intCalc = AddInt;
Console.WriteLine(intCalc(10, 20)); // 30
Calculator<double> doubleCalc = AddDouble;
Console.WriteLine(doubleCalc(3.5, 2.5)); // 6.0
Calculator<string> stringCalc = Concat;
Console.WriteLine(stringCalc("Hello", "World")); // HelloWorld
// 甚至可以用 Lambda
Calculator<int> max = (a, b) => a > b ? a : b;
Console.WriteLine(max(15, 27)); // 27
}
}
2.3 多个类型参数
// 两个类型参数的泛型委托
delegate TResult Converter<TInput, TResult>(TInput input);
class Program
{
static void Main()
{
// int → string
Converter<int, string> intToStr = n => $"这是数字 {n}";
Console.WriteLine(intToStr(42)); // 这是数字 42
// string → int
Converter<string, int> strToInt = s => int.Parse(s);
Console.WriteLine(strToInt("123") + 1); // 124
// bool → string
Converter<bool, string> boolToStr = b => b ? "是" : "否";
Console.WriteLine(boolToStr(true)); // 是
Console.WriteLine(boolToStr(false)); // 否
}
}
2.4 泛型委托做方法参数
delegate bool Filter<T>(T item);
// 通用过滤方法——传入什么类型就筛选什么类型
static List<T> FilterList<T>(List<T> list, Filter<T> condition)
{
List<T> result = new List<T>();
foreach (T item in list)
{
if (condition(item))
result.Add(item);
}
return result;
}
// 使用
List<int> numbers = new List<int> { 1, 15, 7, 23, 8, 42, 3 };
List<string> names = new List<string> { "Alice", "Bob", "Charlie", "David" };
var bigNumbers = FilterList(numbers, n => n > 10);
var longNames = FilterList(names, n => n.Length > 4);
Console.WriteLine($"大于10的数字: {string.Join(", ", bigNumbers)}"); // 15, 23, 42
Console.WriteLine($"长名字: {string.Join(", ", longNames)}"); // Alice, Charlie, David
三、内置泛型委托(Action、Func、Predicate)
C# 已经帮你定义好了最常用的泛型委托,99% 的情况下不需要自己定义。
3.1 Action —— 无返回值
// Action 家族:可以有 0~16 个参数,没有返回值
Action sayHello = () => Console.WriteLine("你好!");
Action<string> print = s => Console.WriteLine(s);
Action<string, int> repeat = (s, n) =>
{
for (int i = 0; i < n; i++)
Console.Write(s);
Console.WriteLine();
};
Action<int, int, int> sum3 = (a, b, c) => Console.WriteLine(a + b + c);
sayHello(); // 你好!
print("泛型委托"); // 泛型委托
repeat("⭐", 3); // ⭐⭐⭐
sum3(1, 2, 3); // 6
// Action 的实际使用场景
List<string> names = new List<string> { "张三", "李四", "王五" };
// List 的 ForEach 方法接收的就是 Action<T>
names.ForEach(name => Console.WriteLine($"学生: {name}"));
// 输出:
// 学生: 张三
// 学生: 李四
// 学生: 王五
3.2 Func —— 有返回值
// Func 家族:最后一个类型参数是返回值类型
Func<int> rollDice = () => new Random().Next(1, 7);
Func<int, int> square = x => x * x;
Func<int, int, int> add = (a, b) => a + b;
Func<string, string, bool> startsWith = (s, prefix) => s.StartsWith(prefix);
Func<int, int, int, double> avg = (a, b, c) => (a + b + c) / 3.0;
Console.WriteLine(rollDice()); // 比如: 4
Console.WriteLine(square(9)); // 81
Console.WriteLine(add(15, 27)); // 42
Console.WriteLine(startsWith("Hello", "He")); // True
Console.WriteLine(avg(80, 90, 100)); // 90.0
Func 参数的命名不是固定的,关键看有几个参数:
Func<int, bool> // 参数 int → 返回 bool
Func<string, int, bool> // 参数 string, int → 返回 bool
Func<double> // 无参数 → 返回 double
记忆技巧:Func 的最后一个类型参数永远是返回值类型,前面的都是参数类型。
3.3 Predicate —— 返回 bool
// Predicate<T> 等价于 Func<T, bool>,专门用于做判断
Predicate<int> isPositive = n => n > 0;
Predicate<string> isLong = s => s.Length > 5;
Predicate<object> isNull = obj => obj == null;
Console.WriteLine(isPositive(-5)); // False
Console.WriteLine(isLong("Hello")); // False
Console.WriteLine(isNull(null)); // True
Predicate 主要用在 List 的 Find、FindAll 等方法:
List<int> scores = new List<int> { 45, 78, 92, 60, 55, 88, 39 };
// Find:找第一个满足条件
int firstPass = scores.Find(s => s >= 60);
Console.WriteLine($"第一个及格的: {firstPass}"); // 78
// FindAll:找所有满足条件
List<int> allPassed = scores.FindAll(s => s >= 60);
Console.WriteLine($"全部及格的: {string.Join(", ", allPassed)}"); // 78, 92, 60, 88
// FindIndex:找索引
int index = scores.FindIndex(s => s == 92);
Console.WriteLine($"92分的索引: {index}"); // 2
// RemoveAll:删除所有满足条件的
scores.RemoveAll(s => s < 60);
Console.WriteLine($"删除不及格后: {string.Join(", ", scores)}"); // 78, 92, 60, 88
四、内置泛型委托速查表
| 委托类型 | 参数个数 | 返回值 | 什么时候用 | 示例 |
|---|---|---|---|---|
Action |
0 | 无 | 执行一个操作 | Action act = () => ... |
Action<T> |
1 | 无 | 处理一个东西 | Action<string> print = s => ... |
Action<T1,T2> |
2 | 无 | 处理两个东西 | Action<int,int> = (a,b) => ... |
Action<T1~T16> |
1~16 | 无 | 多参数操作 | |
Func<TResult> |
0 | 1 | 得到一个值 | Func<int> r = () => 42 |
Func<T,TResult> |
1 | 1 | 转换/处理返回 | Func<int,int> sq = x => x*x |
Func<T1,T2,TResult> |
2 | 1 | 两入一出 | Func<int,int,int> add |
Func<T1~T16,TResult> |
1~16 | 1 | 多入一出 | |
Predicate<T> |
1 | bool |
判断条件 | Predicate<int> p = x => x>0 |
五、实战示例——泛型委托的综合运用
5.1 示例:通用数据处理器
using System;
using System.Collections.Generic;
class Program
{
// 通用处理器——三个委托参数:筛选、转换、输出
static List<TResult> ProcessData<TInput, TResult>(
List<TInput> data,
Predicate<TInput> filter, // 筛选条件
Func<TInput, TResult> convert, // 转换规则
Action<TResult> output) // 输出动作
{
List<TResult> results = new List<TResult>();
foreach (TInput item in data)
{
if (filter(item))
{
TResult converted = convert(item);
output(converted);
results.Add(converted);
}
}
return results;
}
static void Main()
{
// 原始数据
List<int> scores = new List<int> { 45, 78, 92, 60, 55, 88, 39 };
List<string> names = new List<string> { "ZhangSan", "LiSi", "WangWu", "ZhaoLiu" };
// 场景一:处理分数——筛选及格,转成等级,打印
Console.WriteLine("===== 分数处理 =====");
var grades = ProcessData(
scores,
s => s >= 60, // 筛选:及格
s => s >= 90 ? "A" : s >= 80 ? "B" : "C", // 转换:分数→等级
grade => Console.Write($"{grade} "));
Console.WriteLine();
// 场景二:处理名字——筛选长度>5,转成大写,打印
Console.WriteLine("\n===== 名字处理 =====");
var processed = ProcessData(
names,
n => n.Length > 5, // 筛选:名字长于5
n => n.ToUpper(), // 转换:转大写
n => Console.Write($"{n} "));
Console.WriteLine();
}
}
输出:
===== 分数处理 =====
B A C B
===== 名字处理 =====
ZHANGSAN WANGWU ZHAOLIU
5.2 示例:回调系统
using System;
class Program
{
// 异步操作,完成后回调
static void DoWorkAsync<T>(Func<T> work, Action<T> onSuccess, Action<string> onError)
{
try
{
Console.WriteLine("工作中...");
T result = work();
onSuccess(result); // 成功时回调
}
catch (Exception ex)
{
onError(ex.Message); // 失败时回调
}
}
static void Main()
{
// 成功的情况
DoWorkAsync(
work: () => { return 42; },
onSuccess: result => Console.WriteLine($"成功!结果: {result}"),
onError: err => Console.WriteLine($"失败: {err}")
);
// 失败的情况
DoWorkAsync(
work: () => { throw new Exception("网络超时"); },
onSuccess: r => Console.WriteLine($"结果: {r}"),
onError: err => Console.WriteLine($"失败: {err}")
);
}
}
输出:
工作中...
成功!结果: 42
工作中...
失败: 网络超时
5.3 示例:链式操作
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// LINQ 的方法很多就是用 Func 和 Predicate 实现的
// Where → 接收 Func<T, bool>(也就是 Predicate<T>)
// Select → 接收 Func<T, TResult>
// ForEach → 接收 Action<T>
var result = numbers
.Where(n => n % 2 == 0) // Predicate<int>: 筛选偶数
.Select(n => n * n) // Func<int, int>: 转成平方
.OrderByDescending(n => n); // Func<int, int>: 降序排列
Console.WriteLine("偶数平方降序:");
result.ToList().ForEach(n => Console.Write($"{n} "));
// 输出: 100 64 36 16 4
}
}
5.4 完整实战:简易事件总线
using System;
using System.Collections.Generic;
class EventBus
{
// 用一个泛型字典存储事件处理器
// key 是事件类型,value 是处理器委托列表
private Dictionary<Type, Delegate> _handlers
= new Dictionary<Type, Delegate>();
// 注册事件处理器(订阅)
public void Subscribe<T>(Action<T> handler)
{
Type eventType = typeof(T);
if (_handlers.ContainsKey(eventType))
{
_handlers[eventType] = Delegate.Combine(_handlers[eventType], handler);
}
else
{
_handlers[eventType] = handler;
}
Console.WriteLine($"订阅 {eventType.Name} 事件成功");
}
// 发布事件
public void Publish<T>(T eventData)
{
Type eventType = typeof(T);
if (_handlers.ContainsKey(eventType))
{
Action<T> handler = (Action<T>)_handlers[eventType];
handler?.Invoke(eventData);
}
}
}
// 事件数据类型
class UserLoginEvent
{
public string Username;
public DateTime LoginTime;
}
class OrderCreatedEvent
{
public int OrderId;
public double Amount;
}
class Program
{
static void Main()
{
var bus = new EventBus();
// 订阅用户登录事件
bus.Subscribe<UserLoginEvent>(e =>
{
Console.WriteLine($"📝 [日志] 用户 {e.Username} 在 {e.LoginTime:HH:mm:ss} 登录");
});
bus.Subscribe<UserLoginEvent>(e =>
{
Console.WriteLine($"🔔 [通知] 欢迎回来,{e.Username}!");
});
// 订阅订单事件
bus.Subscribe<OrderCreatedEvent>(e =>
{
Console.WriteLine($"💰 [财务] 新订单 #{e.OrderId},金额 {e.Amount} 元");
});
Console.WriteLine("\n===== 发布事件 =====");
// 发布事件
bus.Publish(new UserLoginEvent { Username = "张三", LoginTime = DateTime.Now });
bus.Publish(new OrderCreatedEvent { OrderId = 10086, Amount = 299.5 });
}
}
输出:
订阅 UserLoginEvent 事件成功
订阅 UserLoginEvent 事件成功
订阅 OrderCreatedEvent 事件成功
===== 发布事件 =====
📝 [日志] 用户 张三 在 14:30:25 登录
🔔 [通知] 欢迎回来,张三!
💰 [财务] 新订单 #10086,金额 299.5 元
六、泛型委托与普通委托的对比
6.1 代码量对比
// ===== 不用泛型委托 =====
delegate int IntOperation(int a, int b);
delegate double DoubleOperation(double a, double b);
delegate string StringOperation(string a, string b);
// ... 每加一种类型就加一个委托
// ===== 用泛型委托 =====
delegate T Operation<T>(T a, T b); // 一个顶所有
// 或者直接用内置的 Func<T, T, T>
Func<int, int, int> intOp = (a, b) => a + b;
Func<double, double, double> doubleOp = (a, b) => a + b;
Func<string, string, string> stringOp = (a, b) => a + b;
6.2 对比表
| 特性 | 普通委托 | 泛型委托 |
|---|---|---|
| 类型灵活度 | 写死类型 | 类型参数化,调用时指定 |
| 代码量 | 每种类型一个委托 | 一个委托所有类型 |
| 维护成本 | 高(加类型要加委托) | 低 |
| 类型安全 | 安全 | 安全(编译期检查) |
| 代表 | delegate void MyAction(string s) |
delegate void MyAction<T>(T s) |
七、泛型委托的约束
泛型委托和普通泛型一样,可以加约束来限制类型参数:
// 约束 T 必须是引用类型
delegate void ReferenceAction<T>(T item) where T : class;
// 约束 T 必须是值类型
delegate void ValueAction<T>(T item) where T : struct;
// 约束 T 必须有无参构造函数
delegate T Creator<T>() where T : new();
// 约束 T 必须继承自某个类或实现某接口
delegate void AnimalAction<T>(T animal) where T : Animal;
// 多约束
delegate T Converter<T, U>(U input)
where T : class, new()
where U : struct;
使用示例:
class Animal { }
class Dog : Animal { public Dog() { } }
// T 必须是 Animal 的子类,且有无参构造
delegate T AnimalCreator<T>() where T : Animal, new();
AnimalCreator<Dog> makeDog = () => new Dog();
Dog dog = makeDog();
// AnimalCreator<int> makeInt = () => 5; // ❌ 错误!int 不继承 Animal
八、匿名方法与 Lambda 中的泛型委托
8.1 Lambda 自动适配泛型委托
// 同一个 Lambda 可以用在不同类型的委托上
var lambda = (int x) => x * 2;
Func<int, int> f1 = lambda; // 没问题
// Predicate<int> p1 = lambda; // ❌ 返回 int,不是 bool
8.2 泛型方法返回泛型委托
// 创建一个返回泛型委托的方法
static Func<T, bool> CreateFilter<T>(T threshold) where T : IComparable<T>
{
return item => item.CompareTo(threshold) > 0;
}
// 使用
var greaterThan5 = CreateFilter(5);
Console.WriteLine(greaterThan5(10)); // True
Console.WriteLine(greaterThan5(3)); // False
var greaterThanH = CreateFilter("H");
Console.WriteLine(greaterThanH("Z")); // True
Console.WriteLine(greaterThanH("A")); // False
九、常见易错点(避坑指南)
坑1:用自定义泛型委托而不用内置的
// ❌ 重复造轮子
delegate void MyVoidAction<T>(T item);
delegate TResult MyFunc<T, TResult>(T input);
delegate bool MyPredicate<T>(T item);
// ✅ 直接用内置的
Action<string> print = s => Console.WriteLine(s);
Func<int, string> convert = n => n.ToString();
Predicate<int> check = n => n > 0;
坑2:Func 的参数顺序搞反
// Func 的最后一个参数永远是返回值!
// ❌ 错误理解
// Func<int, string, bool> —— "int 和 bool 参数,返回 string" ← 错的!
// ✅ 正确理解
Func<int, string, bool> // 参数: int, string → 返回: bool
坑3:泛型委托和协变/逆变
// 内置的 Action 和 Func 已经标了 in 和 out,支持协变逆变
Action<object> printObj = obj => Console.WriteLine(obj);
Action<string> printStr = printObj; // ✅ 逆变!(Action<in T>)
Func<Dog> makeDog = () => new Dog();
Func<Animal> makeAnimal = makeDog; // ✅ 协变!(Func<out TResult>)
// 自己定义的泛型委托不自动支持协变/逆变,除非手动加 in/out
delegate T MyFunc<out T>(); // 手动加 out 才支持协变
delegate void MyAction<in T>(T t); // 手动加 in 才支持逆变
坑4:找不到就用 Func 和 Action 组合
// 如果找不到合适的委托,就用 Func 和 Action 组合
// 需要 3 个参数 + 返回 bool?
Func<int, string, double, bool> myCheck = (x, s, d) =>
{
return x > 0 && s.Length > 3 && d < 100;
};
// 需要 4 个参数 + 无返回值?
Action<int, int, int, int> myAction = (a, b, c, d) =>
{
Console.WriteLine(a + b + c + d);
};
坑5:Lambda 和局部函数的选择
// 当 Lambda 太长时,考虑用局部函数代替
// ❌ Lambda 太长了
Func<int, int, int> calc = (a, b) =>
{
if (a > 100) return a;
if (b < 0) return 0;
int sum = a + b;
sum *= 2;
return sum;
};
// ✅ 用局部函数更清晰
int Calc(int a, int b)
{
if (a > 100) return a;
if (b < 0) return 0;
int sum = a + b;
sum *= 2;
return sum;
}
Func<int, int, int> calc2 = Calc;
十、总结
泛型委托三步决策法
我需要一个委托 →
1. 有返回值吗?
├─ 无 → 用 Action 系列
│ Action (无参数)
│ Action<T> (1个参数)
│ Action<T1,T2> (2个参数)
│ ...
│
├─ 返回 bool → 用 Predicate<T> 或 Func<T, bool>
│
└─ 返回其他 → 用 Func 系列
Func<TResult> (无参数)
Func<T, TResult> (1个参数)
Func<T1, T2, TResult> (2个参数)
...
2. 参数超过 16 个?
└─ 用自定义泛型委托
3. 需要约束?
└─ 自定义泛型委托 + where 约束
速查表
| 你想要的 | 用什么 |
|---|---|
| 执行一个操作,不要返回值 | Action / Action<T> |
| 得到一个值 | Func<T> |
| 转换一个值 | Func<T, TResult> |
| 判断一个值 | Predicate<T> 或 Func<T, bool> |
| 两个参数操作 | Func<T1, T2, TResult> 或 Action<T1, T2> |
| 需要约束类型参数 | 自定义泛型委托 +where |
记忆口诀
委托不加泛型累,每种类型都得写
加上泛型变灵活,一套模板全解决
Action 干活不要回报
Func 办事必有结果(最后一个参数是返回值)
Predicate 专门做判断(返回 bool)
内置三兄弟全搞定,自己定义已没必要
一句话总结:泛型委托就是让委托支持类型参数,一个定义适配所有类型。C# 内置了三大家族——
Action(干活不要回报)、Func(办事有结果)、Predicate(专门做判断),99% 的场景不需要自己定义泛型委托,直接用它们就对了。