委托与事件,它们的应用非常广泛,为了便于复习,我特地将它们总结了一下。
一、委托
委托,通俗的讲,就是‘方法’的容器。
是用来存放和调用方法用的。
下面这个例子,简单的介绍一下委托的用法:
public delegate void SayHi_Delegate(string name);
这就是一个委托,任何形如 void **(string **);的函数,都可以使用这个委托来调用。
比如:
private static void SayHiEn(string name) //说英文
{
Console.WriteLine("Hi {0}, I am ZeroCool_24!",name);
}
private static void SayHiCh(string name) //说中文
{
Console.WriteLine("你好 {0}, 我是 ZeroCool_24!", name);
}
调用的方法如
static void Main(string[] args)
{
SayHi_Delegate Hi;
Hi = SayHiCh;
Hi += SayHiEn;
Hi("cyh");
Console.ReadLine();
}
输出结果为:
可以看到,在向SayHi方法容器中添加方法的时候,使用了 += ,同理还可以用 -=; 值得注意的一点是,容器内必须至少要有一个方法。
我们知道,可以将方法作为参数来传递,同理的,我们也可以将委托(方法的容器),作为参数来进行传递,如下:
首先定义接收委托的函数
private static void GreetPeople(string name, SayHi_Delegate MakeGreeting)
{
MakeGreeting(name);
}
接下来调用一下:
static void Main(string[] args)
{
SayHi_Delegate Hi;
Hi = SayHiCh;
Hi += SayHiEn;
//Hi("cyh");
GreetPeople("cyh", Hi);
Console.ReadLine();
}
得到的结果与上面是一样的。
二、事件
由于大家已经对委托有了一个初步的认识,下面,我们将这个例子做一个改进
using System;
namespace 委托
{
public delegate void SayHi_Delegate(string name);
public class GreetClass //新的类
{
public void GreetPeople(string name, SayHi_Delegate MakeGreeting)
{
MakeGreeting(name);
}
}
}
using System;
namespace 委托
{
class TestClass
{
private static void SayHiEn(string name)
{
Console.WriteLine("Hi {0}, I am ZeroCool_24!",name);
}
private static void SayHiCh(string name)
{
Console.WriteLine("你好 {0}, 我是 ZeroCool_24!", name);
}
static void Main(string[] args)
{
SayHi_Delegate Hi;
Hi = SayHiCh;
Hi += SayHiEn;
//Hi("cyh");
GreetClass greet = new GreetClass();
greet.GreetPeople("cyh", Hi);
greet.GreetPeople("zc", SayHiCh); //SayHiCh()非 SayHi_Delegate 委托,但是因为可以转化成一个只含SayHiCh()方法的委托。
Console.ReadLine();
}
}
}
结果如下:
演示的结果很好,看上去好像也没有问题。但是,能不能更好的处理呢?
答案是肯定的。应用面向对象的思想,我们应该将main()中的委托变量Hi封装到GreetClass类中。
using System;
namespace 委托
{
public delegate void SayHi_Delegate(string name);
public class GreetClass
{
public SayHi_Delegate Hi; //SayHi_Delegate的实例
public void GreetPeople(string name, SayHi_Delegate MakeGreeting)
{
MakeGreeting(name);
}
}
}
static void Main(string[] args)
{
GreetClass greet = new GreetClass();
greet.Hi = SayHiCh;
greet.Hi += SayHiEn;
greet.GreetPeople("Cyh_Zc",greet.Hi);
Console.ReadLine();
}
这样,又有问题出来了,委托变量Hi虽然被封装到了GreetClass,但是,客户端依然可以随意的访问它,这就带来了一个安全性的问题,如果现在的Hi不是SayHi_Delegate类型,而是string类型,我们可以使用属性来解决。
但是,委托变量该如何解决呢?
没错,就是事件。Event,它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。
我们来改写GreetClass类:
public class GreetClass
{
public event SayHi_Delegate Hi; //SayHi_Delegate的事件,只是修饰符多了一个event public void GreetPeople(string name)
{
Hi(name);
}
}
static void Main(string[] args)
{
GreetClass greet = new GreetClass();
//greet.Hi = SayHiCh; //编译错误 greet.Hi += SayHiCh;
greet.Hi += SayHiEn;
greet.GreetPeople("Cyh_Zc");
Console.ReadLine();
}
可以看到,在使用greet.Hi = SayHiCh的使用,出现了编译错误,这是为什么呢?
原来 greet.Hi 只能出现在 += 或 -= 的左边(从类“GreetClass”中使用时除外)。为什么会这样?
我们用.NET Reflector来看看GreetClass编译后的过程:
public class GreetClass
{
// Fields
private SayHi_Delegate Hi;
// Events
public event SayHi_Delegate Hi;
// Methods
public GreetClass();
public void GreetPeople(string name);
}
看到上面编译之后的代码,大家会疑惑,为什么会多了一个(Fields) private SayHi_Delegate Hi;
原来,public event SayHi_Delegate Hi;这行代码编译之后,会默认自动的新增一个private的字段SayHi_Delegate Hi;
猜想:这个private SayHi_Delegate类型的字段Hi是在GreetClass中使用的,Public 事件 Hi是供外部使用的。
验证:查看GreetClass中的Hi(name);
发现引用的是:
private SayHi_Delegate Hi;
再查看Main()方法中的greet.Hi,可以看到:
public event SayHi_Delegate Hi
{
add
{
SayHi_Delegate delegate3;
SayHi_Delegate hi = this.Hi;
do
{
delegate3 = hi;
SayHi_Delegate delegate4 = (SayHi_Delegate) Delegate.Combine(delegate3, value);
hi = Interlocked.CompareExchange<SayHi_Delegate>(ref this.Hi, delegate4, delegate3);
}
while (hi != delegate3);
}
remove
{
SayHi_Delegate delegate3;
SayHi_Delegate hi = this.Hi;
do
{
delegate3 = hi;
SayHi_Delegate delegate4 = (SayHi_Delegate) Delegate.Remove(delegate3, value);
hi = Interlocked.CompareExchange<SayHi_Delegate>(ref this.Hi, delegate4, delegate3);
}
while (hi != delegate3);
}
}
说明了上面的猜想是正确的。
看到event Hi()中,有两个方法:add()和remove(),我想大家应该已经猜到,这两个函数对应了greet.Hi的两个操作 += 和 -= 。这下我想大家也应该知道了为什么greet.Hi = SayHiCh;的时候会报错了------因为,Hi事件中没有与 ‘=’对应的方法。