VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • C#教程之关于委托和事件的使用

原文:https://www.codeproject.com/articles/85296/important-uses-of-delegates-and-events
原文作者: Shivprasad koirala 
 

介绍

在这篇文章中, 我们会尝试着去理解delegate能解决什么样的问题, 然后会在实例中去使用。 之后, 我们要进一步理解多播委托的概念以及事件是如何封装委托的。 最终, 我们要明白事件和委托的不同, 学会如何异步调用委托。
 
在文章的最后,我们能能总结出委托的六种重要用处。
 

方法和函数的抽象问题

在讲委托之前,让我们先搞明白委托到底能解决什么问题。下面是一个很简单的类“ClsMaths”, 它只有一个方法“Add”。这个类会被一个简单的客户端消费(调用)。假设过了一段时间之后,现在客户端对ClsMaths这个类有了新的需求: 添加一个"Subtration"方法。那么,按之前的做法, 我们需要修改客户端已添加对新方法的调用代码。
换句话说, ClsMaths的一个新增方法导致了客户端的重新编译。
 
 
简单来说, 问题出现了: 功能类和消费类之间存在了紧耦合。所以如何解决? 
我们可以选择使用委托作为中间件(垫片), 消费类不再是直接调用实现类的方法,而是调用一个虚拟指针(委托),让委托去调用真正的执行方法。这样,我们就把消费类和具体实现方法解耦了。
译者注, 他这里的ClsMaths类只有四个方法 加减乘除, 作者使用了一个委托变量来调用4个方法, 所以这里确实做到了解耦。
 
稍后你就可以看到因为抽象指针的作用,ClsMath的修改将不会对消费类产生任何影响。 这里的抽象指针就是委托啦。
/** 题外话,上图提到的Balsamiq Mockups是一个很棒的软件, 可以用来画UI效果图, 我喜欢用来画流程图(稍显不如visio方便, 但是阅读和美观效果完爆之) **/
 

如何创建一个委托

创建一个委托只要四步: 定义, 创建, 引用, 调用(和C# in depth 中的说法一致)
第一步是定义一个和函数有同样返回类型、输入参数的委托, 例如下面的Add函数有2个int类型输入参数以及一个int类型的输入参数。
1 private int Add(int i,int y)
2 {
3     return i + y;
4 }

 

对此, 我们可以定义如下的委托:
1 // Declare delegate
2 public delegate int PointetoAddFunction(int i,int y);

 

注意, 返回类型和输入类型要兼容, 否则会报错。
 
下一步就是创建一个委托类型的变量喽:
1 // Create delegate reference
2 PointetoAddFunction myptr = null;

 

最后就是调用了:
1 // Invoke the delegate
2 myptr.Invoke(20, 10)

 

下图为实例代码:
 

如何使用委托解决抽象指针问题

为了解耦算法的变化, 我们使用一个抽象的指针指向所有的算法:(因为这四个方法的格式是一致的)
 
 
第一步, 在实现类中定义一个委托如下:(注意输入输出参数的格式)
1 public class clsMaths
2 {
3   public delegate int PointerMaths(int i, int y);
4 }

 

第二步, 定义一个返回委托的函数用以暴露具体实现方法给消费类:
复制代码
 1 public class clsMaths
 2 {
 3   public delegate int PointerMaths(int i, int y);
 4 
 5   public PointerMaths getPointer(int intoperation)
 6   {
 7     PointerMaths objpointer = null;
 8     if (intoperation == 1)
 9     {
10       objpointer = Add;
11     }
12     else if (intoperation == 2)
13     {
14       objpointer = Sub;
15     }
16     else if (intoperation == 3)
17     {
18       objpointer = Multi;
19     }
20     else if (intoperation == 4)
21     {
22       objpointer = Div;
23     }
24     return objpointer;
25   }
26 }
复制代码

 

下面就是完整的代码, 所有的具体实现函数都被标记为private, 只有委托和暴露委托的函数是public的。
复制代码
 1 public class clsMaths
 2 {
 3   public delegate int PointerMaths(int i, int y);
 4 
 5   public PointerMaths getPointer(int intoperation)
 6   {
 7     PointerMaths objpointer = null;
 8     if (intoperation == 1)
 9     {
10       objpointer = Add;
11     }
12     else if (intoperation == 2)
13     {
14       objpointer = Sub;
15     }
16     else if (intoperation == 3)
17     {
18       objpointer = Multi;
19     }
20     else if (intoperation == 4)
21     {
22       objpointer = Div;
23     }
24     return objpointer;
25   }
26 
27   private int Add(int i, int y)
28   {
29     return i + y;
30   }
31   private int Sub(int i, int y)
32   {
33     return i - y;
34   }
35   private int Multi(int i, int y)
36   {
37     return i * y;
38   }
39   private int Div(int i, int y)
40   {
41     return i / y;
42   }
43 }
复制代码

 

所以消费类的调用就和具体实现方法没有耦合了:
1 int intResult = objMath.getPointer(intOPeration).Invoke(intNumber1,intNumber2);

 

 

多播委托

在我们之前的例子中,我们已经知道了如何创建委托变量和绑定具体实现方法到变量上。但实际上, 我们可以给一个委托附上若干个具体实现方法。如果我们调用这样的委托, 那么附到委托上的函数会顺序执行。(至于如果函数有返回值, 那么只有最后一个函数的返回值会被捕捉到)
复制代码
1 // Associate method1
2 delegateptr += Method1;
3 // Associate Method2
4 delegateptr += Method2;
5 // Invoke the Method1 and Method2 sequentially
6 delegateptr.Invoke();
复制代码

 

所以, 我们可以在“发布者/消费者”模式中使用多播委托。例如, 我们的应用中需要不同类型的错误日志处理方式,当错误发生时,我们需要把错误信息广播给不同的组件进行不同的处理。 (如下图)
 

多播委托的简单例子

我们可以通过下面这个例子更好的理解多播委托。 在这个窗体项目中,我们有“Form1”, “Form2”, “Form3”。
“Form1中有一个多播委托来把动作的影响传递到“Form2”和“Form3”中。
 
在"Form1"中, 我们首先定义一个委托以及委托变量, 这个委托是用来传递动作的影响到其他Form中的。
复制代码
1 // Create a simple delegate
2 public delegate void CallEveryOne();
3 
4 // Create a reference to the delegate
5 public CallEveryOne ptrcall=null;
6 // Create objects of both forms
7 
8 public Form2 obj= new Form2();
9 public Form3 obj1= new Form3();
复制代码

 

在“Form1”的Form_Load函数中, 我们调用其他的Forms;把其他表单中的CallMe方法附加到“Form1”的委托中。
复制代码
1 private void Form1_Load(object sender, EventArgs e)
2 {
3   // Show both the forms
4   obj.Show();
5   obj1.Show();
6   // Attach the form methods where you will make call back
7   ptrcall += obj.CallMe;
8   ptrcall += obj1.CallMe;
9 }
复制代码

 

最终, 我们在"Form1"的按钮点击函数中调用委托(多播的):
复制代码
1 private void button1_Click(object sender, EventArgs e)
2 {
3   // Invoke the delegate
4   ptrcall.Invoke();
5 }
复制代码

 

 

多播委托的问题 -- 暴露过多的信息

 
上面例子的第一个问题就是, 消费者并没有权利来选择订阅或是不订阅,因为这个过程是由“Form1”也就是发布者来决定的。 
我们可以用其他方式, 把委托传递给消费者, 让消费者来决定他们要不要订阅来自发布者(Form1)的多播委托。 但是, 这种做法会引发另个问题: 破坏封装。 如果我们把委托暴露给消费者, 就意味着委托完全裸露在了消费者面前。 
 

事件 -- 委托的封装

事件能解决委托的封装问题。 事件包裹在委托之外, 使得消费者只能接收但不会有委托的完全控制权。
下图是对这一概念的图解:
1. 具体的实现方法被委托抽象和封装了
2. 委托被多播委托进一步封装了以提供广播的效果
3. 事件进一步封装了多播委托
 

实现事件

我们来把多播委托的例子改造成事件的方式。 第一步是在发布者“Form1”中定义委托和委托类型的事件; 下面就是对应的代码块,请注意关键字event。 我们定义了一个委托“CallEveryone”, 然后定义了一个委托类型的事件“EventCallEveryone”。
1 public delegate void CallEveryone();
2 public event CallEveryone EventCallEveryOne;

 

从发布者“Form1”中创建“Form2”和“Form3”的对象, 然后把当前这个“Form1”对象传到“Form2”、 "Form3"中, 这样 2、 3就可以监听事件了。
 
复制代码
1 Form2 obj = F new Form2();
2 obj.obj = this;
3 Form3 obj1 = new Form3();
4 obj1.obj = this;
5 obj.Show();
6 obj1.Show();
7 EventCallEveryOne();
复制代码

 

在消费者这边, “Form2”和“Form3”自主决定是否把具体某个方法付到事件上。
1 obj.EventCallEveryOne += Callme;

 

这段代码的执行结果将会和我们上文的多播委托的例子结果一样。 
 

委托和事件的不同

 
所以, 如果事件不是委托的语法糖那么他们之间的区别在哪?  我们在上文中已经提到了一个主要的区别: 事件比委托多了一层封装。因此, 如果我们传递委托, 那么消费者接受的是一个赤裸裸的委托, 用户可以修改委托的信息。 而我们使用事件,那么用户只能监听事件而不能修改它。 
函数的异步委托调用
委托的另一种用法是异步函数的调用。 你能够异步的调用委托指向的函数。 
 
异步调用意味着客户端调用委托之后, 代码的控制权又立即回到了客户端手中以继续执行后续的代码。 
委托携带者调用者的信息在parallel的线程池中启用新的线程执行具体的函数, 当委托执行结束后, 它会发出信息通知客户端(调用者)。 
为了能够异步得调用函数, 我们需要call “begininvoke”方法。 在“begininvoke”方法中, 我们需要为委托提供一个回调函数。 如下图的CallbackMethod。 
1 delegateptr.BeginInvoke(new AsyncCallback(CallbackMethod), delegateptr);

 

下面的代码段就是一个回调函数的demo, 这段代码会在委托中的函数执行完成之后被立即调用。
1 static void CallbackMethod(IAsyncResult result)
2 {
3   int returnValue = flusher.EndInvoke(result);
4 }

 

总结委托的用法

委托有5种重要的使用方式:(译者注: 原文写的6种, 我只看到了5种)
1. 抽象、封装一个方法(匿名调用)
    这是委托最重要的功能, 它帮助我们定义一个能够指向函数的抽象的指针。 同时, 这个指针还可以指向其他符合其规范的函数。 
开头的时候我们展示的一个math类, 之后我们使用一个抽象指针就把该math类中添加的所有函数都包括在内了。 这个例子就很好的说明了委托的这一使用方法。 
2. 回调机制
    客户端可以使用委托来创建回调函数。 
3. 异步执行
    使用BeginInvoke 和 EndInvoke 我们可以异步得调用所有的委托。 
4. 多点广播
    有的时候, 我们希望能够让函数顺序执行, 那么多播委托就可以做到这一点。 
5. 事件
    事件可以帮助我们方便的建立 发布/订阅 模式。 
 

相关教程