一、什么是委托
委托就相当于是一个可以存放方法的箱子,我们可以通过这个“箱子”调用里面的方法,比如我下面的代码:
// 0.背景:委托定义与方法定义// 创建了一个名为MyDelegate的委托,这个委托里面装的函数必须有两个int类型的参数,一个int类型的返回值!publicdelegateintMyDelegate(inta,intb);// 创建了一个Add方法,需要两个int参数,一个int返回值,与上面委托要求的相符!intAdd(inta,intb){returna+b;}// 1.实例化类,MyClassmyClass=newMyClass();// 2.实例化委托并且将方法装载到委托中MyDelegatemyDelegate1=newMyDelegate(myClass.Add);// 3.调用委托,即调用委托这个“箱子里的方法Add(1,2)”intresult=myDelegate1.Invoke(1,2);这里我演示了如何简单的使用委托,那么有的同学看到可能会疑惑:
为什么要使用委托?直接调用Add(1,2)函数不就好了吗?为什么要写这么麻烦?
这个问题困扰着每一个委托初学者,其实这个问题非常好,但是现在我们先往下学,在本文,我会告诉大家这个问题的答案。
1.1 创建委托的语法
创建委托的方法有以下几个
① 显示创建委托
这是 C# 1.0 引入的最基础方式,通过new关键字显式创建委托实例,并将目标方法作为构造函数参数。
委托类型 委托实例=new委托类型(目标方法);// 1. 定义委托类型publicdelegatevoidMyDelegate(stringmessage);// 2. 目标方法(静态或实例方法均可)publicstaticvoidShowMessage(stringmsg){Console.WriteLine(msg);}// 3. 显式实例化委托MyDelegatedel=newMyDelegate(ShowMessage);del("Hello, 显式实例化委托!");// 调用委托② 方法组转换(简化方式)
C# 2.0 引入了方法组转换,允许直接将方法名赋值给委托实例(编译器自动完成new操作)。这是最常用的方式之一。
委托类型 委托实例=目标方法;// 复用上面的 MyDelegate 委托和 ShowMessage 方法MyDelegatedel=ShowMessage;// 方法组转换del("Hello, 方法组转换!");说明:
- 目标方法可以是静态方法(直接用类名调用)或实例方法(需先创建类实例)。
- 方法签名(参数类型、返回值类型)必须与委托完全匹配。
③ 匿名方法(临时方法)
C# 2.0 引入了匿名方法,允许在创建委托时直接定义方法体,无需单独声明命名方法。适合临时使用的简单逻辑。
语法:
委托类型 委托实例=delegate(参数列表){// 方法体};示例:
publicdelegatevoidMyDelegate(stringmessage);MyDelegatedel=delegate(stringmsg){Console.WriteLine("匿名方法:"+msg);};del("Hello, 匿名方法!");说明:
- 匿名方法可以访问外部变量(闭包),但需注意变量生命周期。
- 若委托无参数,
delegate()中的参数列表可省略(仅匿名方法支持)。
④ Lambda 表达式(最简洁方式
C# 3.0 引入了Lambda 表达式,是匿名方法的更简洁语法,广泛用于 LINQ、事件处理等场景。
语法:
- 无参数:
() => { 方法体 }或() => 表达式(单行可省略大括号) - 有参数:
(参数1, 参数2) => { 方法体 }或参数 => 表达式(单参数可省略括号)
示例:
// 1. 无参数委托publicdelegatevoidNoParamDelegate();NoParamDelegatedel1=()=>Console.WriteLine("无参数 Lambda!");del1();// 2. 有参数委托(单行简化)MyDelegatedel2=msg=>Console.WriteLine("Lambda:"+msg);del2("Hello, Lambda!");// 3. 多行 Lambda(需大括号和 return)publicdelegateintCalcDelegate(inta,intb);CalcDelegatedel3=(a,b)=>{intsum=a+b;returnsum;};intresult=del3(10,20);// 结果:30说明:
- Lambda 表达式同样支持闭包,访问外部变量。
- 编译器会根据委托签名自动推断参数类型(若委托是泛型委托,如
Func/Action,则参数类型需显式或由上下文推断)。
1.2 调用委托的语法
① 基础调用:Invoke()方法
Invoke()是委托类型的实例方法,用于显式调用委托所引用的方法。这是最明确的调用方式,适合强调 “通过委托调用” 的场景。
委托实例.Invoke(参数列表);publicdelegatevoidMyDelegate(stringmessage);publicstaticvoidShowMessage(stringmsg){Console.WriteLine(msg);}// 创建委托实例MyDelegatedel=ShowMessage;// 使用 Invoke() 调用del.Invoke("Hello via Invoke!");// 输出:Hello via Invoke!2. 简化调用:直接调用(编译器语法糖)
C# 允许将委托实例直接作为方法名调用,编译器会自动将其转换为Invoke()调用。这是最常用的简化语法。
语法:
委托实例(参数列表);示例:
// 复用上面的委托和方法MyDelegatedel=ShowMessage;// 直接调用(等价于 del.Invoke(...))del("Hello via direct call!");// 输出:Hello via direct call!1.2 委托的原理
谈到这里,我们就可以好好聊聊之前的问题了,委托到底是什么?
① 先想清楚:为什么需要委托?
假设你是一个房东,想把房子租出去,但你不想自己找租客、签合同、收房租(太麻烦了)。这时候你会找一个房产中介,把 “租房” 这件事委托给中介去做。
在编程里,方法就像 “租房” 这件事,而委托就是 “房产中介”—— 它帮你管理方法的调用,让你不用直接和方法打交道。
② 委托的本质:方法的 “代言人”
委托的核心作用是:把 “方法” 变成一个可以 “传递、存储、调用” 的 “对象”。
用更通俗的话讲:
- 委托是一个“方法的代言人”:它知道 “要调用哪个方法”“怎么调用这个方法”。
- 委托是一个“类型安全的中介”:它只接受 “符合要求的方法”(比如参数、返回值要匹配,就像中介只接受 “合法的房东”)。
- 委托是一个“可以同时干多件事的中介”:它可以同时帮你调用多个方法(比如中介同时帮你找租客、修房子、收房租)。
③ 用 “租房” 例子拆解委托的核心功能
我们把 “租房” 的流程对应到委托的使用上,你就能完全理解了:
第一步:定义 “委托类型”(相当于 “中介的服务范围”)
房东找中介前,得先明确 “中介要做什么”(比如 “帮我找租客,签合同,收房租”)。在编程里,这就是定义委托类型,规定 “这个委托能代言什么样的方法”(比如参数类型、返回值类型)。
// 定义一个委托类型:"租房中介",负责"处理租房相关的事"// (参数是租客名字,返回值是租金)publicdelegateintRentHouseDelegate(stringtenantName);第二步:准备 “要委托的方法”(相当于 “房东的具体需求”)
房东需要明确 “具体要做哪些事”,比如:
- 找租客:
FindTenant方法 - 签合同:
SignContract方法 - 收房租:
CollectRent方法
// 房东的具体方法(要委托给中介的事)publicclassLandlord{// 找租客:返回押金(比如1000元)publicintFindTenant(stringtenant){Console.WriteLine($"帮房东找租客:{tenant}");return1000;// 押金}// 签合同:返回第一个月租金(比如3000元)publicintSignContract(stringtenant){Console.WriteLine($"帮房东和租客{tenant}签合同");return3000;// 首月租金}}第三步:创建 “委托实例”(相当于 “雇佣中介”)
房东找到中介,把 “找租客”“签合同” 这些事委托给中介(绑定方法到委托实例)。
// 创建房东实例(具体的房东)Landlordlandlord=newLandlord();// 雇佣中介:把"找租客"方法委托给中介RentHouseDelegateagent=landlord.FindTenant;// 还可以让中介同时干多件事:再委托"签合同"方法agent+=landlord.SignContract;4. 第四步:调用 “委托”(相当于 “让中介干活”)
现在,房东只需要告诉中介 “租客名字”,中介就会自动执行所有委托的方法(找租客 → 签合同)。
// 让中介干活:处理租客"张三"的租房事宜inttotalMoney=agent("张三");Console.WriteLine($"总共收到钱:{totalMoney}元");5. 执行结果(中介干的活)
帮房东找租客:张三 帮房东和租客 张三 签合同 总共收到钱:3000元 // 注意:多播委托只返回最后一个方法的结果(签合同的3000元)④ 委托的本质总结(一句话)
委托就是一个 “方法的代言人”,它帮你管理方法的调用,让方法可以像变量一样 “传递、存储、批量调用”。
⑤ 为什么不用直接调用方法?
你可能会问:“我直接调用landlord.FindTenant("张三")不行吗?为什么要找中介?”
这就涉及到委托的核心价值:解决 “方法不能直接传递” 的问题。
比如:
- 你写了一个 “按钮” 控件,点击按钮时要执行 “用户自定义的方法”,但你不知道用户会写什么方法(可能是 “打开文件”,也可能是 “保存数据”)。这时候,你就需要一个委托来 “接收” 用户的方法,点击时再调用。
- 你写了一个 “排序” 方法,需要用户提供 “比较规则”(比如按年龄排序还是按姓名排序),但你不知道用户的比较规则是什么。这时候,你就需要一个委托来 “接收” 用户的比较方法,排序时再调用。
⑥ 再举一个更简单的例子:按钮点击事件
这是委托最常见的应用场景,你肯定见过:
// 1. 系统定义了一个委托类型:"点击事件的代言人"publicdelegatevoidEventHandler(objectsender,EventArgse);// 2. 按钮有一个"点击事件"(本质是委托实例)button.Click+=newEventHandler(OnButtonClick);// 3. 你写的"点击后要执行的方法"privatevoidOnButtonClick(objectsender,EventArgse){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委托
usingSystem;classProgram{staticvoidMain(){// 1. 无参数 Action:封装无参数方法ActionprintHello=()=>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委托
usingSystem;classProgram{staticvoidMain(){// 1. 无输入参数,返回 int:封装无参数、返回 int 的方法Func<int>getRandomNumber=()=>newRandom().Next(1,100);intrandomNum=getRandomNumber();Console.WriteLine($"随机数:{randomNum}");// 输出:随机数:[1-99的随机数]// 2. 带 1 个输入参数,返回 string:封装带 1 个参数、返回 string 的方法Func<int,string>numberToString=num=>$"数字是:{num}";stringresult1=numberToString(123);Console.WriteLine(result1);// 输出:数字是:123// 3. 带 2 个输入参数,返回 int:封装带 2 个参数、返回 int 的方法Func<int,int,int>add=(a,b)=>a+b;intsum=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委托
usingSystem;classProgram{staticvoidMain(){// 检查数字是否为偶数(等价于 Func<int, bool>)Predicate<int>isEven=num=>num%2==0;Console.WriteLine(isEven(4));// 输出:TrueConsole.WriteLine(isEven(5));// 输出:False}}学完了这些,我们基本上算委托入门了,接下来我会带着大家做几个委托实战来巩固这些知识点。