一、什么是委托
委托就相当于是一个可以存放方法的箱子,我们可以通过这个"箱子"调用里面的方法,比如我下面的代码:
cs
体验AI代码助手
代码解读
复制代码
// 0.背景:委托定义与方法定义 // 创建了一个名为MyDelegate的委托,这个委托里面装的函数必须有两个int类型的参数,一个int类型的返回值! public delegate int MyDelegate(int a,int b); // 创建了一个Add方法,需要两个int参数,一个int返回值,与上面委托要求的相符! int Add(int a, int b) { return a + b; } // 1.实例化类, MyClass myClass = new MyClass(); // 2.实例化委托并且将方法装载到委托中 MyDelegate myDelegate1 = new MyDelegate(myClass.Add); // 3.调用委托,即调用委托这个"箱子里的方法Add(1,2)" int result = myDelegate1.Invoke(1,2);
这里我演示了如何简单的使用委托,那么有的同学看到可能会疑惑:
为什么要使用委托?直接调用Add(1,2)函数不就好了吗?为什么要写这么麻烦?
这个问题困扰着每一个委托初学者,其实这个问题非常好,但是现在我们先往下学,在本文,我会告诉大家这个问题的答案。
1.1 创建委托的语法
对我来说,我知道创建委托的方法有以下几个
① 显示创建委托
这是 C# 1.0 引入的最基础方式,通过 new 关键字显式创建委托实例,并将目标方法作为构造函数参数。
cs
体验AI代码助手
代码解读
复制代码
委托类型 委托实例 = new 委托类型(目标方法);
cs
体验AI代码助手
代码解读
复制代码
// 1. 定义委托类型 public delegate void MyDelegate(string message); // 2. 目标方法(静态或实例方法均可) public static void ShowMessage(string msg) { Console.WriteLine(msg); } // 3. 显式实例化委托 MyDelegate del = new MyDelegate(ShowMessage); del("Hello, 显式实例化委托!"); // 调用委托
② 方法组转换(简化方式)
C# 2.0 引入了方法组转换 ,允许直接将方法名赋值给委托实例(编译器自动完成 new 操作)。这是最常用的方式之一。
cs
体验AI代码助手
代码解读
复制代码
委托类型 委托实例 = 目标方法;
cs
体验AI代码助手
代码解读
复制代码
// 复用上面的 MyDelegate 委托和 ShowMessage 方法 MyDelegate del = ShowMessage; // 方法组转换 del("Hello, 方法组转换!");
说明:
- 目标方法可以是静态方法 (直接用类名调用)或实例方法(需先创建类实例)。
- 方法签名(参数类型、返回值类型)必须与委托完全匹配。
③ 匿名方法(临时方法)
C# 2.0 引入了匿名方法,允许在创建委托时直接定义方法体,无需单独声明命名方法。适合临时使用的简单逻辑。
语法:
cs
体验AI代码助手
代码解读
复制代码
委托类型 委托实例 = delegate(参数列表) { // 方法体 };
示例:
cs
体验AI代码助手
代码解读
复制代码
public delegate void MyDelegate(string message); MyDelegate del = delegate(string msg) { Console.WriteLine("匿名方法:" + msg); }; del("Hello, 匿名方法!");
说明:
- 匿名方法可以访问外部变量(闭包),但需注意变量生命周期。
- 若委托无参数,
delegate()中的参数列表可省略(仅匿名方法支持)。
④ Lambda 表达式(最简洁方式
C# 3.0 引入了Lambda 表达式,是匿名方法的更简洁语法,广泛用于 LINQ、事件处理等场景。
语法:
- 无参数:
() => { 方法体 }或() => 表达式(单行可省略大括号) - 有参数:
(参数1, 参数2) => { 方法体 }或参数 => 表达式(单参数可省略括号)
示例:
cs
体验AI代码助手
代码解读
复制代码
// 1. 无参数委托 public delegate void NoParamDelegate(); NoParamDelegate del1 = () => Console.WriteLine("无参数 Lambda!"); del1(); // 2. 有参数委托(单行简化) MyDelegate del2 = msg => Console.WriteLine("Lambda:" + msg); del2("Hello, Lambda!"); // 3. 多行 Lambda(需大括号和 return) public delegate int CalcDelegate(int a, int b); CalcDelegate del3 = (a, b) => { int sum = a + b; return sum; }; int result = del3(10, 20); // 结果:30
说明:
- Lambda 表达式同样支持闭包,访问外部变量。
- 编译器会根据委托签名自动推断参数类型(若委托是泛型委托,如
Func/Action,则参数类型需显式或由上下文推断)。
1.2 调用委托的语法
① 基础调用:Invoke()方法
Invoke() 是委托类型的实例方法,用于显式调用委托所引用的方法。这是最明确的调用方式,适合强调 "通过委托调用" 的场景。
ini
体验AI代码助手
代码解读
复制代码
委托实例.Invoke(参数列表);
cs
体验AI代码助手
代码解读
复制代码
public delegate void MyDelegate(string message); public static void ShowMessage(string msg) { Console.WriteLine(msg); } // 创建委托实例 MyDelegate del = ShowMessage; // 使用 Invoke() 调用 del.Invoke("Hello via Invoke!"); // 输出:Hello via Invoke!
2. 简化调用:直接调用(编译器语法糖)
C# 允许将委托实例直接作为方法名 调用,编译器会自动将其转换为 Invoke() 调用。这是最常用的简化语法。
语法:
cs
体验AI代码助手
代码解读
复制代码
委托实例(参数列表);
示例:
cs
体验AI代码助手
代码解读
复制代码
// 复用上面的委托和方法 MyDelegate del = ShowMessage; // 直接调用(等价于 del.Invoke(...)) del("Hello via direct call!"); // 输出:Hello via direct call!
1.2 委托的原理
谈到这里,我们就可以好好聊聊之前的问题了,委托到底是什么?
① 先想清楚:为什么需要委托?
假设你是一个房东 ,想把房子租出去,但你不想自己找租客、签合同、收房租(太麻烦了)。这时候你会找一个房产中介 ,把 "租房" 这件事委托给中介去做。
在编程里,方法 就像 "租房" 这件事,而委托 就是 "房产中介"------ 它帮你管理方法的调用,让你不用直接和方法打交道。
② 委托的本质:方法的 "代言人"
委托的核心作用是:把 "方法" 变成一个可以 "传递、存储、调用" 的 "对象" 。
用更通俗的话讲:
- 委托是一个 "方法的代言人":它知道 "要调用哪个方法""怎么调用这个方法"。
- 委托是一个 "类型安全的中介":它只接受 "符合要求的方法"(比如参数、返回值要匹配,就像中介只接受 "合法的房东")。
- 委托是一个 "可以同时干多件事的中介":它可以同时帮你调用多个方法(比如中介同时帮你找租客、修房子、收房租)。
③ 用 "租房" 例子拆解委托的核心功能
我们把 "租房" 的流程对应到委托的使用上,你就能完全理解了:
第一步:定义 "委托类型"(相当于 "中介的服务范围")
房东找中介前,得先明确 "中介要做什么"(比如 "帮我找租客,签合同,收房租")。在编程里,这就是定义委托类型,规定 "这个委托能代言什么样的方法"(比如参数类型、返回值类型)。
cs
体验AI代码助手
代码解读
复制代码
// 定义一个委托类型:"租房中介",负责"处理租房相关的事" // (参数是租客名字,返回值是租金) public delegate int RentHouseDelegate(string tenantName);
第二步:准备 "要委托的方法"(相当于 "房东的具体需求")
房东需要明确 "具体要做哪些事",比如:
- 找租客:
FindTenant方法 - 签合同:
SignContract方法 - 收房租:
CollectRent方法
cs
体验AI代码助手
代码解读
复制代码
// 房东的具体方法(要委托给中介的事) public class Landlord { // 找租客:返回押金(比如1000元) public int FindTenant(string tenant) { Console.WriteLine($"帮房东找租客:{tenant}"); return 1000; // 押金 } // 签合同:返回第一个月租金(比如3000元) public int SignContract(string tenant) { Console.WriteLine($"帮房东和租客 {tenant} 签合同"); return 3000; // 首月租金 } }
第三步:创建 "委托实例"(相当于 "雇佣中介")
房东找到中介,把 "找租客""签合同" 这些事委托给中介(绑定方法到委托实例)。
cs
体验AI代码助手
代码解读
复制代码
// 创建房东实例(具体的房东) Landlord landlord = new Landlord(); // 雇佣中介:把"找租客"方法委托给中介 RentHouseDelegate agent = landlord.FindTenant; // 还可以让中介同时干多件事:再委托"签合同"方法 agent += landlord.SignContract;
4. 第四步:调用 "委托"(相当于 "让中介干活")
现在,房东只需要告诉中介 "租客名字" ,中介就会自动执行所有委托的方法(找租客 → 签合同)。
cs
体验AI代码助手
代码解读
复制代码
// 让中介干活:处理租客"张三"的租房事宜 int totalMoney = agent("张三"); Console.WriteLine($"总共收到钱:{totalMoney}元");
5. 执行结果(中介干的活)
arduino
体验AI代码助手
代码解读
复制代码
帮房东找租客:张三 帮房东和租客 张三 签合同 总共收到钱:3000元 // 注意:多播委托只返回最后一个方法的结果(签合同的3000元)
④ 委托的本质总结(一句话)
委托就是一个 "方法的代言人",它帮你管理方法的调用,让方法可以像变量一样 "传递、存储、批量调用" 。
⑤ 为什么不用直接调用方法?
你可能会问:"我直接调用 landlord.FindTenant("张三") 不行吗?为什么要找中介?"
这就涉及到委托的核心价值:解决 "方法不能直接传递" 的问题。
比如:
- 你写了一个 "按钮" 控件,点击按钮时要执行 "用户自定义的方法",但你不知道用户会写什么方法(可能是 "打开文件",也可能是 "保存数据")。这时候,你就需要一个委托来 "接收" 用户的方法,点击时再调用。
- 你写了一个 "排序" 方法,需要用户提供 "比较规则"(比如按年龄排序还是按姓名排序),但你不知道用户的比较规则是什么。这时候,你就需要一个委托来 "接收" 用户的比较方法,排序时再调用。
⑥ 再举一个更简单的例子:按钮点击事件
这是委托最常见的应用场景,你肯定见过:
cs
体验AI代码助手
代码解读
复制代码
// 1. 系统定义了一个委托类型:"点击事件的代言人" public delegate void EventHandler(object sender, EventArgs e); // 2. 按钮有一个"点击事件"(本质是委托实例) button.Click += new EventHandler(OnButtonClick); // 3. 你写的"点击后要执行的方法" private void OnButtonClick(object sender, EventArgs e) { Console.WriteLine("按钮被点击了!"); }
这里的 EventHandler 就是委托,它帮系统 "记住" 了你要执行的 OnButtonClick 方法,当按钮被点击时,系统就调用这个委托,从而执行你的方法。
核心结论
委托的本质其实很简单:它是一个 "方法的代言人",让方法可以像变量一样被传递、存储和调用。
就像你找中介租房,不用自己跑断腿;编程里用委托,不用直接调用复杂的方法,而是让委托帮你搞定。
二、系统内置的两个委托
在 .NET 框架中,Action 和 Func 是最常用的两个内置泛型委托 ,它们由 System 命名空间提供,无需手动定义,可直接用于几乎所有委托场景。这两个委托的设计目标是减少自定义委托的数量,提高代码复用性和可读性。
Action 委托:无返回值的方法引用
Action 委托用于封装无返回值 的方法(即 void 返回类型)。它支持 0 到 16 个输入参数,通过泛型参数指定参数类型。
1. 基本定义
- 无参数 :
Action用于封装 "无参数、无返回值" 的方法。 - 带参数 :
Action<T1>、Action<T1, T2>、...、Action<T1, T2, ..., T16>用于封装 "带 1~16 个参数、无返回值" 的方法,泛型参数T1~T16表示参数类型。
2. 示例:使用 Action 委托
cs
体验AI代码助手
代码解读
复制代码
using System; class Program { static void Main() { // 1. 无参数 Action:封装无参数方法 Action printHello = () => Console.WriteLine("Hello, Action!"); printHello(); // 输出:Hello, Action! // 2. 带 1 个参数 Action:封装带 1 个参数的方法 Action<string> printMessage = msg => Console.WriteLine($"Message: {msg}"); printMessage("你好,世界!"); // 输出:Message: 你好,世界! // 3. 带 2 个参数 Action:封装带 2 个参数的方法 Action<string, int> printInfo = (name, age) => Console.WriteLine($"{name} 今年 {age} 岁"); printInfo("张三", 25); // 输出:张三 今年 25 岁 } }
Func 委托:有返回值的方法引用
Func 委托用于封装有返回值 的方法。它支持 0 到 16 个输入参数,最后一个泛型参数始终表示返回值类型。
1. 基本定义
- 无输入参数,仅返回值 :
Func<TResult>用于封装 "无参数、返回值类型为TResult" 的方法。 - 带输入参数和返回值 :
Func<T1, TResult>、Func<T1, T2, TResult>、...、Func<T1, T2, ..., T16, TResult>用于封装 "带 1~16 个输入参数、返回值类型为TResult" 的方法,前N个泛型参数表示输入参数类型,最后一个表示返回值类型。
2. 示例:使用 Func 委托
cs
体验AI代码助手
代码解读
复制代码
using System; class Program { static void Main() { // 1. 无输入参数,返回 int:封装无参数、返回 int 的方法 Func<int> getRandomNumber = () => new Random().Next(1, 100); int randomNum = getRandomNumber(); Console.WriteLine($"随机数:{randomNum}"); // 输出:随机数:[1-99的随机数] // 2. 带 1 个输入参数,返回 string:封装带 1 个参数、返回 string 的方法 Func<int, string> numberToString = num => $"数字是:{num}"; string result1 = numberToString(123); Console.WriteLine(result1); // 输出:数字是:123 // 3. 带 2 个输入参数,返回 int:封装带 2 个参数、返回 int 的方法 Func<int, int, int> add = (a, b) => a + b; int sum = add(10, 20); Console.WriteLine($"和:{sum}"); // 输出:和:30 } }
三、Action 与 Func 的核心区别
| 特性 | Action 委托 |
Func 委托 |
|---|---|---|
| 返回值 | 无返回值(void) |
必须有返回值(最后一个泛型参数为返回值类型) |
| 泛型参数 | 仅表示输入参数(0~16 个) | 前 N 个为输入参数,最后一个为返回值类型(0~16 个输入参数) |
| 适用场景 | 用于 "执行操作"(如打印、修改状态) | 用于 "计算结果"(如求和、转换类型) |
补充:Predicate 委托(特殊的 Func)
除了 Action 和 Func,.NET 还提供了 Predicate<T> 委托,它是 Func<T, bool> 的 "语法糖",专门用于返回布尔值的条件判断(如 "判断元素是否满足条件")。
基本定义
Predicate<T>:封装 "输入参数为T、返回值为bool" 的方法,等价于Func<T, bool>。
示例:使用 Predicate 委托
cs
体验AI代码助手
代码解读
复制代码
using System; class Program { static void Main() { // 检查数字是否为偶数(等价于 Func<int, bool>) Predicate<int> isEven = num => num % 2 == 0; Console.WriteLine(isEven(4)); // 输出:True Console.WriteLine(isEven(5)); // 输出:False } }
学完了这些,我们基本上算委托入门了。