C# 委托与事件总结

  • 推荐资料
  • 委托
    • 使用 C# 内置委托:Action 和 Func
    • 自定义委托
    • 委托的综合实例
    • 多播委托
  • 事件
    • 事件声明的完整格式
    • 事件声明的简略格式
    • 事件的本质
    • 使用 EventHandler
    • 命名约定

C# 中的委托和事件可以说是超级拦路虎了,一个不小心就容易让人直接放弃学习。最近几天花了点时间钻研了一下,查看了一些资料,希望做个总结。

我还达不到技术大佬的级别,主要就是做个自我总结,很推荐大家看一下我下面发的参考资料里的文章和视频,那些是真正的大佬产出的内容。

推荐资料

张子扬博客中的文章:

  • C# 中的委托和事件 - Part.1 - 张子扬
  • C# 中的委托和事件 - Part.2 - 张子扬

刘铁猛的视频:

  • 刘铁猛《C#语言入门详解》- P19 委托详解
  • 刘铁猛《C#语言入门详解》- P20 事件详解(上)
  • 刘铁猛《C#语言入门详解》- P21 事件详解(中)
  • 刘铁猛《C#语言入门详解》- P22 事件详解(下)

官方文档:C# 官方文档


把委托单独拿出来讲倒是不难理解,但是和和事件结合就会产生化学反应,第一次学习的时候直接爆炸了…

委托

委托其实就是个类(因此它定义时往往和类平级),它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递(其实就是一直说的函数式编程),主要目的就是为了让程序有更好的拓展型。

即使不学习这个概念,基本上不会影响我们编写程序(大不了就是 if else 走天下,来新需求就大规模的改代码…),但是这个概念对于写出高质量、优雅的程序至关重要。

类比一下其他语言,关于完成“传递方法”这件事,各个语言有不同的做法:

  • C / C++ 通过函数指针传递函数,而委托可以看作函数指针的 “升级版”,它比函数指针更加安全
  • Java 中没有委托的概念,需要依靠接口来实现传递方法,其中一些典型的函数式接口如:Supplier 优化不一定执行的代码、Consumer:接收一个值决定要做什么、Predicate:让过滤条件更灵活、Function:实现类型转换
  • JavaScript 中“函数是一等公民”,可以直接实现将函数作为参数与返回值

可以看到,函数式编程是个重要的概念,各个语言基于这个概念只是实现方式不同,而 C# 的实现方式就是 委托

使用 C# 内置委托:Action 和 Func

先学会怎么用,再探究怎么写,因此我们需要先学会使用 AcitonFunc

ActionFunc:是 C# 中内置的委托类型:

  • Action 用于委托没有返回值的函数
    Action 表示无参,无返回值的委托
    Action<int, string> 表示有传入参数 int、string 无返回值的委托
    Action<int, string, bool> 表示有传入参数 int、string、bool 无返回值的委托
  • Func 用于委托有返回值的函数
    Func<int> 表示无参,返回值是 int 的委托
    Func<int, int> 表示返回值是 int,传入参数 int 的委托
    Func<double, double, int> 表示返回值是 double,传入参数是 double、int 的委托
class Test
{class Calculator{public void Report() => Console.WriteLine("I have 3 methods");public int Add(int a, int b) => a + b;}static void Main(){Calculator calculator = new Calculator();// Action用于委托没有返回值的函数Action action = new Action(calculator.Report);action(); // action.Invoke() 也可以// Func的范型的第一个参数是返回值,然后是函数的输入参数Func<int, int, int> func = new Func<int, int, int>(calculator.Add);Console.WriteLine(func(1, 2)); // func.Invoke(1, 2); }
}

自定义委托

在学会使用委托的情况下,再来自己声明一个委托试试看,然后使用它

委托是类,所以声明位置是和 class 处于同一个级别。但 C# 允许嵌套声明类(一个类里面可以声明另一个类),所以有时也会有 delegate 在 class 内部声明的情况。

// 使用 delegate 声明委托
public delegate double Calc(double x, double y);class Calculator
{public double Mul(double x, double y) => x * y;public double Div(double x, double y) => x / y;
}class Program
{static void Main(string[] args){Calculator calculator = new Calculator();// 创建委托实例的完整写法Calc calc1 = new Calc(calculator.Mul);// 创建委托实例的简单写法Calc calc2 = calculator.Div;Console.WriteLine(calc1(6, 2)); // 12Console.WriteLine(calc2(6, 2)); // 3}
}

委托的综合实例

委托的一般使用场景:

  • 模版方法,提高代码复用性
  • 回调函数,将某个方法传入主调方法中,根据其逻辑决定是否调用该方法

综合实例:

using System;class Program
{static void Main(string[] args){var productFactory = new ProductFactory();// Func 用于有返回值的函数,范型中第一个类型即返回值类型Func<Product> func1 = new Func<Product>(productFactory.MakePizza);Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);var wrapFactory = new WrapFactory();var logger = new Logger();// Action 用于没有返回值的函数,范型中的类型即传入参数类型Action<Product> log = new Action<Product>(logger.Log);Box box1 = wrapFactory.WrapProduct(func1, log);Box box2 = wrapFactory.WrapProduct(func2, log);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}
}// 日志类 - 一般不属于业务,属于额外操作
class Logger
{public void Log(Product product){// Now 是带时区的时间,存储到数据库应该用不带时区的时间 UtcNow。Console.WriteLine("Product '{0}' created at {1}.Price is {2}", product.Name, DateTime.UtcNow, product.Price);}
}// 产品类 - 业务
class Product
{public string Name { get; set; }public double Price { get; set; }
}// 包装类 - 业务
class Box
{public Product Product { get; set; }
}// 包装工厂类 - 用于包装产品
class WrapFactory
{// 模板方法,提高复用性// 所有的产品包装过程都需要遵守这个规范,// 对WrapFactory来说不关心具体对象, 只负责执行这个操作public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallBack){var box = new Box();Product product = getProduct.Invoke();// 只 log 价格高于 50 的if (product.Price >= 50){logCallBack(product);}box.Product = product;return box;}
}// 产品工厂类 - 用于生产不同的产品
class ProductFactory
{public Product MakePizza(){return new Product { Name = "Pizza", Price = 12 }; ;}public Product MakeToyCar(){return new Product { Name = "Toy Car", Price = 100 }; ;}
}

多播委托

多播委托就是,一个委托内部有多个方法,当该委托被调用时,其中的方法依次调用:

class Program
{static void Main(string[] args){Student stu1 = new Student { Name = "aaa" };Student stu2 = new Student { Name = "bbb" };Action action1 = new Action(stu1.Study);Action action2 = new Action(stu2.Study);// 单播委托 -> 每个委托依次调用//action1.Invoke();//action2.Invoke();// 多播委托 -> 多个委托合并为一个委托再调用action1 += action2;action1.Invoke();}
}class Student
{public string Name { get; set; }public void Study() => Console.WriteLine(Name + " is doing studying!");
}

事件

事件声明有完整声明和简略声明两种,简略声明是完整声明的语法糖

事件无论是从表层约束还是从底层实现都是依赖于委托的

事件声明的完整格式

声明委托类型 ≠ 声明委托类型字段

  • 委托类型是与类同级别的
  • 委托类型字段是类内部的一个字段
using System;
using System.Threading;namespace EventExample
{class Program{static void Main(string[] args){// 1.事件拥有者var customer = new Customer();// 2.事件响应者var waiter = new Waiter();// 3.Order 事件成员 5. +=事件订阅customer.Order += waiter.Action;customer.Action();customer.PayTheBill();}}// 该类用于传递点的是什么菜,作为事件参数,需要以 EventArgs 结尾,且继承自 EventArgspublic class OrderEventArgs : EventArgs{// 菜品名public string DishName { get; set; }// 菜品大小public string Size { get; set; }}// 声明一个委托类型,因为该委托用于事件处理,所以以 EventHandler 结尾// 注意委托类型的声明和类声明是平级的public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);public class Customer{// 委托类型字段private OrderEventHandler orderEventHandler;// 事件声明public event OrderEventHandler Order{add { this.orderEventHandler += value; }remove { this.orderEventHandler -= value; }}public double Bill { get; set; }public void PayTheBill(){Console.WriteLine("I will pay ${0}.", this.Bill);}public void WalkIn(){Console.WriteLine("Walk into the restaurant");}public void SitDown(){Console.WriteLine("Sit down.");}public void Think(){for (int i = 0; i < 5; i++){Console.WriteLine("Let me think ...");Thread.Sleep(1000);}if (this.orderEventHandler != null){var e = new OrderEventArgs();e.DishName = "Kongpao Chicken";e.Size = "large";this.orderEventHandler.Invoke(this, e);}}public void Action(){Console.ReadLine();this.WalkIn();this.SitDown();this.Think();}}public class Waiter{// 4.事件处理器public void Action(Customer customer, OrderEventArgs e){Console.WriteLine("I will serve you the dish - {0}.", e.DishName);double price = 10;switch (e.Size){case "small":price *= 0.5;break;case "large":price *= 1.5;break;default:break;}customer.Bill += price;}}
}

事件声明的简略格式

简略格式:一种 filed-like 的声明格式。

filed-like:像字段声明一样 。

简略格式与上例的完整格式只有事件声明事件触发两处不同

using System;
using System.Threading;namespace EventExample
{class Program{static void Main(string[] args){// 1.事件拥有者var customer = new Customer();// 2.事件响应者var waiter = new Waiter();// 3.Order 事件成员 5. +=事件订阅customer.Order += waiter.Action;customer.Action();customer.PayTheBill();}}public class OrderEventArgs : EventArgs{public string DishName { get; set; }public string Size { get; set; }}public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);public class Customer{// 简略事件声明,看上去像一个委托(delegate)类型字段public event OrderEventHandler Order;public double Bill { get; set; }public void PayTheBill(){Console.WriteLine("I will pay ${0}.", this.Bill);}public void WalkIn(){Console.WriteLine("Walk into the restaurant");}public void SitDown(){Console.WriteLine("Sit down.");}public void Think(){for (int i = 0; i < 5; i++){Console.WriteLine("Let me think ...");Thread.Sleep(1000);}// 微软的语法糖使得事件变得像委托类型字段一样if (this.Order != null){var e = new OrderEventArgs();e.DishName = "Kongpao Chicken";e.Size = "large";// 事件触发this.Order.Invoke(this, e);}}public void Action(){Console.ReadLine();this.WalkIn();this.SitDown();this.Think();}}public class Waiter{// 4.事件处理器public void Action(Customer customer, OrderEventArgs e){Console.WriteLine("I will serve you the dish - {0}.", e.DishName);double price = 10;switch (e.Size){case "small":price *= 0.5;break;case "large":price *= 1.5;break;default:break;}customer.Bill += price;}}
}

事件的本质

事件本质上就是,对委托字段的一个包装器

  • 事件这个包装器对委托字段的访问起限制作用,只让你访问 +=、-= ,让你只能给事件添加或移除事件处理器,让程序更加安全更好维护
  • 事件对外界隐藏了委托实例的大部分功能,仅仅暴露添加/删除事件处理器的功能

使用 EventHandler

C# 中内置一个通用的委托声明:EventHandler

我们可以将前面代码中自己声明的委托去掉:

public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

将上面这行代码注释后,在 Customer 中使用 EventHandler 作为委托声明:

public class Customer
{// 使用默认的 EventHandler,而不是声明自己的public event EventHandler Order;// code..
}

命名约定


触发事件的方法一般命名为 OnXxx,且访问级别为 protected(自己的类成员及派生类能访问)

依据单一职责原则,把原来的 Think 中触发事件的部分单独提取为 OnOrder 方法

public void Think()
{for (int i = 0; i < 5; i++){Console.WriteLine("Let me think ...");Thread.Sleep(1000);}this.OnOrder("Kongpao Chicken","large");
}protected void OnOrder(string dishName, string size)
{if (this.Order != null){var e = new OrderEventArgs();e.DishName = dishName;e.Size = size;this.Order.Invoke(this, e);}
}

C# 委托与事件总结相关推荐

  1. [C#]委托和事件(讲解的非常不错)

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

  2. .NET基础示例系列之六:委托及事件

    委托是一个类. 定义委托时,实是定义一个用户自定义的类,它能代表具有相同参数列表和返回类型的任何方法,方法可以是静态方法或成员方法.示例: public partial class Form1 : F ...

  3. 大白话系列之C#委托与事件讲解(一)

    从序言中,大家应该对委托和事件的重要性有点了解了吧,虽然说我们现在还是能模糊,但是从我的大白话系列中,我会把这些概念说的通俗易懂的.首先,我们还是先说说委托吧,从字面上理解,只要是中国人应该都知道这个 ...

  4. 委托、事件、事件访问器

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 委托与事 ...

  5. js中的事件委托或是事件代理详解(转载)

    起因: 1.这是前端面试的经典题型,要去找工作的小伙伴看看还是有帮助的: 2.其实我一直都没弄明白,写这个一是为了备忘,二是给其他的知其然不知其所以然的小伙伴们以参考: 概述: 那什么叫事件委托呢?它 ...

  6. JS事件委托或者事件代理原理以及实现

    事件委托(事件代理)原理:简单的说就是将事件交由别人来执行,就是将子元素的事件通过冒泡的形式交由父元素来执行. 为什么要用时间委托? 在JavaScript中,添加到页面上的事件处理程序数量将直接关系 ...

  7. 对C#下函数,委托,事件的一点理解!

    <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 今天一来是有点 ...

  8. 13委托和事件在观察者模式中的应用

    当一个被监视对象的方法执行会触发观察者Observer的方法的时候,我们就可以在被监视对象中声明委托和事件. 例子 有一个宠物追踪器挂宠物身上,只要宠物离开主人100米之外,主人手上的显示器显示警告信 ...

  9. C#综合揭秘——深入分析委托与事件

    引言 本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单. 还将为您解释委托的协变与逆变,以及如何使用 Deleg ...

  10. 【转】c#委托与事件

    c#用委托来实现事件通知机制.委托相当与c++函数指针.整个过程涉及一个呼叫者,一个被呼叫者,还有就是这个委托. - 实现步骤 有以下几步: 1. 申明委托, 2.定义呼叫者和调用的函数, 3.定义被 ...

最新文章

  1. 数据库事务的ACID特性及含义
  2. 解决Fedora 自己编译的内核不能运行Virtualbox的问题。
  3. Matlab实现字符串分割
  4. iOS 判断纯汉字,还是是否含有汉字
  5. The hierarchy of the type UserOperateLogAdvisor is inconsistent
  6. python网络运维案例代码库_OLDboy-python第八期运维教程
  7. 2017百度之星资格赛 1003 度度熊与邪恶大魔王
  8. mysql索引的种类
  9. Unicode与UTF8:字符集与字符编码的关系
  10. 增强网络安全意识——如何5分钟破解校园网上网账号和密码
  11. java 全双工串口,Java实现全双工串口通信
  12. 树莓派raspi-config配置工具
  13. Linux基础命令介绍四:文本编辑vim
  14. 微信小程序、苹果手机(ios)时间格式显示NAN.....(已解决)
  15. uni app 自动化索引列表
  16. 6.PCIe协议分析3-PCIe TLP包详解2
  17. vs2017或vs2019安装中Microsoft.VisualStudio.MinShell.Msi.Resources出错的问题
  18. mysql的主从复制和半同步复制的配置
  19. 张勋说:棒磨机断棒的几个可能原因
  20. 用多备份将网站数据备份到百度云,七牛云存储,阿里云OSS,亚马逊S3,金山云等云存储服务上

热门文章

  1. 要想赚到最轻松的钱,就要靠资源整合的重要性
  2. API/POSIX/C库的区别与联系
  3. bison、lex版本不同造成的问题
  4. 如何在Windows Server 2016上配置iSCSI启动器
  5. POJ3714 Raid 分治/K-D Tree
  6. HDU 2068 Choose the best route
  7. Swift - 控制流/控制结构说明(if,switch,for,while)
  8. CS48 D BIT
  9. 【学习笔记】OSG 基本几何图元
  10. 《Java高级程序设计》第一周作业