news 2026/5/10 10:04:47

.NET进阶——深入理解委托(1)委托入门

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.NET进阶——深入理解委托(1)委托入门

一、什么是委托

委托就相当于是一个可以存放方法的箱子,我们可以通过这个“箱子”调用里面的方法,比如我下面的代码:

// 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 框架中,ActionFunc是最常用的两个内置泛型委托,它们由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}}

三、ActionFunc的核心区别

特性Action委托Func委托
返回值无返回值(void必须有返回值(最后一个泛型参数为返回值类型)
泛型参数仅表示输入参数(0~16 个)前 N 个为输入参数,最后一个为返回值类型(0~16 个输入参数)
适用场景用于 “执行操作”(如打印、修改状态)用于 “计算结果”(如求和、转换类型)

补充:Predicate委托(特殊的Func

除了ActionFunc,.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}}

学完了这些,我们基本上算委托入门了,接下来我会带着大家做几个委托实战来巩固这些知识点。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/10 1:13:23

3步构建专业数据仪表板:marimo让商业智能平民化

3步构建专业数据仪表板&#xff1a;marimo让商业智能平民化 【免费下载链接】marimo A next-generation Python notebook: explore data, build tools, deploy apps! 项目地址: https://gitcode.com/GitHub_Trending/ma/marimo 还在为复杂的数据可视化工具而头疼吗&…

作者头像 李华
网站建设 2026/5/2 17:31:15

AC6966B蓝牙音箱电路设计完整指南:从原理图到产品生产

AC6966B蓝牙音箱电路设计完整指南&#xff1a;从原理图到产品生产 【免费下载链接】AC6966B蓝牙音箱标准原理图下载分享 AC6966B蓝牙音箱标准原理图下载 项目地址: https://gitcode.com/Open-source-documentation-tutorial/d58d7 AC6966B是杰理公司推出的一款高性能蓝牙…

作者头像 李华
网站建设 2026/5/9 8:41:54

Cesium中实现流光线

概要 Cesium中实现流光线&#xff0c;本质上是在特定的时间改变颜色等属性即可。可以通过MaterialProperty实现&#xff0c;但是它是用在Entity上的&#xff0c;如果要用Primitvie上就得通过自定义的Material实现。要想Material实现会动的效果&#xff0c;需要借助Cesium的一些…

作者头像 李华
网站建设 2026/5/6 18:29:22

Docker部署边缘Agent常见问题解析(避坑指南+性能调优)

第一章&#xff1a;边缘 Agent 的 Docker 轻量级部署概述在物联网与边缘计算快速发展的背景下&#xff0c;边缘 Agent 作为连接终端设备与云端服务的核心组件&#xff0c;其部署效率与资源占用成为关键考量因素。Docker 容器化技术凭借轻量、可移植和隔离性强的优势&#xff0c…

作者头像 李华
网站建设 2026/5/10 4:59:43

轻量文件加密软件推荐:2025 年 5 款不占内存软件实测

在数据安全愈发重要的当下&#xff0c;轻量不占内存的文件加密工具成为刚需。2025 年实测 5 款优质软件&#xff0c;它们兼顾加密强度与运行效率&#xff0c;无需复杂配置即可快速上手&#xff0c;适配个人办公与小型团队协作场景&#xff0c;帮你轻松守护文件隐私&#xff0c;…

作者头像 李华
网站建设 2026/5/9 22:38:24

VSCode中实现量子电路仿真的完整路径(量子开发者的隐藏武器)

第一章&#xff1a;VSCode中实现量子电路仿真的完整路径&#xff08;量子开发者的隐藏武器&#xff09;在现代量子计算开发中&#xff0c;VSCode 已成为开发者不可或缺的集成环境。结合强大的扩展生态与开源量子框架&#xff0c;它为构建、仿真和调试量子电路提供了前所未有的便…

作者头像 李华