深入浅出话委托

简介:
小序
好久不来更新 Blog 了,一是因为工作比较忙,最重要的还是交了女朋友 :) ,太爱她了。回顾了一下前面写的文章,看来大家还是很喜欢我的灌水风格,那今天就来写一写最近被问到的最多的问题——委托与事件。并把本文献给支持我工作的兄弟姐妹们和我亲爱的女友(虽然你看不懂代码,嘻嘻)。说实话,论“深入”,就我这臭水平绝对深不到哪儿去,我尽量给大家讲明白就是了。如果内容上有什么闪失,麻烦兄弟姐妹们多拍几块砖——小弟正急着盖房子呢!
正文

一.什么是委托(Delegate

          先来点经典的解释——摘自 MSDN delegate is a type  that references a metho d . Once a delegate is assigned a method, it behaves exactly like that metho d . The delegate method can be used like any other method, with parameters and a return value.
我先把它译过来:委托( Delegate ,也有人称之为“代理”)是一种数据类型,这种类型(的实例)引用着一个方法。一旦为一个委托分配(你可以理解为“挂接”)上一个方法,那么它的行为将与这个方法一致(挂接上就是为了调用这个方法,不一致我还挂个什么劲儿……原来老外也会说车轱辘话 :p )。委托可以像任何方法一样使用,比如有或者没有参数,以及返回值等等。
          我给出一段例子,演示什么是 Delegate
 
//======= 水之真谛出品 ========
//===http://blog.csdn.net/FantasiaX
//===
上善若水,润物无声 =====

using  System;
using System.Collections.Generic;
using System.Text;

namespace DelegateSample
{
    // 
地雷(类)
    class Mine
    {
        public void Blast(int enemies)
        {
            Console.WriteLine("Killed {0} Japanese soldier(s)!!!", enemies);
        }
    }

    // 
院子(类)
    class Yard
    {
        public int enemiesInYard;
        public Mine mineInYard = new Mine();
    }

    // 
绳子(委托)
    public delegate void PullingString(int enemies);

    // 
战士(类)
    class Soldier
    {
        //
一个战士可以控制三颗地雷(你可以尝试用 ArrayList ,更爽)
        public PullingString[] pullingStrings = new PullingString[3];
    }

    // 
主程序类
    class Program
    {
        static void Main(string[] args)
        {
            // 
三个院子  
            Yard yardOfZhang = new Yard();
            Yard yardOfWang = new Yard();
            Yard yardOfLi = new Yard();
            
            // 
嘎子给地雷挂弦儿
            Soldier gazi = new Soldier();
            gazi.pullingStrings[0] = new PullingString(yardOfZhang.mineInYard.Blast);
            gazi.pullingStrings[1] = new PullingString(yardOfWang.mineInYard.Blast);
            gazi.pullingStrings[2] = new PullingString(yardOfLi.mineInYard.Blast);

            // 
鬼子来啦!老张家的院儿里有五个鬼子,老王家三个,老李家十个。
            yardOfZhang.enemiesInYard = 3;
            yardOfWang.enemiesInYard = 5;
            yardOfLi.enemiesInYard = 10;

            // 
嘎子拉弦儿了
            gazi.pullingStrings[0](yardOfZhang.enemiesInYard);
            gazi.pullingStrings[1](yardOfWang.enemiesInYard);
            gazi.pullingStrings[2](yardOfLi.enemiesInYard);
        }
    }
}

通过上面这个例子,我来说一句话,看看你能不能明白:
委托就是一种机制,这种机制使得从一个类(本例中是 Soldier 类)中调用其它类(本例中是 Mine 类)中方法 变得简单而清晰。降低了类与类之间的耦合度和程序设计的复杂程度(你可以试想一下,不使用委托,而把调用 Mine Blast 方法直接写在 Soldier 类里,代码将是个什么情形……头大吧)。
          什么?不明白?没关系,我解释一下上面的代码:
1.    这段代码是用 Visual Studio 2005 写的,你可以建立一个 Console 项目,用上面的代码完全覆盖 VS 为你准备好的代码,按下 Ctrl+F5 就可以执行了。
2.    地雷类:只包含一个 public Blast 方法,这个方法要求一个 int 型的参数。这个参数决定地雷炸死几个鬼子。强调一下: Blast Mine 类中的一个方法,注意它的“签名”,是 void 型返回值外加一个 int 型参数。
3.    院子类:每个院子里埋了一颗铁西瓜,并且允许一定的小鬼子进来。
4.    绳子委托:这是本例的核心。战士想让地雷爆炸,有两种方法,一种是自己拿根香去点——前提是胆子足够大、捻儿足够长、跑的足够快——不过估计小鬼子不干;另一种是挂上一根发火弦儿(不见鬼子不挂弦儿吗 ~~ ),然后再那么一拉 ~~ 舌头就出来啦! Sorry! 是小鬼子就归西了。那么大家发现:绳子一头连着战士,另一头连着地雷的 Blast 这个方法,于是,战士拉绳子的动作与亲自去点火的效果就等价了( it behaves exactly like that metho d )。换句话说:战士把亲自点火这件事“委托”给了绳子去做——所以,绳子是一个“委托”的实例。
5.    战士类:一个战士手里可以拉着三根弦儿,我用一个数组表示的。因为 Delegate 是可以进行加 / 减运算的(后面讲“多播委托”的时候会提到),所以在调用参数一致的方法时(本例的参数是不一致的,分别是 3,5,10 ),不用数组也行。强调一下:就是这个战士类要调用地雷类中的方法——跨类的方法调用。
6.    主程序中第一块代码制造了三个院子,分别是老张、老王、老李家的院子。第二块代码是我们的主角张嘎子同学(由孙挺挺同学饰)登场……分别把手中的每根弦儿挂接在各个院子的地雷上。第三块代码——鬼子来啦!第四块代码,嘎子同学通过拉动自己手中的三根弦儿,完成了清场的任务。

二.委托的由来

          看完上面这段代码,也许你会说:嗯,很简单吗……
          是的,在有了委托之后的确非常简单。不过你可以试着不用委托来实现同样的功能——完全可以实现,但类与类之间的关系就会搅和在一起,变得骤然复杂起来。为了避免损失脑细胞,我就不给出例子了。那么也许大家会问: C/C++ 没有委托,那怎么办呢?呵呵,这个问题问的帅呆了! C/C++ 中是用函数指针和回调函数的方法来实现同样的功能的。因此,委托可以说是“ .NET 版的函数指针”,不过如果你不了解什么是函数指针那也没关系,你只需要记住委托是由 C/C++ 中的函数指针发展而来的就可以了。
          在随后的日子里,我会给出一个 C++ 编写的、用函数指针实现的程序。

三. 升级!多播委托(Broadcast Delegate

          如果说委托仅仅是函数指针的 .NET 版,那可就真没多大意思了——微软当然没那么傻!实际上,委托较之函数指针有了很多改进,其中最实用也是最酷的一点就是:委托是“多播”的,而函数指针是“单播”的。换句话说:委托是“一对多的”,即一个委托上可以挂接好几个与之签名相同的函数;而函数指针只能“一对一”,即一个函数指针只能指向一个函数。为了说明这点,我把第一个例子进行了改进——改动比较大,而且很多操作移动到了各个类的构造函数中(模拟 VS2005 为我们生成的 WinForm 程序)。顺便给大家一个小小的建议:虽然我给出的例子 Copy 过去就能执行,我还希望大家能自己动手敲一敲代码,这样会有很多意想不到的收获
//======= 水之真谛出品 ========
//===http://blog.csdn.net/FantasiaX
//===
上善若水,润物无声 =====

using  System;
using System.Collections.Generic;
using System.Text;

namespace DelegateSample
{
    // 
绳子(委托)
    public delegate void PullingString();

    // 
地雷(类)
    class Mine
    {
        private int enemiesInScope;
        public PullingString stringOfMine;
        public Mine(int enemisInYard)
        {
            enemiesInScope = enemisInYard;
            stringOfMine = new PullingString(Blast);
        }
        public void Blast()
        {
            Console.WriteLine("Killed {0} Japanese soldier(s)!!!", enemiesInScope);
        }
    }

    // 
院子(类)
    class Yard
    {
        private int enemiesInYard;
        public Mine mineInYard;
        public Yard(int enemies)
        {
            enemiesInYard = enemies;
            mineInYard = new Mine(enemiesInYard);
        }
    }

    // 
战士(类)
    class Soldier
    {
        public PullingString mainString;
    }

    // 
主程序类
    class Program
    {
        static void Main(string[] args)
        {
            // 
三个院子,老张家的院儿里有五个鬼子,老王家三个,老李家十个
            Yard yardOfZhang = new Yard(3);
            Yard yardOfWang = new Yard(1);
            Yard yardOfLi = new Yard(10);

            // 
把地雷上的绳子都接到嘎子手中的绳子上 这里用到了多播
            Soldier gazi = new Soldier();
            gazi.mainString += yardOfZhang.mineInYard.stringOfMine;
            gazi.mainString += yardOfWang.mineInYard.stringOfMine;
            gazi.mainString += yardOfLi.mineInYard.stringOfMine;

            // 
嘎子拉弦儿了
            gazi.mainString();
        }
    }
}
      OK
,我再把上面这段代码仔细解释一遍。
1.     这次的绳子委托与上一次的不一样——是无参的。因为没有参数,所以调用起来形式是“统一”的,适用于多播委托。不过这里要提醒大家一点:“统一”并不一定非得是“无参”,比如在 WinForm 程序中,用于建立控件事件的 EventHandler 委托,它是 2 个参数,分别是 object 型的 sender EventArg 型的 e ,(与之匹配的事件处理函数也都具有这样的两个参数),这也算“统一”。
2.      地雷类:这次的地雷是改进型的。最大的特点就是,本身就已经带了一根绳(绳子委托的实例)并且暴露给使用者。还有,地雷的 Blast() 方法改成了无参的——与绳子委托匹配。杀伤数目已经变成了 private ,并于构造函数处初始化。
3.      院子数:变化不大,但在构造函数处初始化了院子里的地雷实例。像这种在构造函数中初始化成员变量的方法,在程序设计中是很常用的
4.      战士类:变化很大,不再是拿着 3 根绳子,而是只拿着一根绳子。只要把地雷上的绳子接到手里的 1 根绳子上,然后一拉……呵呵,跟拉 3 根绳子的效果没有任何区别。
5.      进入主程序,你会发现:因为把很多操作交给了类自己的构造函数,所以主程序显得简洁轻快多了。第 1 块代码是生成 3 个院子。第 2 块代码是本程序的核心——多播委托——注意到委托可以使用 += 这个操作符了吗? += 操作符可以把签名相同的一组委托合并到一起(相反, -= 操作符会移除不想要的委托),这是函数指针所不具有的功能。最后一句代码,拉 1 根绳子就相当于拉 3 根地雷上的绳子。
6.      请大家仔细看 += 这段代码,并感受这一点:程序开发过程中,有两种程序员,一种是开发类库( Class Library )和基础架构( Framework )的,一种是使用类库编写目标程序的。类库开发人员开发完类库后,一般是将编译好的、包含类的 DLL 文件提供给写目标程序的人员——换句话说就是写目标程序的程序员既不能看到类的源代码、也不可能改动类的源代码。假设没有委托这种机制,那么每添加一次类与类之间的交互,就要改动一次类的代码并编译一次。解决的办法是,要么写类库和写目标程序的是同一个人(这样写不出大程序来),要么写类库的人员与写目标程序的人员进行极频繁的沟通和版本维护(出 Bug 的可能性极高)……而且有一个问题不可能避免,就是你的程序变成一锅炸酱面(类与类之间的耦合关系太密切、代码绞缠在一起的结果)。而委托的存在,使得类与类之间的交互独立于类代码之外,解决了以上的所有问题









本文转自 水之真谛 51CTO博客,原文链接:http://blog.51cto.com/liutiemeng/18756,如需转载请自行联系原作者
目录
相关文章
|
14天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
11505 126
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
3天前
|
人工智能 JSON 监控
Claude Code 源码泄露:一份价值亿元的 AI 工程公开课
我以为顶级 AI 产品的护城河是模型。读完这 51.2 万行泄露的源码,我发现自己错了。
3754 8
|
2天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
1372 3
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
13天前
|
人工智能 IDE API
2026年国内 Codex 安装教程和使用教程:GPT-5.4 完整指南
Codex已进化为AI编程智能体,不仅能补全代码,更能理解项目、自动重构、执行任务。本文详解国内安装、GPT-5.4接入、cc-switch中转配置及实战开发流程,助你从零掌握“描述需求→AI实现”的新一代工程范式。(239字)
7656 139
|
4天前
|
人工智能 自然语言处理 数据挖掘
零基础30分钟搞定 Claude Code,这一步90%的人直接跳过了
本文直击Claude Code使用痛点,提供零基础30分钟上手指南:强调必须配置“工作上下文”(about-me.md+anti-ai-style.md)、采用Cowork/Code模式、建立标准文件结构、用提问式提示词驱动AI理解→规划→执行。附可复制模板与真实项目启动法,助你将Claude从聊天工具升级为高效执行系统。
|
3天前
|
云安全 供应链 安全
Axios投毒事件:阿里云安全复盘分析与关键防护建议
阿里云云安全中心和云防火墙第一时间响应
1153 0
|
3天前
|
人工智能 定位技术
Claude Code源码泄露:8大隐藏功能曝光
2026年3月,Anthropic因配置失误致Claude Code超51万行源码泄露,意外促成“被动开源”。代码中藏有8大未发布功能,揭示其向“超级智能体”演进的完整蓝图,引发AI编程领域震动。(239字)
2222 9
|
2天前
|
人工智能 安全 IDE
Claude Code 51万行源码意外泄露:一次 .map 文件事故背后的 AI 工程启示录
源码仓库(Gitee 镜像):https://gitee.com/jeecg/claude-code
1057 3