委托与事件,它们的应用非常广泛,为了便于复习,我特地将它们总结了一下。
一、委托
委托,通俗的讲,就是‘方法’的容器。
是用来存放和调用方法用的。
下面这个例子,简单的介绍一下委托的用法:
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事件中没有与 ‘=’对应的方法。