C#委托和事件

委托

什么是委托?

  • 现实中的委托: 3个室友赖床,听到了我要去食堂吃饭的消息,所以他们准备委托我给他们带饭

不需要亲自执行,全权委托第三方,处理各种事务。例如律师帮助雇主起诉别人,雇主委托侦探去调查自己的老婆昨晚为什么没有回家。室友委托自己带饭。

  • 在程序中委托: 通过委托类型的变量,间接的去调用封装在委托内部的方法

委托是一种类,是一种引用类型的数据类型,委托创建的实例可以存储一个,或者多个方法的引用。

如何声明委托?

//声明了一个无返回值的委托,无任何参数的委托
public delegate void MyDelegate();

//声明一个返回类型为int的委托,参数为2个int类型
public delegate int MyDelegate2(int a,int b);

声明委托的位置?

//声明在类外部的委托
public delegate void MyDelegate1()
public class MyClass{
  //声明在类内部的委托称之为嵌套类型
	public delegate void MyDelegate2()
}

如何使用委托间接调用方法?

static void GoodMorning()
{
    Console.WriteLine("Good morning");
}

static void GoodAfternoon()
{
    Console.WriteLine("Good afternoon");
}

static void GoodEvening()
{
    Console.WriteLine("Good evening");
}

static string SayHello(string name){
		return "Hello "+name+"";
}

static int TwoSum(int a,int b){
		return a*b;
}
public delegate void MyDelegate();
MyDelegate d1;
d1 = new MyDelegate(GoodMorning);
d1 += new MyDelegate(GoodAfternoon);
d1 += new MyDelegate(GoodEvening);
d1.Invoke();
  • 首先声明一个委托类型MyDelegate,该委托【无返回值,无参】【定义的位置可以在类的外部或者类内部】
  • 初始化一个委托类型的变量 d1
  • 实例化 委托 d1
  • 使用Invoke调用封装在d1内部的方法

MyDelegate(void () target)

上面中表示的是,需要穿一个目标方法target,该方法返回类型为void,并且参数为()也就是空参数,只要传方法名即可不用传参数。

如果调用有参数和有返回值的呢?

public delegate string MyDelegate1(string name);
public delegate int MyDelegate2(int a,int b);

MyDelegate1 d1;
d1 = new MyDelegate1(SayHello);
d1.Invoke("frank");

MyDelegate2 d1;
d1 = new MyDelegate2(TwoSum);
d1.Invoke(2,4);

使用委托间接方法时,要注意委托返回值类型和方法是否一致,委托参数个数、 类型是否和方法一致。

如何使用语法糖衣简化委托?

完整版本调用和使用委托:

public delegate void MyDelegate();
MyDelegate d1;
d1 = new MyDelegate(GoodMorning);
d1 += new MyDelegate(GoodAfternoon);
d1.Invoke();

语法糖衣简化版本:

public delegate void MyDelegate();
MyDelegate d1;
d1 = GoodMorning;
d1 += GoodAfternoon;
d2();

通过语法糖衣简化了实例化委托的过程以及调用的过程。

单播委托、多播委托是什么?

//单播绑定
d1 = new MyDelegate(GoodMorning);

//多播绑定
d1 += new MyDelegate(GoodAfternoon);

//解绑
d1 -= new MyDelegate(GoodAfternoon);

顾名思义,单播委托就是绑定一个方法,多播委托则是绑定多个方法,在上面的例子中使用了运算法 = 、+=来表现单播委托和多播委托。


使用委托的意义是什么?

可能会有人问,委托是间接调用方法,和直接调用有什么区别,执行的结果不都是一样的吗?有什么实际的意义?

那么究竟使用委托间接调用方法比直接调用好在哪里?

  • 委托可以通过封装方法到委托变量,达到间接调用方法的目的
  • 也就是说可以将委托变量作为一个参数传递到另外一个方法去执行。
  • 达到了一种动态调用方法的效果

委托的缺点是什么?

  • 多播委托很容易因为操作失误导致封装的方法被重置

    在多播委托中使用 += ,一旦写成了 = 则导致 覆盖掉之前的引用,既重置了委托封装的引用列表

  • 过度使用委托会导致内存泄漏:Memory Leak

    多播委托会引用多个方法,而当这个方法是实例方法【非静态方法】的话,也就是说这个方法隶属于一个对象。

    一旦我们使用委托引用这个方法的话,那么这个对象就必须存在于内存当中。即便没有其他地方引用这个对象,因为委托的关系,这个对象也不能释放。

    因为一旦释放,委托就不再能够间接调用到这个方法了,所以委托可能导致内存泄漏【Memory Leak】。

    随着泄露的内存越来越多,会导致性能下降,程序崩溃的风险。

  • 滥用委托会导致可读性下降

    在委托变量中封装方法,然后将委托变量作为一个参数传递给其他方法,会导致可读性大大下降。

Action委托/Func委托

想要间接调用方法,需要使用委托,那么是否所有委托都需要我们使用delegate关键字自己去声明委托类型吗?

其实在C#类库中已经为我们准备好了2个内置委托类型,百分之90%的情况下已经可以满足使用了,可以不用自定义委托。

//语法糖衣写法
Action a1;
a1 = GoodMorning;
a1.Invoke();

//完整版本写法
Func<string,string> b1;
b1 = new Func<string, string>(SayHello);
b1.Invoke("Frank");
  • Action委托是无返回值、可以无参或者有参的泛型委托【参数最多可达16个】
public delegate void Action();
  • Func委托是有返回值、可以无参或者有参的泛型委托【参数最多可达16个】
public delegate TResult Func<in T, out TResult>(T arg);

Action委托与Func委托的区别在于:一个没有返回值,一个必须要有返回值

什么是模板方法/回调方法?

模板方法和回调方法是将委托变量作为一个参数传递进方法。

模板方法:下面有一个SayHello的模板方法。

public string SayHello(string name){
	return "Hello" + name;
}
//首先声明一个Func委托变量封装SayHello方法
Func<string,string> d1 = SayHello;
//将委托变量作为参数传入方法内。
MakeFriend(d1);

public void MakeFriend(Func<string,string> _func){
  //可以灵活的间接调用委托内部的方法。
	string greet = _func("Frank");
  Console.WriteLine(greet);
}

回调方法:

public void GoHome(Someone){
		//go home
}
//逛街
public void DateWithGirdfriend(Action<SomeOne> _action){
	 //接女友
	 //看电影
	 if(DateTime.Now >= 23:00)
	 //回家
	 _action(Someone);
}

模板方法和回调方法在我看来都是差不多的,都是将委托变量传进一个方法,模板方法一般是有返回值的,回调函数一般没有返回值,方法的调用与否取决于代码的逻辑

事件(Events)

当发生一些有趣的事件时,事件能使一个类或者对象去通知其他类、对象。

别的类、对象在接收到这个通知之后,就会对这个事件作出各自的响应。

____________发生了 
游戏发布
玩家阵亡
写数学作业
被父母批评
闹钟响了
发送消息
表白成功

C#如何定义事件?

  • 事件是一种类型成员

类结构

  • 事件可以”发生“,通过通知其他的类【其他关注这个事件的类】,他们得到通知后,发生的效果,才是事件的功能。

事件的核心功能就是将事件的参数、也就是事件的相关信息,通知给那些订阅了这个事件的类或者对象。

事件如何通知?

女神失恋了,这件事情发生后,女神闷闷不乐,于是在朋友圈写道:“人生不值得”。

这条消息都所有关注女神的人看到了。

在上面这个故事对应到程序语言则是:女神【类】 失恋了【事件】 发朋友圈【通知】,被关注【订阅】的人看到

也就是说女神把失恋这个事件发布到朋友圈后,所有关注着女神的人都能够得到通知。而不关注女神的人则得不到通知。

类如何对事件做出响应?

易烊千玺【类】 发布了一首新歌【事件】,因为我关注了易烊千玺的微博【订阅】 所以我得到了消息【通知】,于是我买了他的新专辑【响应】

在程序里,我们在接收到事件的通知后,会调用某一个方法去响应,我们把这 个方法称之为事件处理器

事件5个组成部分/步骤

事件模式在构建和运作时的5个步骤

  1. 我【类】要有一个事件【成员】
  2. 一群别的类关心、订阅我的事件
  3. 我的事件发生了
  4. 关心这个事件的类们都被依次通知到了
  5. 被通知到的类,根据拿到的事件信息,做出相应

事件模型的五大组成部分

  • 事件的拥有者:一定是一个类【或者说对象】

    皮之不存,毛将焉附:没有事件拥有者【类、对象】也就没有事件

  • 事件:核心功能室通知其他类,对象、做出响应

    ⭐事件不会主动发生、一定是由事件拥有者的内部逻辑触发的。

    按钮事件也不会主动被触发,一定是执行点击操作之后,被动触发的

    我们可以通过事件降低代码的耦合性,保护程序不会内存泄漏。

  • 事件的响应者:订阅事件的类、对象,他们会根据自身的事件处理器【方法】,来处理事件

  • 事件订阅:事件发生后,通知的一定是订阅了事件的对象们

事件处理器和事件之间的关系

不同的事件需要不同的事件处理器,本质上来说就是,事件处理器的返回值和参数列表是否和事件的委托类型一致。

☘️知识点连接:向委托变量中封装方法时,也要遵循委托类型的返回值、参数列表与方法是否一致。概念相同

从Timer了解事件

1.  Timer timer = new Timer();

2.	timer.Elapsed += Printer.MyAction;
		//timer.Elapsed += new ElapsedEventHandler(MyAction);
		timer.Interval = 500;
		timer.Start();

3 . private void MyAction(object sender, ElapsedEventArgs e)
		{
		    Console.WriteLine("Hello");
		}
4.  timer.Elapsed = Printer.MyAction;
  1. 声明了一个Timer类
  2. 调用Timer类中写好的Elapsed事件,并且为这个事件订阅事件处理器
  3. 实现事件处理器

事件是Timer类的成员,Elapsed是Timer类中的一个委托类型 +=操作也就是给委托类型变量Elapsed封装方法,本质上和委托是一致的,要保持委托类型和目标方法【事件处理器】的返回类型、参数类型一致

  1. 事件修复了委托中多播委托=可能会将封装方法重置的缺点,所以禁止使用=,避免重置已经调阅了这个事件的、事件处理器们。

如何定义完整的自定义事件?

//事件模型的五个组成部分
//事件的拥有者【类】
//事件【event关键字修饰】
//事件的响应者【类】
//事件处理器【方法-受到约束】
//事件的订阅关系【+=】

一 . 声明委托类型

public delegate void OrderEventHandler(Object sender, EventArgs e);
  • 命名规范
事件 + EventHandler

遵循这样规定的委托变量,默认是用来声明事件的。不会被用来当做参数进行传递

  • 返回值

事件的核心功能是通知,所以返回类型是Void

  • 参数列表
(Object sender, EventArgs e);
  1. 这里的sender是事件的拥有者,告诉是谁触发了这个消息,第二个参数类型可以是EventArgs的派生类,告诉我们传递过来的事件里面是一些什么样的内容
  2. 这里的EventArgs是C#提供的一个空的数据类型,无法储存数据。
  • 自定义参数类型用以存储事件相关数据
public class OrderEventArgs : EventArgs
{
    public int CoffeePrice { get; set; }
    public string CoffeeSize { get; set; }
    public string CoffeeName { get; set; }
}
  1. 用于事件参数存储的类都应该继承自EventArgs
  2. 遵循相应的命名规范: 事件 + EventArgs

二、声明委托类型字段

private OrderEventArgs orderEventArgs;
  • 使用private 访问修饰符,使外部无法访问内部委托字段。

三、声明事件

//创建一个委托类型
public delegate void OrderEventHandler(object sender, OrderEventArgs e);
//创建一个委托类型的字段
private OrderEventHandler orderEventHandler;
//创建一个委托类型的事件
public event OrderEventHandler OnOder {
    add {
****        OnOder += value; //添加事件处理器
    }
    remove {
        OnOder -= value; //移除事件处理器
    }
}
  1. 这里创建的事件使用event关键字修饰
  2. OnOder事件中我们使用add和remove控制事件的添加和移除
  3. 🌟也就是说这里的事件只是对委托字段的一个封装,限制了事件的添加和移除
  4. ⭐在这里事件和委托字段的关系,和字段与属性是十分相似的。

四、触发事件

public void Order(){
	if(orderEventHandler != null){
			OrderEventArgs e = new OrderEventArgs(){
				CoffeePrice = 15,
				CoffeeSize = "Tall",
				CoffeeName = "Cab"
			};
			orderEventHandler(this,e);
	}
}
  • 事件只能由事件内部主动触发,无法被外部触发。所以触发方法要写在事件拥有者的内部
  • 在类的内部定义Order方法进行触发,当orderEventHandler字段不等于空意味着有其他类订阅了这个事件,那么则触发事件。

语法糖衣声明事件

public delegate void OrderEventHandler(object sender, OrderEventArgs e);
public event OrderEventHandler OnOder;

public void Order(){
	if(OnOder!= null){
			OrderEventArgs e = new OrderEventArgs(){
				CoffeePrice = 15,
				CoffeeSize = "Tall",
				CoffeeName = "Cab"
			};
			OnOder(this,e);
	}
}
  • 简化版本事件的声明很简便,事件看起来像是使用了event关键字修饰的委托类型的字段
  • 这里在触发事件的时候也要使用看起来像字段的事件名来判断
  • 根据事件声明的完整版本我们知道,事件根本不是字段,而是对委托类型的字段进行了封装
  • 这样做的好处是限制外部的恶意调用,恶意访问。

EventHandler、EventArgs

  1. 在定义自定义事件的时候,我们必须要自定义委托类型吗?

其实C#已经帮我写好了一些基础的委托类型供我们调用

//C#中自带的委托类型
public delegate void EventHandler(object sender, EventArgs e);

//自定义的委托类型
public delegate void OrderEventHandler(object sender, EventArgs e);

当我们有特殊需要时,再自定义委托类型,别忘记要遵循命名规范:Order + EventHandler

  1. EventArgs是一个空类型,是不能传递参数的

所以那么如何自定义类型传递参数?

  • 遵循命名规范:Event + Args
  • 派生自EventArgs 这个类
public class OrderEventArgs : EventArgs
{
    public int CoffeePrice { get; set; }
    public string CoffeeSize { get; set; }
    public string CoffeeName { get; set; }
}

这样我们就能够将OrderEventArgs 类型的变量传递到事件处理器了,使用语法 as 转化为OrderEventArgs类型,进行取值

internal static void PayBill(object sender, EventArgs e)
{
    OrderEventArgs order = e as OrderEventArgs;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值