C#中的事件本质论
最近一段时间在学习C#中的一些比较抽象的东西,就比如说委托和事件,反反复复的学习了几次之后,感觉能稍微懂了一些,在这里和一些学习C#的同学一起来分享一下
在说事件之前,先简单的看一下委托
什么是委托
比如我们有以下的三个需求
1:将一个字符串数组的每个元素都转换成大写字母
2:将一个字符串数组的每个元素都转换成小写字母
3:将一个字符串数组的每个元素两边加上双引号
最开始我们都会这么去写我们的程序
using System;
using System.Collection.Generic;
using System.Linq;
using System.Text;
using System.Threading.Task;
namespace StringPro
{
class Program
{
static void Main(string[] args)
{
string[] strs = {"DfdsSDGF","dsagDFSFWfdsafd","fdsafewgGEWTE"};
//根据每种处理字符串的情况不同,我们封装三个方法,将字符串数组作为参数传入方法当中,因为C#中字符串数组是引用类型,所以写的三个方法不需要有返回值
ProToLower(strs);
Console.ReadKey();
}
///<summary>
///将字符串数组的每个元素转换成小写
///<summary>
///<param name="strs">要操作的数组</param>
static void ProToLower(string[] strs)
{
for(int i = 0;i < strs.Length;i++)
{
strs[i] = strs[i].ToLower();
}
}
///<summary>
///将字符串数组的每个元素转换成大写
///<summary>
///<param name="strs">要操作的数组</param>
static void ProToUpper(string[] strs)
{
for(int i = 0;i < strs.Length;i++)
{
strs[i] = strs[i].ToUpper();
}
}
///<summary>
///将字符串数组的每个元素都加上双引号
///<summary>
///<param name="strs">要操作的数组</param>
static void ProToSYH
(string[] strs)
{
for(int i = 0;i < strs.Length;i++)
{
strs[i] = "\""+strs[i]+"\"";
}
}
}
}
上面的代码是我们传统的写法,可以发现,我们只是有一个对于字符串的处理方式不一样,剩下的代码都是一样的,那么在C#中我们可以使用委托来进行简化我们上述的代码
using System;
using System.Collection.Generic;
using System.Linq;
using System.Text;
using System.Threading.Task;
namespace StringPro
{
//声明委托的时候,我们委托的签名要和委托指向的方法的签名一致,因为我们要处理字符串数组,所以我们声明的委托签名就是字符串数组作为参数,没有返回值
public delegate void DelStr(string strs);
class Program
{
static void Main(string[] args)
{
string[] strs = {"DfdsSDGF","dsagDFSFWfdsafd","fdsafewgGEWTE"};
ProStr(strs,(str)=>{return str.ToLower();});
//ProStr(strs,(str)=>{return str.ToUpper();});
//ProStr(strs,(str)=>{return "\""+str+"\"";});
Console.ReadKey();
}
//利用委托来简化代码,用一个方法来代替三个方法
static void ProStr(string strs,DelStr del)
{
for(int i = 0;i < strs.Length;i++)
{
del(strs[i]);
}
}
}
}
这样我们就用委托和Lambda表达式来代替了方法,减少了代码的数量,简化了开发
那么事件是什么呢,我们先用一个实际生活中的例子来说明事件,就比如说:
你骂我,我打你。
我们可以想一下,如果你不骂我, 那么我不会打你,所以一个事件发生的时候一定有因果关系。
我们写一个利用事件实现的简单程序
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace _04事件的本质
{
class Program
{
static void Main(string[] args)
{
PlayMusic p = new PlayMusic("忐忑");
p.DelPlayOver += P_DelPlayOver;
p.Play();
Console.ReadKey();
}
private static void P_DelPlayOver(object sender, EventArgs e)
{
PlayMusic p = sender as PlayMusic;
Console.WriteLine(p.Name+"播放完了");
}
}
class PlayMusic
{
public event EventHandler DelPlayOver;
public string Name { get; set; }
public PlayMusic(string name)
{
this.Name = name;
}
public void Play()
{
Console.WriteLine("正在播放"+Name);
Thread.Sleep(3000);
if (DelPlayOver != null)
{
DelPlayOver(this, new EventArgs());
}
}
}
}
在这段代码中,我们用播放音乐的实例来模拟事件,我们这样想,如果一个音乐播放完成,那么它的前提是这个音乐必须播放,如果音乐不播放,那么就不存在播放完成这件事情了。
在上面那段代码中,PlayMusic这个类模拟了播放音乐,在这个类的内部,我们用event关键字声明一个事件,这个事件是在模拟播放完成之后完成的,在Main方法中,我们实例化这个类,执行Play方法,那么在播放完成后,会触发DelPalyOver这个事件,因此,在播放完成之前,我们需要给这个事件进行赋值,让这个事件指向一个函数(其实可以指向多个函数)。
我们发现,委托也是指向一个函数,事件可以指向多个函数,这个和多播委托有点类似。那么他们之间有什么关系呢?
在C#中,我们用event关键字来声明一个委托,然后我们用.Net Reflector反编译刚才我们写的程序,会看到这样的一些内容。
DelPlayOver这个事件内部经过反编译之后有add_DelPlayOver(EventHandler)和remove_DelPlayOver(EventHandler)这两个方法,那么这两个方法究竟是干什么的呢?
我们看到上面两个方法的参数是一个EventHandler类型的变量
我们看到EvnetHandler是一个具有object和EventArgs类型作为参数并且没有返回值的委托,那么也就不难解释add_***和remove_***两个方法中的内容,这两个方法的真是作用其实就是在内部封装了一个多播委托,通过+=和-=运算符来指向不同的函数。也就是说,在C#中,事件的实质就是封装的一个多播委托
为什么要这么做
C#中委托作为一种变量类型,指向的是函数,因为委托可以随便调用,那么这就有一定的不安全性,会造成程序的一些漏洞,有可能会造成程序的崩溃。那么我们用event关键字,可以对委托进行访问级别的一些限制,不管类定义的访问修饰符是public、internal还是protected,event关键字定义的委托总是private的,那么这个委托只能在类的内部被访问,在外部只能进行+=或者-=的操作,这样就加强了程序的安全性,做到了防御性编程。