一、前言

刚开始工作的时候,觉得委托和事件有些神秘,而当你理解他们之后,也觉得好像没有想象中的那么难。在项目中运用委托和事件,你会发现他非常棒,这篇博文算是自己对委托和事件的一次梳理和总结。

二、委托

C#中的委托,相当于C++中的指针函数,但委托是面向对象的,是安全的,是一个特殊的类,当然他也是引用类型,委托传递的是对方法的引用。

2.1、delegate

声明委托就必须使用关键字“delegate”,委托是先声明,后实例化。至少0个参数,至多32个参数

格式如下所示:

private delegate string GetAsString();

委托是一个类,所以他的实例化跟类的实例化一样,只是他总是接受一个将委托方法作为参数的构造函数。调用委托方法就有两种方式,如下所示:

int i = 10;
var method = new GetAsString(i.ToString);
//调用方法一
Console.WriteLine($"method方法{method()}");
//调用方法二
Console.WriteLine($"method.Invoke方法{method.Invoke()}");

运行结果:

2.2、Action

Action是无返回值的泛型委托,可以接受0个至16个传入参数

Action 表示无参,无返回值的委托

Action<int,string> 表示有传入参数int,string无返回值的委托

前面我们【Log4Net 日志记录的实现】中,就使用了Action。如:

 public static void Debug(string message, Action RegistedProperties)
{
RegistedProperties();
log.Debug(message);
}

调用方式为:

PFTLog.Debug("测试扩展字段", () => {
LogicalThreadContext.Properties["LogType"] = "扩展字段内容";
});

在运行中,直接运行Action中的内容即可。

2.3、Func

Func是有返回值的泛型委托,可以接受0个至16个传入参数

Func<int> 表示无参,返回值为int的委托

Func<object,string,int> 表示传入参数为object, string 返回值为int的委托

public static decimal GetTotal(Func<int, int, decimal> func, int a, int b)
{
return func(a, b);
}

调用方式

var total = GetTotal((a, b) => { return (decimal)a + b; }, 1, 2);
Console.WriteLine($"结果为{total}");

运行结果

2.4、predicate

predicate 是返回bool型的泛型委托,只能接受一个传入参数

predicate<int> 表示传入参数为int 返回bool的委托

定义一个方法:

public static bool FindPoints(int a)
{
return a >= 60;
}

定义Predicate委托

 Predicate<int> predicate = FindPoints;

调用

var points = new int[] {
10,
50,
60,
80,
100 };
var result = Array.FindAll(points, predicate); Console.WriteLine($"结果为{string.Join(";", result)}");

运行结果

2.5、多播委托

前面的只包含了一个方法的调用,委托可以包含多个方法,这种委托就叫做多播委托。多播委托利用“+=”和“-+”两种运算符进行添加和删除委托。

先定义两个方法

public static void MultiplyByTwo(double v)
{
double result = v * 2;
Console.WriteLine($"传值:{v};MultiplyByTwo结果为{result}");
}
public static void Square(double v)
{
double result = v * v;
Console.WriteLine($"传值:{v};Square结果为{result}");
}

然后调用

Action<double> operations = MultiplyByTwo;
operations(1);
operations += Square;
operations(2);

运行结果:

三、事件

事件是基于委托,为委托提供一种发布/订阅机制,声明事件需要使用event关键字。

发布者(Publisher):一个事件的发行者,也称作是发送者(sender),其实就是个对象,这个对象会自行维护本身的状态信息,当本身状态信息变动时,便触发一个事件,并通知说有的事件订阅者;

订阅者(Subscriber):对事件感兴趣的对象,也称为Receiver,可以注册感兴趣的事件,在事件发行者触发一个事件后,会自动执行这段代码

是不是看到sender,就有种很熟悉的感觉!!!先不忙着急,我们先看下事件的声明和使用

有这样一个应用场景,如果系统有异常,需要及时的通知管理员。那么需要在我们的日志记录里面添加通知管理员的功能,但是问题来了,该怎么通知管理员呢?至少现在无法知道。所以我们就需要在使用到事件。

添加代码如下,如果不知道日志功能的可以参考【Log4Net 日志记录的实现

//声明一个通知的委托
public delegate void NoticeEventHander(string message);
//在委托的机制下我们建立以个通知事件
public static event NoticeEventHander OnNotice;

调用方式

public static void Debug(string message, Action RegistedProperties)
{
RegistedProperties();
log.Debug(message);
//执行通知
OnNotice?.Invoke($"系统异常,请及时处理,异常信息:{message}");
}

在引用场景的代码,先定义一个通知管理员的方法(这里我们直接Console.WriteLine出来)

public static void Notice(string message)
{
Console.WriteLine($"通知内容为{message}");
}

先注册,然后触发异常消息

//注册方式一
PFTLog.OnNotice += Notice;
//注册方式二
//PFTLog.OnNotice += new PFTLog.NoticeEventHander(Notice); PFTLog.Debug("测试扩展字段", () => {
LogicalThreadContext.Properties["LogType"] = "扩展字段内容";
});

运行结果

这里面我只需要定义好发布者,你可以以任何方式订阅,是不是很非常简单。

弄明白了上面的事件,我们在来说说.Net经常出现的object sender和EventArgs e

.Net Framework的编码规范:

一、委托类型的名称都应该以EventHandler结束

二、委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)

三、事件的命名为 委托去掉 EventHandler之后剩余的部分

四、继承自EventArgs的类型应该以EventArgs结尾

现在我们以一个新书发布的自定义事件为例

创建对应的类文件:

事件者发布代码:

public class BookInfoEventArgs : EventArgs
{
public BookInfoEventArgs(string bookName)
{
BookName = bookName;
} public string BookName { get; set; } }
public class BookDealer
{
//泛型委托,定义了两个参数,一个是object sender,第二个是泛型 TEventArgs 的e
//简化了如下的定义
//public delegate void NewBookInfoEventHandler(object sender, BookInfoEventArgs e);
//public event NewBookInfoEventHandler NewBookInfo;
public event EventHandler<BookInfoEventArgs> NewBookInfo;
public void NewBook(string bookName)
{
RaiseNewBookInfo(bookName);
} public void RaiseNewBookInfo(string bookName)
{
NewBookInfo?.Invoke(this, new BookInfoEventArgs(bookName));
}
}

事件订阅者

public class Consumer
{
public Consumer(string name)
{
Name = name;
} public string Name { get; set; } public void NewBookHere(object sender, BookInfoEventArgs e)
{
Console.WriteLine($"用户:{Name},收到书名为:{ e.BookName}");
}
}

事件订阅和取消订阅

var dealer = new BookDealer();
var consumer1 = new Consumer("用户A");
dealer.NewBookInfo += consumer1.NewBookHere;
dealer.NewBook("book112"); var consumer2 = new Consumer("用户B");
dealer.NewBookInfo += consumer2.NewBookHere; dealer.NewBook("book_abc"); dealer.NewBookInfo -= consumer1.NewBookHere; dealer.NewBook("book_all");

运行结果

经过这个例子,我们可以知道Object sender参数代表的是事件发布者本身,而EventArgs e 也就是监视对象了。深入理解之后,是不是觉得也没有想象中的那么难了。

四、总结

这里我们讲了委托和事件,在.Net开发中使用委托和事件,可以减少依赖性和层的耦合,开发出具有更高的重用性的组件。

C#委托(delegate、Action、Func、predicate)和事件的更多相关文章

  1. 委托delegate,Action,Func,Predicate

    C#委托的介绍(delegate.Action.Func.predicate) 委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递.事件是一种特殊的委托. 1.委托的声明 ...

  2. C# 委托应用总结(委托,Delegate,Action,Func,predicate)

    C# 委托应用总结 一.什么是委托 1.1官方解释 委托是一种定义方法签名的类型.当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联.您可以通过委托实例调用方法. 1.2个人理解 委托就是执 ...

  3. Delegate,Action,Func,Predicate的使用与区别

    C#4.0推出后,类似Linq,Lamda表达式等许多新的程序写法层次不穷.与之相关的Delegate,Action,Func,Predicate的使用和区别也常常让大家迷惑,此处就结合实际的应用,对 ...

  4. 【Unity|C#】基础篇(11)——内置的泛型委托(Action/Func/Predicate)

    [Action] 无返回值 的泛型委托,可以有0~16个参数(函数重载) public delegate void Action(); // 无参数 public delegate void Acti ...

  5. (C#) Action, Func, Predicate 等泛型委托

    (转载网络文章) (1). delegate delegate我们常用到的一种声明   Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型.   例:public del ...

  6. Delegate,Action,Func,匿名方法,匿名委托,事件 (转载)

    Delegate,Action,Func,匿名方法,匿名委托,事件 (转载) 一.委托Delegate 一般的方法(Method)中,我们的参数总是string,int,DateTime...这些基本 ...

  7. c# Action,Func,Predicate委托

    System命名空间下已经预先定义好了三中泛型委托,Action,Func和Predicate,这样我们在编程的时候,就不必要自己去定义这些委托了 Action是没有返回值的 Func是带返回值的 不 ...

  8. Delegate,Action,Func,匿名方法,匿名委托,事件

    一.委托Delegate 一般的方法(Method)中,我们的参数总是string,int,DateTime...这些基本的数据类型(或者没有参数),比如 public void HelloWorld ...

  9. 温故而知新:Delegate,Action,Func,匿名方法,匿名委托,事件

    Tks: http://www.cnblogs.com/yjmyzz/archive/2009/11/23/1608818.html 20150801 add: http://www.cnblogs. ...

  10. 对委托 以及 action func 匿名函数 以及 lambda表达式的简单记录

    class Program { public delegate void MyDelegate(string str); static void Main(string[] args) { // My ...

随机推荐

  1. 伪分布式Spark + Hive on Spark搭建

    Spark大数据平台有使用一段时间了,但大部分都是用于实验而搭建起来用的,搭建过Spark完全分布式,也搭建过用于测试的伪分布式.现在是写一遍随笔,记录一下曾经搭建过的环境,免得以后自己忘记了.也给和 ...

  2. 结构型设计模式——适配器模式(Go)

    适配器模式: 适配器模式是用于当别人提供的对象或接口中的方法或者其它属性啥的和我们的重复了,或者看的不顺眼.名字太长了记不住,而将其包装到一个对象中,然后通过你感觉自己舒服的方式或者方法名字去间接的调 ...

  3. 消息中间件-activemq消息机制和持久化介绍(三)

    前面一节简单学习了activemq的使用,我们知道activemq的使用方式非常简单有如下几个步骤: 创建连接工厂 创建连接 创建会话 创建目的地 创建生产者或消费者 生产或消费消息 关闭生产或消费者 ...

  4. 一文了解java异常机制

    1.异常的概述 1.1什么是异常? 异常:程序在运行过程中发生由于外部问题导致的程序异常事件,发生的异常会中断程序的运行.(在Java等面向对象的编程语言中)异常本身是一个对象,产生异常就是产生了一个 ...

  5. vs2013 在按F5调试时,总是提示 “项目已经过期”的解决方案

    这个是由于缺少某些文件(如.h,xxx.icon),或者文件时间不对 引起的. 如图在工具选项设置 最小为 “诊断”. 然后编译一下,会提示 xxx过期,确认下即可.

  6. 一.安全NA之syslog SNMP SSH NTP

    一.常用命令 配置模式下: no logging console #关闭屏幕实时显示日志,不影响到日志buffer里(show logging) logging console #打开屏幕实时日志显示 ...

  7. Windows Server 2008在网络环境配置打印机

    下面学习在Windows Server2008在网络环境搭建打印机服务器,打印机服务器也是很常用的,特别是在中大型企业里面,打印机数量比较多为方便管理,可以搭建一个打印机服务,这里介绍一下,本地打印机 ...

  8. 图数据库 Nebula Graph 的数据模型和系统架构设计

    Nebula Graph:一个开源的分布式图数据库.作为唯一能够存储万亿个带属性的节点和边的在线图数据库,Nebula Graph 不仅能够在高并发场景下满足毫秒级的低时延查询要求,而且能够提供极高的 ...

  9. Docker跨服务器通信Overlay解决方案(上) Consul单实例

    场景 公司微服务快上线了,微服务都是用Docker容器进行部署的,在同一台主机下,把服务都部署上,注册到Nacos的IP与PORT都是内网的IP与Dockerfile中定义的端口号,看起来好像也没什么 ...

  10. rabbitmq集群操作与启停

    一.rabbitmq集群必要条件 1.1. 绑定实体ip,即ifconfig所能查询到的绑定到网卡上的ip,以下是绑定方法 1.2. 配置域名映射到实体ip 二.启动停止 2.1 停止 2.2 启动 ...