C#里面的委托详解
一.介绍
在 C# 中,委托(Delegate)是一种引用类型,它本质上是一个 “方法的容器”,能存储对符合特定签名(返回值类型 + 参数列表)方法的引用,实现 “将方法作为参数传递” 或 “动态调用方法”。
1. 委托的核心作用
- 解耦代码:调用者无需知道具体调用的是哪个方法,只需通过委托间接调用。
- 支持回调:允许将方法作为参数传给另一个方法(如常用的
List<T>.ForEach、事件处理)。 - 动态绑定:运行时可修改委托引用的方法,灵活切换逻辑。
2. 委托的使用步骤(以自定义委托为例)
步骤 1:定义委托(声明 “方法签名规则”)
用delegate关键字声明,格式为:delegate 返回值类型 委托名(参数列表);示例:声明一个 “接收 int 参数、返回 string” 的委托
csharp
// 定义委托:规定方法必须是“int参数,string返回值”
public delegate string MyDelegate(int num);
步骤 2:准备符合签名的方法
方法的返回值、参数数量 / 类型必须与委托完全匹配(可是静态方法或实例方法):
csharp
// 静态方法(符合MyDelegate签名)
public static string GetNumberStr(int num)
{
return $"数字是:{num}";
}
// 实例方法(符合MyDelegate签名)
public class MyClass
{
public string GetDoubleNumStr(int num)
{
return $"数字的2倍是:{num * 2}";
}
}
步骤 3:创建委托实例并绑定方法
通过 “new 委托名 (方法名)” 创建实例,或直接用 “方法名” 简化赋值:
csharp
// 1. 绑定静态方法
MyDelegate del1 = GetNumberStr;
// 2. 绑定实例方法(需先创建类实例)
MyClass obj = new MyClass();
MyDelegate del2 = obj.GetDoubleNumStr;
步骤 4:调用委托(间接调用绑定的方法)
通过 “委托实例 (参数)” 调用,效果等同于直接调用绑定的方法:
csharp
// 调用del1,实际执行GetNumberStr(10)
string result1 = del1(10); // 结果:"数字是:10"
// 调用del2,实际执行obj.GetDoubleNumStr(10)
string result2 = del2(10); // 结果:"数字的2倍是:20"
3. 常用的系统内置委托(无需自定义)
C# 提供了 3 个通用委托,覆盖绝大多数场景,避免重复定义:
-
Action:无返回值的委托,支持 0~16 个参数(如Action、Action<int>、Action<string, int>)。示例:调用无返回值、带 1 个 string 参数的方法csharp
Action<string> printAction = Console.WriteLine; printAction("Hello Delegate"); // 等同于 Console.WriteLine("Hello Delegate") -
Func:有返回值的委托,支持 1~17 个参数(最后一个泛型是返回值类型,如Func<int>、Func<int, string>、Func<string, int, bool>)。示例:调用返回 int、带 1 个 int 参数的方法csharp
Func<int, int> squareFunc = num => num * num; // lambda表达式(简化方法) int result = squareFunc(5); // 结果:25 -
Predicate<T>:返回bool的委托,仅支持 1 个参数(常用于 “条件判断”,如List<T>.Find)。示例:筛选列表中大于 10 的数字csharp
List<int> nums = new List<int> {5, 12, 8, 15}; Predicate<int> isGreaterThan10 = num => num > 10; int found = nums.Find(isGreaterThan10); // 结果:12
4. 委托的关键特性:多播(Multicast)
委托支持通过+=添加多个方法引用,-=移除方法引用,调用时会按添加顺序依次执行所有绑定的方法(仅void返回值的委托适合多播,有返回值的委托仅返回最后一个方法的结果)。
示例:
csharp
// 定义2个无返回值的方法
void MethodA() => Console.WriteLine("执行MethodA");
void MethodB() => Console.WriteLine("执行MethodB");
// 创建委托并添加2个方法
Action multiDel = MethodA;
multiDel += MethodB;
// 调用委托:依次执行MethodA、MethodB
multiDel(); // 输出:执行MethodA → 执行MethodB
// 移除MethodA,仅保留MethodB
multiDel -= MethodA;
multiDel(); // 输出:执行MethodB
总结
委托的核心是 “对方法的抽象”,它让方法具备了 “像变量一样传递、组合” 的能力,是 C# 中事件(Event)、LINQ、异步编程(如async/await)的底层基础。实际开发中,优先使用Action/Func等内置委托,减少自定义委托的代码量。
二.生活例子
简单说,委托就是 “方法的‘变量’”—— 变量能存数字 / 字符串,委托能存方法;变量能传参、赋值,委托也能把方法当参数传、动态换存的方法。
1. 先搞懂:为什么需要委托?(解决什么问题)
举个生活例子:你去奶茶店点单,有 3 种需求:
- 要 “热饮 + 少糖”
- 要 “冰饮 + 多糖”
- 要 “常温 + 无糖”
如果不用委托:你得写 3 个方法(MakeHotLessSugar()、MakeColdMoreSugar()、MakeNormalNoSugar()),每次点单都要调用不同方法,代码重复且难维护。
如果用委托:只需要 1 个 “做奶茶” 的核心方法,把 “调温度、调糖度” 这些 “小步骤” 用委托传进去 —— 不管用户要哪种,都调用同一个核心方法,只是传不同的 “小步骤”(方法)。
2. 委托的实际用途:3 个最常见场景
你平时写代码或用框架时,其实早就接触过委托,只是没察觉:
场景 1:“遍历集合时,想做自定义操作”(如List.ForEach)
比如你有个学生列表,想给每个学生 “加分”,或 “打印名字”,或 “标记为优秀”—— 这些都是 “自定义操作”。List<T>.ForEach的参数就是Action<T>委托(存方法的 “变量”),你把要做的操作(方法)传进去,它就会帮你遍历执行。
csharp
List<string> students = new List<string> { "张三", "李四" };
// 需求1:打印每个学生(把“打印方法”传给ForEach)
students.ForEach(name => Console.WriteLine(name));
// 需求2:给每个学生名加“同学”(把“加后缀方法”传给ForEach)
students.ForEach(name => Console.WriteLine(name + "同学"));
如果没有委托,List得内置 “打印”“加后缀”“加分” 等无数方法,根本不可能 —— 委托让List只负责 “遍历”,具体做什么交给你传的方法。
场景 2:“事件处理”(如按钮点击、定时器触发)
你点窗口的 “确定” 按钮,程序要执行 “保存数据”;点 “取消” 按钮,要执行 “关闭窗口”—— 这些 “点击后要做的事”,就是通过委托(事件本质是特殊委托)绑定的。按钮本身只负责 “检测到点击”,具体执行什么逻辑(保存 / 关闭),是你把对应的方法 “绑” 到按钮的Click事件(委托)上。
csharp
// 按钮点击事件:参数是EventHandler委托(存“点击后执行的方法”)
button1.Click += Button1_Click;
// 点击后要执行的方法(符合EventHandler委托的签名)
void Button1_Click(object sender, EventArgs e)
{
MessageBox.Show("按钮被点击了!");
}
如果没有委托,按钮控件得提前知道 “点击后要保存数据”—— 但控件根本不知道你的业务逻辑,委托让 “控件触发事件” 和 “你处理逻辑” 解耦了。
场景 3:“回调函数”(如异步操作完成后通知)
比如你调用一个 “下载文件” 的方法,下载需要 10 秒,你不想等 10 秒再做别的 —— 这时可以传一个 “回调方法”(用委托存),告诉它:“下载完了就调用这个方法通知我”。
csharp
// 下载方法:参数1是URL,参数2是“下载完要执行的回调方法”(Func委托)
void DownloadFile(string url, Func<string, void> callback)
{
// 模拟下载(耗时操作)
Thread.Sleep(10000);
string result = "下载完成:" + url;
// 下载完调用回调方法,通知你结果
callback(result);
}
// 调用:传“下载地址”和“回调方法”(下载完打印结果)
DownloadFile("https://xxx.jpg", (msg) => Console.WriteLine(msg));
如果没有委托,你得不停 “问” 下载方法 “完了没”(轮询),浪费资源 —— 委托让下载方法 “主动通知” 你,更高效。
3. 最后总结:委托的核心价值
- 不用委托:方法 A 要调用方法 B,必须在 A 里写死
B()—— 如果想换调用C(),就得改 A 的代码。 - 用委托:方法 A 里只调用 “委托变量”(比如
myDelegate()),想调用 B 就把 B 存进委托,想调用 C 就把 C 存进去 —— 不用改 A 的代码,灵活且解耦。
一句话:委托让 “方法” 能像 “变量” 一样灵活传递、动态切换,解决了 “方法之间硬耦合” 的问题。
三.委托在实际开发中最常用的例子
在实际开发中,委托最常用的场景集中在 “解耦代码” 和 “传递逻辑” 上,以下是 3 个最高频的例子,覆盖日常开发 80% 以上的委托使用场景:
1. 集合遍历(配合 Action 委托)
最基础也最常用的场景 —— 用 List.ForEach 遍历集合时,通过 lambda 传递 “遍历逻辑”,本质是通过 Action<T> 委托接收这个逻辑。不用委托的痛点:如果每次遍历都写 for 循环,代码重复;如果单独写一个打印 / 处理方法,又要额外定义方法,不够灵活。用委托的优势:直接把 “要做什么” 的逻辑(比如打印、修改值)通过 lambda 传给委托,代码简洁且逻辑内聚。
csharp
List<string> students = new List<string> { "张三", "李四", "王五" };
// ForEach 的参数是 Action<string> 委托,lambda 是委托的简化实现
students.ForEach(name =>
{
// 这里的“打印姓名+序号”就是传给委托的逻辑
Console.WriteLine($"学生:{name}");
});
2. 事件处理(配合自定义委托 /EventHandler)
UI 开发(如 WinForm、WPF、Blazor)或业务逻辑中,“响应事件” 是委托的核心场景 —— 比如按钮点击、数据变化时,通过委托(事件本质是 “特殊的委托”)传递 “事件触发后要执行的逻辑”。不用委托的痛点:如果按钮点击逻辑写死在按钮内部,按钮和业务逻辑强耦合(改逻辑要动按钮代码);用委托后,按钮只负责 “触发事件”,具体执行什么逻辑由外部决定。
csharp
// 1. 定义事件(本质是基于委托的“特殊容器”,这里用系统自带的 EventHandler 委托)
public event EventHandler ButtonClicked;
// 2. 按钮内部:点击时触发事件(调用委托)
private void btnSubmit_Click(object sender, EventArgs e)
{
// 委托不为空时,执行“外部传给它的逻辑”
ButtonClicked?.Invoke(this, EventArgs.Empty);
}
// 3. 外部使用:给事件绑定逻辑(通过 lambda 传递给委托)
// 这里的逻辑和按钮本身解耦,按钮不用知道“点击后要保存数据”
btnSubmit.ButtonClicked += (s, e) =>
{
SaveData(); // 点击后要执行的业务逻辑(比如保存表单)
ShowSuccessMessage(); // 额外逻辑(比如弹窗提示)
};
3. 回调函数(配合 Func 委托处理 “有返回值的逻辑”)
当你需要 “调用一个方法时,让它回头执行你传递的逻辑,并返回结果”(即回调),比如处理异步结果、自定义计算规则时,常用 Func<T, TResult> 委托(带返回值的委托)。不用委托的痛点:如果一个方法要支持多种计算逻辑(比如 “求和”“求平均值”),不用委托就要写多个方法(CalculateSum/CalculateAvg);用委托后,一个方法就能通过传递不同逻辑实现多种功能。
csharp
// 定义一个“通用计算方法”:接收“数据列表”和“计算逻辑(委托)”,返回计算结果
public static int Calculate(List<int> numbers, Func<List<int>, int> calculateLogic)
{
// 委托接收外部传递的“计算逻辑”,直接执行并返回结果
return calculateLogic(numbers);
}
// 实际使用:传递不同逻辑,实现不同计算
List<int> scores = new List<int> { 80, 90, 75, 95 };
// 1. 计算总分:给委托传递“求和逻辑”
int total = Calculate(scores, list => list.Sum());
Console.WriteLine(total); // 输出 340
// 2. 计算最高分:给委托传递“求最大值逻辑”
int max = Calculate(scores, list => list.Max());
Console.WriteLine(max); // 输出 95
// 3. 计算平均分(自定义逻辑):
int avg = Calculate(scores, list => list.Sum() / list.Count);
Console.WriteLine(avg); // 输出 85
总结:委托的核心价值
这 3 个例子本质都是通过委托实现 “逻辑和方法解耦”:
- 方法(如
ForEach、事件、Calculate)只负责 “框架性工作”(遍历、触发事件、接收数据); - 具体 “要做什么” 的逻辑(打印、保存数据、计算规则)通过委托传递,不用修改原方法就能灵活切换逻辑 —— 这也是委托在实际开发中最核心的作用。
更多推荐



所有评论(0)