C# 中的委托和事件(详解)

2023-10-27

C# 中的委托和事件

       委托和事件在 .NET Framework 中的应用非常广泛,然而,较好地理解委托和事件对很多接触 C# 时间不长的人来说并不容易。它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去的人每次见到委托和事件就觉得心里堵得慌,浑身不自在。本章中,我将由浅入深地讲述什么是委托、为什么要使用委托、事件的由来、.NET Framework 中的委托和事件、委托中方法异常和超时的处理、委托与异步编程、委托和事件对Observer 设计模式的意义,对它们的编译代码也做了讨论。

1.1 理解委托

1.1.1 将方法作为方法的参数

我们先不管这个标题如何的绕口,也不管委托究竟是个什么东西,来看下面这两个最简单的方法,它们不过是在屏幕上输出一句问候的话语:

public void GreetPeople(string name)
{
    
    EnglishGreeting(name);
}
 
public void EnglishGreeting(string name)
{
    
    Console.WriteLine("Good Morning, " + name);
}

暂且不管这两个方法有没有什么实际意义。GreetPeople 用于向某人问好,当我们传递代表某人姓名的 name 参数,比如说“Liker”进去的时候,在这个方法中,将调用 EnglishGreeting 方法,再次传递 name 参数,EnglishGreeting 则用于向屏幕输出 “Good Morning, Liker”。

现在假设这个程序需要进行全球化,哎呀,不好了,我是中国人,我不明白“Good Morning”是什么意思,怎么办呢?好吧,我们再加个中文版的问候方法:

public void ChineseGreeting(string name)
{
    
    Console.WriteLine("早上好, " + name);
}

这时候,GreetPeople 也需要改一改了,不然如何判断到底用哪个版本的 Greeting 问候方法合适呢?在进行这个之前,我们最好再定义一个枚举作为判断的依据:

public enum Language
{
    
    English, Chinese
}
 
public void GreetPeople(string name, Language lang)
{
    
    switch (lang)
    {
    
        case Language.English:
            EnglishGreeting(name);
            break;
        case Language.Chinese:
            ChineseGreeting(name);
            break;
    }
}

OK,尽管这样解决了问题,但我不说大家也很容易想到,这个解决方案的可扩展性很差,如果日后我们需要再添加韩文版、日文版,就不得不反复修改枚举和GreetPeople() 方法,以适应新的需求。

在考虑新的解决方案之前,我们先看看 GreetPeople 的方法签名:

       public void GreetPeople(string name, Language lang);

我们仅看 string name,在这里,string 是参数类型,name 是参数变量,当我们赋给 name 字符串“Liker”时,它就代表“Liker”这个值;当我们赋给它“李志中”时,它又代表着“李志中”这个值。然后,我们可以在方法体内对这个 name 进行其他操作。哎,这简直是废话么,刚学程序就知道了。

如果你再仔细想想,假如 GreetPeople() 方法可以接受一个参数变量,这个变量可以代表另一个方法,当我们给这个变量赋值 EnglishGreeting 的时候,它代表着 EnglsihGreeting() 这个方法;当我们给它赋值ChineseGreeting 的时候,它又代表着 ChineseGreeting() 法。我们将这个参数变量命名为 MakeGreeting,那么不是可以如同给 name 赋值时一样,在调用 GreetPeople()方法的时候,给这个MakeGreeting 参数也赋上值么(ChineseGreeting 或者EnglsihGreeting 等)?然后,我们在方法体内,也可以像使用别的参数一样使用MakeGreeting。但是,由于 MakeGreeting 代表着一个方法,它的使用方式应该和它被赋的方法(比如ChineseGreeting)是一样的,比如:MakeGreeting(name);

好了,有了思路了,我们现在就来改改GreetPeople()方法,那么它应该是这个样子了:

public void GreetPeople(string name, *** MakeGreeting)

{

       MakeGreeting(name);

}

注意到 *** ,这个位置通常放置的应该是参数的类型,但到目前为止,我们仅仅是想到应该有个可以代表方法的参数,并按这个思路去改写 GreetPeople 方法,现在就出现了一个大问题:这个代表着方法的 MakeGreeting 参数应该是什么类型的?

说明:这里已不再需要枚举了,因为在给MakeGreeting 赋值的时候动态地决定使用哪个方法,是 ChineseGreeting 还是 EnglishGreeting,而在这个两个方法内部,已经对使用“Good Morning”还是“早上好”作了区分。

聪明的你应该已经想到了,现在是委托该出场的时候了,但讲述委托之前,我们再看看MakeGreeting 参数所能代表的 ChineseGreeting()和EnglishGreeting()方法的签名:

public void EnglishGreeting(string name)

public void ChineseGreeting(string name)

如同 name 可以接受 String 类型的“true”和“1”,但不能接受bool 类型的true 和int 类型的1 一样。MakeGreeting 的参数类型定义应该能够确定 MakeGreeting 可以代表的方法种类,再进一步讲,就是 MakeGreeting 可以代表的方法的参数类型和返回类型。

于是,委托出现了:它定义了 MakeGreeting 参数所能代表的方法的种类,也就是 MakeGreeting 参数的类型。

本例中委托的定义:

    public delegate void GreetingDelegate(string name);

与上面 EnglishGreeting() 方法的签名对比一下,除了加入了delegate 关键字以外,其余的是不是完全一样?现在,让我们再次改动GreetPeople()方法,如下所示:

public delegate void GreetingDelegate(string name);
public void GreetPeople(string name, GreetingDelegate MakeGreeting)
{
    
    MakeGreeting(name);
}

如你所见,委托 GreetingDelegate 出现的位置与 string 相同,string 是一个类型,那么 GreetingDelegate 应该也是一个类型,或者叫类(Class)。但是委托的声明方式和类却完全不同,这是怎么一回事?实际上,委托在编译的时候确实会编译成类。因为 Delegate 是一个类,所以在任何可以声明类的地方都可以声明委托。更多的内容将在下面讲述,现在,请看看这个范例的完整代码:

public delegate void GreetingDelegate(string name);
 
class Program
{
    
    private static void EnglishGreeting(string name)
    {
    
        Console.WriteLine("Good Morning, " + name);
    }
 
    private static void ChineseGreeting(string name)
    {
    
        Console.WriteLine("早上好, " + name);
    }
 
    private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
    {
    
        MakeGreeting(name);
    }
 
    static void Main(string[] args)
    {
    
        GreetPeople("Liker", EnglishGreeting);
        GreetPeople("李志中", ChineseGreeting);
        Console.ReadLine();
    }
}

我们现在对委托做一个总结:委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If … Else(Switch)语句,同时使得程序具有更好的可扩展性。

1.1.2 将方法绑定到委托

看到这里,是不是有那么点如梦初醒的感觉?于是,你是不是在想:在上面的例子中,我不一定要直接在 GreetPeople() 方法中给 name 参数赋值,我可以像这样使用变量:

static void Main(string[] args)
{
    
    GreetPeople("Liker", EnglishGreeting);
    GreetPeople("李志中", ChineseGreeting);
    Console.ReadLine();
}

而既然委托 GreetingDelegate 和类型 string 的地位一样,都是定义了一种参数类型,那么,我是不是也可以这么使用委托?

static void Main(string[] args)
{
    
    GreetingDelegate delegate1, delegate2;
    delegate1 = EnglishGreeting;
    delegate2 = ChineseGreeting;
    GreetPeople("Liker", delegate1);
    GreetPeople("李志中", delegate2);
    Console.ReadLine();
}

如你所料,这样是没有问题的,程序一如预料的那样输出。这里,我想说的是委托不同于 string 的一个特性:可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法。在这个例子中,语法如下:

static void Main(string[] args)
{
    
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting; 
    delegate1 += ChineseGreeting;
    GreetPeople("Liker", delegate1);
    Console.ReadLine();
}

实际上,我们可以也可以绕过GreetPeople 方法,通过委托来直接调用EnglishGreeting 和ChineseGreeting:

static void Main(string[] args)
{
    
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting;
    delegate1 += ChineseGreeting; 
    delegate1("Liker");
    Console.ReadLine();
}

说明:这在本例中是没有问题的,但回头看下上面 GreetPeople() 的定义,在它之中可以做一些对于 EnglshihGreeting 和 ChineseGreeting 来说都需要进行的工作,为了简便我做了省略。

注意这里,第一次用的“=”,是赋值的语法;第二次,用的是“+=”,是绑定的语法。如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。我们也可以使用下面的代码来这样简化这一过程:

GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting;

既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语法是“-=”:

static void Main(string[] args)
{
    
    GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
    delegate1 += ChineseGreeting;
    GreetPeople("Liker", delegate1);
    Console.WriteLine();
    
    delegate1 -= EnglishGreeting;
    GreetPeople("李志中", delegate1);
    Console.ReadLine();
}

让我们再次对委托作个总结:

    使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。

 

1.2 事件的由来

1.2.1 更好的封装性

我们继续思考上面的程序:上面的三个方法都定义在 Programe 类中,这样做是为了理解的方便,实际应用中,通常都是 GreetPeople 在一个类中,ChineseGreeting 和 EnglishGreeting 在另外的类中。现在你已经对委托有了初步了解,是时候对上面的例子做个改进了。假设我们将 GreetingPeople() 放在一个叫 GreetingManager 的类中,那么新程序应该是这个样子的:

namespace Delegate
{
    
    public delegate void GreetingDelegate(string name);
  
    public class GreetingManager
    {
    
        public void GreetPeople(string name, GreetingDelegate MakeGreeting)
        {
    
            MakeGreeting(name);
        }
    }
 
    class Program
    {
    
        private static void EnglishGreeting(string name)
        {
    
            Console.WriteLine("Good Morning, " + name);
        }
 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# 中的委托和事件(详解) 的相关文章

  • 为什么这个 Web api 控制器不并发?

    我有一个 Web API 控制器 里面有以下方法 public string Tester Thread Sleep 2000 return OK 当我调用它 10 次 使用 Fiddler 时 我预计所有 10 次调用都会在大约 2 秒后
  • 如何在 VC++ CString 中验证有效的整数和浮点数

    有人可以告诉我一种有效的方法来验证 CString 对象中存在的数字是有效整数还是浮点数吗 Use tcstol http msdn microsoft com en us library w4z2wdyc aspx and tcstod
  • 尝试了解使用服务打开对话框

    我已经阅读了有关使用 mvvm 模式打开对话框的讨论 我看过几个使用服务的示例 但我不明白所有部分如何组合在一起 我发布这个问题寻求指导 以了解我应该阅读哪些内容 以更好地理解我所缺少的内容 我将在下面发布我所拥有的内容 它确实有效 但从我
  • 在 CPP 类中将 C 函数声明为友元

    我需要在 C 函数中使用类的私有变量 我正在做这样的事情 class Helper private std string name public std getName return name friend extern C void in
  • 如何在类文件中使用 Url.Action() ?

    如何在 MVC 项目的类文件中使用 Url Action Like namespace 3harf public class myFunction public static void CheckUserAdminPanelPermissi
  • 现代 C++ 编译器是否能够在某些情况下避免调用 const 函数两次?

    例如 如果我有以下代码 class SomeDataProcessor public bool calc const SomeData d1 const SomeData d2 const private Some non mutable
  • 传递 constexpr 对象

    我决定给予新的C 14的定义constexpr旋转并充分利用它 我决定编写一个小的编译时字符串解析器 然而 我正在努力保持我的对象constexpr将其传递给函数时 考虑以下代码 include
  • java中如何重新初始化int数组

    class PassingRefByVal static void Change int pArray pArray 0 888 This change affects the original element pArray new int
  • cpp.react库的C++源代码中奇怪的“->* []”表达式

    这是我在文档中找到的 C 片段cpp react 库 https github com schlangster cpp react implicit parallelism auto in D MakeVar 0 auto op1 in g
  • 获取没有显式特征的整数模板参数的有符号/无符号变体

    我希望定义一个模板类 其模板参数始终是整数类型 该类将包含两个成员 其中之一是类型T 另一个作为类型的无符号变体T 即如果T int then T Unsigned unsigned int 我的第一直觉是这样做 template
  • 在 VS 中运行时如何查看 C# 控制台程序的输出?

    我刚刚编写了一个名为 helloworld 的聪明程序 它是一个 C NET 4 5 控制台应用程序 在扭曲的嵌套逻辑迷宫深处 使用了 Console WriteLine 当我在命令行运行它时 它会运行并且我会看到输出 我可以执行其他命令并
  • 不可变类与结构

    以下是类与 C 中的结构的唯一区别 如果我错了 请纠正我 类变量是引用 而结构变量是值 因此在赋值和参数传递中复制结构的整个值 类变量是存储在堆栈上的指针 指向堆上的内存 而结构变量作为值存储在堆上 假设我有一个不可变的结构 该结构的字段一
  • memcpy/memmove 到联合成员,这是否设置“活动”成员?

    重要说明 一些评论者似乎认为我是从工会抄袭的 仔细看memcpy 它从普通旧地址复制uint32 t 它不包含在联合中 另外 我正在复制 通过memcpy 到工会的特定成员 u a16 or u x in a union 不直接到整个联盟本
  • Oauth2中如何同时撤销RefreshToken和使AccessToken失效

    我正在使用 Owin Oauth2 授权和资源服务器相同 开发单页面应用程序 AngularJS Net MVC Json Rest API 的身份验证流程 我选择了 Bearer Token 路由而不是传统的 cookie session
  • 比较:接口方法、虚方法、抽象方法

    它们各自的优点和缺点是什么 接口方法 虚拟方法 抽象方法 什么时候应该选择什么 做出这一决定时应牢记哪些要点 虚拟和抽象几乎是一样的 虚方法在基类中有一个实现 可以选择重写 而抽象方法则没有 并且must在子类中被覆盖 否则它们是相同的 在
  • 模板类中的无效数据类型生成编译时错误?

    我正在使用 C 创建一个字符串类 我希望该类仅接受数据类型 char 和 wchar t 并且我希望编译器在编译时使用 error 捕获任何无效数据类型 我不喜欢使用assert 我怎样才能做到这一点 您可以使用静态断言 促进提供一个 ht
  • 如何解压 msgpack 文件?

    我正在将 msgpack 编码的数据写入文件 在编写时 我只是使用 C API 的 fbuffer 如 我为示例删除了所有错误处理 FILE fp fopen filename ab msgpack packer pk msgpack pa
  • 代码中的.net Access Forms身份验证“超时”值

    我正在向我的应用程序添加注销过期警报 并希望从我的代码访问我的 web config 表单身份验证 超时 值 我有什么办法可以做到这一点吗 我认为您可以从 FormsAuthentication 静态类方法中读取它 这比直接读取 web c
  • 在 Win32 控制台应用程序中设置光标位置

    如何在 Win32 控制台应用程序中设置光标位置 最好 我想避免制作句柄并使用 Windows 控制台功能 我花了整个早上沿着那条黑暗的小巷跑 它产生的问题比它解决的问题还要多 我似乎记得当我在大学时使用 stdio 做这件事相对简单 但我
  • MySqlConnectionStringBuilder - 使用证书连接

    我正在尝试连接到 Google Cloud Sql 这是一个 MySql 解决方案 我能够使用 MySql Workbench 进行连接 我如何使用 C 连接MySqlConnectionStringBuilder 我找不到提供这三个证书的

随机推荐

  • vue导出功能

    导出功能支持多级表头导出 导出后的excel自带可修改的样式 目录 前言 一 安装相关依赖 二 文件目录 1 Blob js 2 export js 3 Export2Excel js 三 创建导出组件 四 使用导出组件 前言 表格导出功能
  • Linux OOM killer(转)

    OOM killer 当物理内存和交换空间都被用完时 如果还有进程来申请内存 内核将触发OOM killer 其行为如下 1 检查文件 proc sys vm panic on oom 如果里面的值为2 那么系统一定会触发panic 2 如
  • 最新AI创作系统ChatGPT程序源码+详细搭建部署教程+微信公众号版+H5源码/支持GPT4.0+GPT联网提问/支持ai绘画+MJ以图生图+思维导图生成!

    使用Nestjs和Vue3框架技术 持续集成AI能力到系统 新增 MJ 官方图片重新生成指令功能 同步官方 Vary 指令 单张图片对比加强 Vary Strong Vary Subtle 同步官方 Zoom 指令 单张图片无限缩放 Zoo
  • stm32 esp8266配网-smartConfig和BT串口方式配网

    stm32 esp8266 ota系列文章 stm32 esp8266 ota 快速搭建web服务器之docker安装openresty stm32 esp8266 ota升级 tcp模拟http stm32 esp8266 ota升级 h
  • 概要设计、详细设计:概念、方法、实践步骤

    完整软件开发流程 需求分析 概要设计 详细设计 一 1 概念 方法 实践步骤 设计是指根据需求开发的结果 对产品的技术实现由粗到细进行设计的过程 根据设计粒度和目的的不同可以将设计分为概要设计 详细设计等阶段以便于管理和确保质量 设计内容也
  • MFC导出到Excel

    软件 vs2013 程序功能 将ListControl内容导出到Excel里 步骤 第一步 创建基于对话框的MFC工程 第二步 添加库 添加Excel类库 在工程名上右键 选择 添加 类 或者点击菜单栏的 项目 gt 添加类 选择 Type
  • SQL自动生成字段功能实现

    背景 最近在维护的一款数据产品 有一个数据推送功能 就是把数据从A数据源同步到B数据源 通过SQL指定A数据源里面的数据表 和字段 前面有SQL编辑框 可以提交语法无误的SQL 上面截图中的字段 表示期望推送到下游数据源的字段 左侧提供一个
  • node 连接 mysql 报错 ER_NOT_SUPPORTED_AUTH_MODE

    node 版本 v12 12 0 mysql 版本 8 0 我再家尝试使用node连接mysql数据库的时候 发现连接不上 报错信息显示为 code ER NOT SUPPORTED AUTH MODE errno 1251 sqlMess
  • ChatGPT - 基于 Visual Studio Code 进行 AI 编码

    2023 04 15 周六 杭州 晴 前情提要 ChatGPT AutoGPT AgentGPT 现在各种人工智能编码工具层出不穷 通过人工智能编码再也不用记大量的技术知识点了 现在所谓开发人员的经验性的东西也好像弱化了很多 我们可以更有效
  • protobuf与json互相转换

    Java http code google com p protobuf java format maven
  • vue + springboot poi 实现excel模板导出 完整代码

    有导入导出功能的时候 避免用户导入数据有误 提供了excel模板下载 用户直接在系统导出的excel模板中填写数据 再导入该excel 例如下图excel 提供前后台所有导出代码 3 代码 3 1 前台 1 按钮
  • Python 循环所有文件夹(含子文件夹),读取指定格式文件,另存为其他格式文件...

    循环所有文件夹 含子文件夹 读取指定格式文件 另存为其他格式文件 与原有文件在同一级目录 并删除原有文件 usr bin python coding utf 8 遍历所有文件夹 将指定格式文件 批量另存为其他文件 或其他格式 import
  • 【OpenCV学习笔记】【函数学习】十四(cvSeq的用法说明(功能很多,按照需求使用))

    OpenCV CvSeq 结构 一直困惑于CvSeq到底是个什么样的东西 因为曾经拿到别人写的一个函数库 其返回值是一个CvSeq指针 我的任务是遍历所有的Sequence 然后删除其中不符合要求的Sequence 由于没有文档 我当时并不
  • 优雅的获取文件及文件夹

    string filepath D WEB var rel Directory GetFiles filepath SearchOption AllDirectories ToList foreach var file in rel Con
  • Java垃圾回收机制

    Java垃圾回收机制 Java垃圾回收机制是指一种自动化的内存管理方式 Java程序员无需手动管理内存 而是由JVM Java虚拟机 自动进行垃圾回收 下面是简要的Java垃圾回收机制 垃圾收集器 JVM中垃圾回收器 Garbage Col
  • 外包征途-甲方、乙方、外包

    甲方 乙方 外包 在IT这行 我们经常都会听到这几个词 那这几个词到底是什么意思 我们先看看官方的解释 甲方 一般是指提出目标的一方 在合同拟定过程中主要是提出要实现什么目标 是合同的主导方 甲方是合同中双方平等主体的代称 也是为了方便在下
  • Meta算力争夺演变成团队动荡!LLaMA、LLaMA2、OPT团队成员多位离职

    据TheInformation报道 原参与Llama项目的团队成员有多位已经辞职 原因是Meta内部的OPT研究团队与Llama团队之间发生了一场关于计算资源的内部斗争 看来不管是谷歌 微软 OpenAI还是Meta 人才流失都是一个避不开
  • 【满分】【华为OD机试真题2023 JS】组装新的数组

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 组装新的数组 知识点回溯数组 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 给你一个整数M和数组N N中的元素为连续整数 要求根据N中的元素组装成新的数组R 组
  • iOS 299美元企业账号申请流程及注意事项

    iOS开发者众多 但并不是所有的开发者都对账号申请 证书配置这些问题都清楚 毕竟不是所有开发者都能够经历这个环节 多数情况下是进公司之前这些东西都已经有了 作为一个合格的iOS开发者 我们必须要了解苹果的三种开发者账号 下图对三者进行了比较
  • C# 中的委托和事件(详解)

    C 中的委托和事件 委托和事件在 NET Framework 中的应用非常广泛 然而 较好地理解委托和事件对很多接触 C 时间不长的人来说并不容易 它们就像是一道槛儿 过了这个槛的人 觉得真是太容易了 而没有过去的人每次见到委托和事件就觉得