事件的由来
我们继续之前的程序:三个方法都定义在Program类中,这样做是为了理解的方便,实际应用中,通常都是 GreetPeople 在一个类中,ChineseGreeting和 EnglishGreeting 在另外的类中。假设我们将GreetingPeople()放在一个叫GreetingManager的类中,那么新程序应该是这个样子的:
using System; using System.Collections.Generic; using System.Text; namespace Delegate { //定义委托,它定义了可以代表的方法的类型 public delegate void GreetingDelegate(string name); //新建的GreetingManager类 public class GreetingManager { public void GreetPeople(string name, GreetingDelegate MakeGreeting) { MakeGreeting(name); } } class Program { private static void EnglishGreeting(string name) { Console.WriteLine("Morning, " + name); } private static void ChineseGreeting(string name) { Console.WriteLine("早上好, " + name); } static void Main(string[] args) { GreetingManager gm = new GreetingManager(); //gm.GreetPeople("Jimmy Zhang", EnglishGreeting); //gm.GreetPeople("张子阳", ChineseGreeting); GreetingDelegate delegate1; delegate1 = EnglishGreeting; delegate1 += ChineseGreeting; gm.GreetPeople("Jimmy Zhang", delegate1 ); Console.Read(); } } }
运行结果:
//Morning, Jimmy Zhang
//早上好, 张子阳
Morning, Jimmy Zhang
早上好, Jimmy Zhang
现在,假设我们需要使用上一节学到的知识,将多个方法绑定到同一个委托变量,该如何做呢?让我们再次改写代码:
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
GreetingDelegate delegate1;
delegate1 = EnglishGreeting;
delegate1 += ChineseGreeting;
gm.GreetPeople("Jimmy Zhang", delegate1);
}
输出:
Morning, Jimmy Zhang
早上好, Jimmy Zhang
到了这里,我们不禁想到:面向对象设计,讲究的是对象的封装,既然可以声明委托类型的变量(在上例中是delegate1),我们何不将这个变量封装到 GreetManager类中?在这个类的客户端中使用不是更方便么?于是,我们改写GreetManager类,像这样:
public class GreetingManager{
//在GreetingManager类的内部声明delegate1变量
public GreetingDelegate delegate1;
public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
}
现在,我们可以这样使用这个委托变量:
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.delegate1 = EnglishGreeting;
gm.delegate1 += ChineseGreeting;
gm.GreetPeople("Jimmy Zhang", gm.delegate1);
}
输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang
尽管这样做没有任何问题,但我们发现这条语句很奇怪。在调用gm.GreetPeople方法的时候,再次传递了gm的delegate1字段:
gm.GreetPeople("Jimmy Zhang", gm.delegate1);
既然如此,我们何不修改 GreetingManager 类成这样:
public class GreetingManager{
//在GreetingManager类的内部声明delegate1变量
public GreetingDelegate delegate1;
public void GreetPeople(string name) {
if(delegate1!=null){ //如果有方法注册委托变量
delegate1(name); //通过委托调用方法
}
}
}
在客户端,调用看上去更简洁一些:
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.delegate1 = EnglishGreeting;
gm.delegate1 += ChineseGreeting;
gm.GreetPeople("Jimmy Zhang"); //注意,这次不需要再传递 delegate1变量
}
using System; using System.Collections.Generic; using System.Text; namespace Delegate { //定义委托,它定义了可以代表的方法的类型 public delegate void GreetingDelegate(string name); //新建的GreetingManager类 public class GreetingManager { public GreetingDelegate delegate1; //在GreetingManager类的内部声明delegate1变量 <变动1 ADD> //<变动3 Modify> //public void GreetPeople(string name, GreetingDelegate MakeGreeting) //{ // MakeGreeting(name); //} public void GreetPeople(string name) { if (delegate1 != null) //如果有方法注册委托变量 { delegate1(name); //通过委托调用方法 } } } class Program { private static void EnglishGreeting(string name) { Console.WriteLine("Morning, " + name); } private static void ChineseGreeting(string name) { Console.WriteLine("早上好, " + name); } static void Main(string[] args) { GreetingManager gm = new GreetingManager(); //原始---------------------------------------------- //gm.GreetPeople("Jimmy Zhang", EnglishGreeting); //gm.GreetPeople("张子阳", ChineseGreeting); //-------------------------------------------------- //<变动1> //GreetingDelegate delegate1; //delegate1 = EnglishGreeting; //delegate1 += ChineseGreeting; //gm.GreetPeople("Jimmy Zhang", delegate1); //<变动2> //gm.delegate1 = EnglishGreeting; //gm.delegate1 += ChineseGreeting; //gm.GreetPeople("Jimmy Zhang", gm.delegate1 ); //<变动3> gm.delegate1 = EnglishGreeting; gm.delegate1 += ChineseGreeting; gm.GreetPeople("Jimmy Zhang"); Console.Read(); } } }
输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang
尽管这样达到了我们要的效果,但是还是存在着问题:
在这里,delegate1和我们平时用的string类型的变量没有什么分别,而我们知道,并不是所有的字段都应该声明成public,合适的做法是应该public的时候public,应该private的时候private。
我们先看看如果把 delegate1 声明为 private会怎样?结果就是:这简直就是在搞笑。因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,你把它声明为private了,客户端对它根本就不可见,那它还有什么用?
再看看把delegate1 声明为 public 会怎样?结果就是:在客户端可以对它进行随意的赋值等操作,严重破坏对象的封装性。
最后,第一个方法注册用“=”,是赋值语法,因为要进行实例化,第二个方法注册则用的是“+=”。但是,不管是赋值还是注册,都是将方法绑定到委托上,除了调用时先后顺序不同,再没有任何的分别,这样不是让人觉得很别扭么?
现在我们想想,如果delegate1不是一个委托类型,而是一个string类型,你会怎么做?答案是使用属性对字段进行封装。
于是,Event出场了,它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符 与你在声明事件时使用的访问符 相同。
我们改写GreetingManager类,它变成了这个样子:
public class GreetingManager{
//这一次我们在这里声明一个事件
public event GreetingDelegate MakeGreet;
public void GreetPeople(string name) {
MakeGreet(name);
}
}
很容易注意到:MakeGreet 事件的声明与之前委托变量delegate1的声明唯一的区别是多了一个event关键字。看到这里,在结合上面的讲解,你应该明白到:事件其实没什么不好理解的,声明一个事件不过类似于声明一个进行了封装的委托类型的变量而已。
为了证明上面的推论,如果我们像下面这样改写Main方法:
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.MakeGreet = EnglishGreeting; // 编译错误1
gm.MakeGreet += ChineseGreeting;
gm.GreetPeople("Jimmy Zhang");
}
会得到编译错误:事件“Delegate.GreetingManager.MakeGreet”只能出现在 += 或 -= 的左边(从类型“Delegate.GreetingManager”中使用时除外)。
事件和委托的编译代码
这时候,我们注释掉编译错误的行,然后重新进行编译,再借助Reflector来对 event的声明语句做一探究,看看为什么会发生这样的错误:
public event GreetingDelegate MakeGreet;
可以看到,实际上尽管我们在GreetingManager里将 MakeGreet 声明为public,但是,实际上MakeGreet会被编译成 私有字段,难怪会发生上面的编译错误了,因为它根本就不允许在GreetingManager类的外面以赋值的方式访问,从而验证了我们上面所做的推论。
我们再进一步看下MakeGreet所产生的代码:
private GreetingDelegate MakeGreet; //对事件的声明 实际是 声明一个私有的委托变量
[MethodImpl(MethodImplOptions.Synchronized)]
public void add_MakeGreet(GreetingDelegate value){
this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_MakeGreet(GreetingDelegate value){
this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value);
}
现在已经很明确了:MakeGreet事件确实是一个GreetingDelegate类型的委托,只不过不管是不是声明为public,它总是被 声明为private。另外,它还有两个方法,分别是add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委托类型的 方法和取消注册。实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应remove_MakeGreet。而这两个方法的访问限制 取决于声明事件时的访问限制符。
在add_MakeGreet()方法内部,实际上调用了System.Delegate的Combine()静态方法,这个方法用于将当前的变量添加到委托链表中。我们前面提到过两次,说委托实际上是一个类,在我们定义委托的时候:
public delegate void GreetingDelegate(string name);
当编译器遇到这段代码的时候,会生成下面这样一个完整的类:
public sealed class GreetingDelegate:System.MulticastDelegate{
public GreetingDelegate(object @object, IntPtr method);
public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object);
public virtual void EndInvoke(IAsyncResult result);
public virtual void Invoke(string name);
}
关于这个类的更深入内容,可以参阅《CLR Via C#》等相关书籍,这里就不再讨论了。
在add_MakeGreet()方法内部,实际上调用了System.Delegate的Combine()静态方法,这个方法用于将当前的变量添加到委托链表中。我们前面提到过两次,说委托实际上是一个类,在我们定义委托的时候: public delegate void GreetingDelegate(string name); 当编译器遇到这段代码的时候,会生成下面这样一个完整的类: public sealed class GreetingDelegate:System.MulticastDelegate{ public GreetingDelegate(object @object, IntPtr method); public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object); public virtual void EndInvoke(IAsyncResult result); public virtual void Invoke(string name); } 关于这个类的更深入内容,可以参阅《CLR Via C#》等相关书籍,这里就不再讨论了。
------------------------------------------------------------------
Event
using System; using System.Collections.Generic; using System.Text; namespace ConsoleApp { class Event_Delegate { public static void Main() { BookStore store = new BookStore(); Customer[] cs = new Customer[3]; cs[0] = new Customer("李明", "计算机"); cs[1] = new Customer("赵丽", "英语"); cs[2] = new Customer("Tom", "计算机"); for (int i = 0; i < 3; i++) cs[i].register(store); store.NewBook("数据结构与算法","计算机"); store.NewBook("国家地理杂志","英语"); Console.Read(); } } public class Customer { private string m_name; private string m_type; public Customer(string name, string type) { m_name = name; m_type = type; } public void register(BookStore store) { store.OnNewBook +=new BookHandler(store_OnNewBook); } //事件处理方法 void store_OnNewBook(string bookName, string bookType) { if (m_type == bookType) Console.WriteLine("{0}您好:我店新到新书《{1}》",m_name ,bookType ); } } //委托定义 public delegate void BookHandler(string bookName,string bookType); public class BookStore { public void NewBook(string bookName, string bookType) { OnNewBook(bookName,bookType); } //事件 public event BookHandler OnNewBook; } }
李明您好:我店新到新书《计算机》
Tom您好:我店新到新书《计算机》
赵丽您好:我店新到新书《英语》
System程序集中定义了名为EventHander的委托对象,原型为:public delegate void EventHandler(object sender, EventArgs e);
使用EventHander改写:
using System; using System.Collections.Generic; using System.Text; namespace ConsoleApp2 { class Event_Delegate { public static void Main() { BookStore store = new BookStore(); Customer[] cs = new Customer[3]; cs[0] = new Customer("李明", "计算机"); cs[1] = new Customer("赵丽", "英语"); cs[2] = new Customer("Tom", "计算机"); for (int i = 0; i < 3; i++) cs[i].register(store); store.NewBook("数据结构与算法", "计算机"); store.NewBook("国家地理杂志", "英语"); Console.Read(); } } //Add //------------------------- public class BookEventArgs : EventArgs { public string BookName; public string BookType; public BookEventArgs(string name,string type) { BookName = name; BookType = type; } } //------------------------- public class Customer { private string m_name; private string m_type; public Customer(string name, string type) { m_name = name; m_type = type; } public void register(BookStore store) { //Modify //------------------------- //store.OnNewBook += new BookHandler(store_OnNewBook); store.OnNewBook += new EventHandler(store_OnNewBook); } //事件处理方法 void store_OnNewBook(object sender,EventArgs e) { //Modify //------------------------- //if (m_type == bookType) // Console.WriteLine("{0}您好:我店新到新书《{1}》", m_name, bookType); BookEventArgs be = (BookEventArgs)e; if (m_type == be.BookType) Console.WriteLine("{0}您好:我店新到新书《{1}》", m_name, be.BookType ); } } ////Delete 委托 //public delegate void BookHandler(string bookName, string bookType); public class BookStore { public void NewBook(string bookName, string bookType) { //Modify //------------------------- //OnNewBook(bookName, bookType); BookEventArgs e = new BookEventArgs(bookName, bookType); OnNewBook(this, e); } //Modify 事件 //------------------------- //public event BookHandler OnNewBook; public event EventHandler OnNewBook; } }