快捷搜索:
来自 计算机编程 2019-08-17 18:31 的文章
当前位置: 67677新澳门手机版 > 计算机编程 > 正文

【67677新澳门手机版】委托和事件,关于委托和事

方法和函数的抽象问题

在讲委托之前,让我们先搞明白委托到底能解决什么问题。下面是一个很简单的类“ClsMaths”, 它只有一个方法“Add”。这个类会被一个简单的客户端消费(调用)。假设过了一段时间之后,现在客户端对ClsMaths这个类有了新的需求: 添加一个"Subtration"方法。那么,按之前的做法, 我们需要修改客户端已添加对新方法的调用代码。

换句话说, ClsMaths的一个新增方法导致了客户端的重新编译。

 

67677新澳门手机版 1

 

简单来说, 问题出现了: 功能类和消费类之间存在了紧耦合。所以如何解决? 

我们可以选择使用委托作为中间件(垫片), 消费类不再是直接调用实现类的方法,而是调用一个虚拟指针(委托),让委托去调用真正的执行方法。这样,我们就把消费类和具体实现方法解耦了。

译者注, 他这里的ClsMaths类只有四个方法 加减乘除, 作者使用了一个委托变量来调用4个方法, 所以这里确实做到了解耦。

 

稍后你就可以看到因为抽象指针的作用,ClsMath的修改将不会对消费类产生任何影响。 这里的抽象指针就是委托啦。

67677新澳门手机版 2

/** 题外话,上图提到的Balsamiq Mockups是一个很棒的软件, 可以用来画UI效果图, 我喜欢用来画流程图(稍显不如visio方便, 但是阅读和美观效果完爆之) **/

 

14.2.1.5、触发A类的事件

在B类中去调用A类中的触发事件的方法:用A类的对象去调用A类的触发事件的方法。

如何创建一个委托

创建一个委托只要四步: 定义, 创建, 引用, 调用(和C# in depth 中的说法一致)

67677新澳门手机版 3

第一步是定义一个和函数有同样返回类型、输入参数的委托, 例如下面的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);

 

注意, 返回类型和输入类型要兼容, 否则会报错。

 67677新澳门手机版 4

下一步就是创建一个委托类型的变量喽:

1 // Create delegate reference
2 PointetoAddFunction myptr = null;

 

最后就是调用了:

1 // Invoke the delegate
2 myptr.Invoke(20, 10)

 

下图为实例代码:

67677新澳门手机版 5

 

14.2.1、自定义事件

原文:

[转载]C#委托和事件(Delegate、Event、EventHandler、EventArgs),

 

14.2.1.2、声明一个基于某个委托的事件

Event delegateName  eventName;

eventName不是一个类型,而是一个具体的对象,这个具体的对象只能在类A内定义而不能在类A外定义。

原文作者: Shivprasad koirala 

14.2.1.4、初始化A类的事件

在类B中定义一个类A的对象,并且让类A对象的那个事件指向类B中定义的方法,这个方法要与事件关联的委托所限定的方法吻合。

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

 

上面例子的第一个问题就是, 消费者并没有权利来选择订阅或是不订阅,因为这个过程是由“Form1”也就是发布者来决定的。 

我们可以用其他方式, 把委托传递给消费者, 让消费者来决定他们要不要订阅来自发布者(Form1)的多播委托。 但是, 这种做法会引发另个问题: 破坏封装。 如果我们把委托暴露给消费者, 就意味着委托完全裸露在了消费者面前。 

67677新澳门手机版 6

 

14.1.5.5、多播委托的异常处理

通过一个委托调用多个方法还有一个大问题。多播委托包含一个逐个调用的委托集合。如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。下面是MulticastIteration示例。其中定义了一个简单的委托DemoDelegate,它没有参数,返回void。这个委托调用方法One()和Two(),这两个方法满足委托的参数和返回类型要求。注意方法One()抛出了一个异常:

using System;

namespace Wrox.ProCSharp.Delegates

{

public delegate void DemoDelegate();

class Program

{

static void One()

{

Console.WriteLine("One");

throw new Exception("Error in one");

}

static void Two()

{

Console.WriteLine("Two");

}

在Main()方法中,创建了委托d1,它引用方法One(),接着把Two()方法的地址添加到同一个委托中。调用d1委托,就可以调用这两个方法。异常在try/catch块中捕获:

static void Main()

{

DemoDelegate d1 = One;

d1 = Two;

try

{

d1();

}

catch (Exception)

{

Console.WriteLine("Exception caught");

}

}

}

}

委托只调用了第一个方法。第一个方法抛出了异常,所以委托的迭代会停止,不再调用Two()方法。当调用方法的顺序没有指定时,结果会有所不同。

One

Exception Caught

注意:

多播委托包含一个逐个调用的委托集合。如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。即如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。

在这种情况下,为了避免这个问题,应手动迭代方法列表。Delegate类定义了方法GetInvocationList(),它返回一个Delegate对象数组。现在可以使用这个委托调用与委托直接相关的方法,捕获异常,并继续下一次迭代。

static void Main()

{

DemoDelegate d1 = One;

d1 = Two;

Delegate[] delegates = d1.GetInvocationList();

foreach (DemoDelegate d in delegates)

{

try

{

d();

}

catch (Exception)

{

Console.WriteLine("Exception caught");

}

}

}

修改了代码后运行应用程序,会看到在捕获了异常后,将继续迭代下一个方法。

One

Exception caught

Two

注意:其实如果在多播委托的每个具体的方法中捕获异常,并在内部处理,而不抛出异常,一样能实现多播委托的所有方法执行完毕。这种方式与上面方式的区别在于这种方式的宜昌市在函数内部处理的,上面那种方式的异常是在函数外面捕获并处理的。

多播委托

在我们之前的例子中,我们已经知道了如何创建委托变量和绑定具体实现方法到变量上。但实际上, 我们可以给一个委托附上若干个具体实现方法。如果我们调用这样的委托, 那么附到委托上的函数会顺序执行。(至于如果函数有返回值, 那么只有最后一个函数的返回值会被捕捉到)

1 // Associate method1
2 delegateptr  = Method1;
3 // Associate Method2
4 delegateptr  = Method2;
5 // Invoke the Method1 and Method2 sequentially
6 delegateptr.Invoke();

 

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

67677新澳门手机版 7

 

14.1、委托

当要把方法作为实参传送给其他方法的形参时,形参需要使用委托。委托是一个类型,是一个函数指针类型,这个类型将该委托的实例化对象所能指向的函数的细节封装起来了,即规定了所能指向的函数的签名,也就是限制了所能指向的函数的参数和返回值。当实例化委托的时候,委托对象会指向某一个匹配的函数,实质就是将函数的地址赋值给了该委托的对象,然后就可以通过该委托对象来调用所指向的函数了。利用委托,程序员可以在委托对象中封装一个方法的引用,然后委托对象作为形参将被传给调用了被引用方法的代码,而不需要知道在编译时刻具体是哪个方法被调用。

一般的调用函数,我们都不会去使用委托,因为如果只是单纯的调用函数,使用委托更麻烦一些;但是如果想将函数作为实参,传递给某个函数的形参,那么形参就一定要使用委托来接收实参,一般使用方法是:在函数外面定义委托对象,并指向某个函数,再将这个对象赋值给函数的形参,形参也是该委托类型的对象变量,函数里面再通过形参来调用所指向的函数。

实现事件

我们来把多播委托的例子改造成事件的方式。 第一步是在发布者“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;

 

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

 

14.1.3、委托推断

C# 2.0用委托推断扩展了委托的语法。当我们需要定义委托对象并实例化委托的时候,就可以只传送函数的名称,即函数的地址:

Identifier  objectName  =  functionName;

这里面的functionName与14.1.2节中实例化委托的functionName是一样的,没什么区别,满足上面的规则。

C#编译器创建的代码是一样的。编译器会用objectName检测需要的委托类型,因此会创建Identifier委托类型的一个实例,用functionName即方法的地址传送给Identifier的构造函数。

注意:

不能在functionName后面加括号和实参,然后把它传送给委托变量。调用方法一般会返回一个不能赋予委托变量的普通对象,除非这个方法返回的是一个匹配的委托对象。总之:只能把相匹配的方法的地址赋予委托变量。

委托推断可以在需要委托实例化的任何地方使用,就跟定义普通的委托对象是一样的。委托推断也可以用于事件,因为事件基于委托(参见本章后面的内容)。

委托和事件的不同

 

所以, 如果事件不是委托的语法糖那么他们之间的区别在哪?  我们在上文中已经提到了一个主要的区别: 事件比委托多了一层封装。因此, 如果我们传递委托, 那么消费者接受的是一个赤裸裸的委托, 用户可以修改委托的信息。 而我们使用事件,那么用户只能监听事件而不能修改它。 

67677新澳门手机版 8

函数的异步委托调用

委托的另一种用法是异步函数的调用。 你能够异步的调用委托指向的函数。 

 

异步调用意味着客户端调用委托之后, 代码的控制权又立即回到了客户端手中以继续执行后续的代码。 

委托携带者调用者的信息在parallel的线程池中启用新的线程执行具体的函数, 当委托执行结束后, 它会发出信息通知客户端(调用者)。 

67677新澳门手机版 9

为了能够异步得调用函数, 我们需要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 }

 

14.1.6、通过委托对象来调用它所指向的函数

1、委托实例的名称,后面的括号中应包含调用该委托中的方法时使用的参数。

2、调用委托对象的Invoke()方法,Invoke后面的括号中应包含调用该委托中的方法时使用的参数。

注意:实际上,给委托实例提供括号与调用委托类的Invoke()方法完全相同。因为Invoke()方法是委托的同步调用方法。

 

注意:不管是多播委托还是单播委托,在没有特殊处理的情况下,在一个线程的执行过程中去调用委托(委托对象所指向的函数),调用委托的执行是不会新起线程的,这个执行还是在原线程中的,这个对于事件也是一样的。当然,如果是在委托所指向的函数里面去启动一个新的线程那就是另外一回事了。

事件 -- 委托的封装

事件能解决委托的封装问题。 事件包裹在委托之外, 使得消费者只能接收但不会有委托的完全控制权。

下图是对这一概念的图解:

  1. 具体的实现方法被委托抽象和封装了

  2. 委托被多播委托进一步封装了以提供广播的效果

  3. 事件进一步封装了多播委托

67677新澳门手机版 10

 

14.2.2.1、控件事件委托EventHandler

在控件事件中,有很多的委托,在这里介绍一个最常用的委托EventHandler,.NET Framework中控件的事件很多都基于该委托,EventHandler委托已在.NET Framework中定义了。它位于System命名空间:

Public delegate void EventHandler(object sender,EventArgs e);

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

为了解耦算法的变化, 我们使用一个抽象的指针指向所有的算法:(因为这四个方法的格式是一致的)

 

67677新澳门手机版 11

 

第一步, 在实现类中定义一个委托如下:(注意输入输出参数的格式)

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);

 

 

14.2、事件

介绍

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

 

在文章的最后,我们能能总结出委托的六种重要用处。

 

14. 3、小结

(1)、在定义事件的那个类A里面,可以任意的使用事件名,可以触发;在别的类里面,事件名只能出现在 = 或 -= 的左边来指向函数,即只能实例化,不能直接用事件名触发。但是可以通过A类的对象来调用A类中的触发事件的函数。这是唯一触发事件的方式。

(2)、不管是多播委托还是单播委托,在没有特殊处理的情况下,在一个线程的执行过程中去调用委托(委托对象所指向的函数),调用委托的执行是不会新起线程的,这个执行还是在原线程中的,这个对于事件也是一样的。当然,如果是在委托所指向的函数里面去启动一个新的线程那就是另外一回事了。

(3)、事件是针对某一个具体的对象的,一般在该对象的所属类A中写好事件,并且写好触发事件的方法,那么这个类A就是事件的发布者,然后在别的类B里面定义A的对象,并去初始化该对象的事件,让事件指向B类中的某一个具体的方法,B类就是A类事件的订阅者。当通过A类的对象来触发A类的事件的时候(只能A类的对象来触发A类的事件,别的类的对象不能触发A类的事件,只能订阅A类的事件,即实例化A类的事件),作为订阅者的B类会接收A类触发的事件,从而使得订阅函数被执行。一个发布者可以有多个订阅者,当发布者发送事件的时候,所有的订阅者都将接收到事件,从而执行订阅函数,但是即使是有多个订阅者也是单线程。

原文链接: 14.1、委托 当要把方法作为实...

多播委托的简单例子

我们可以通过下面这个例子更好的理解多播委托。 在这个窗体项目中,我们有“Form1”, “Form2”, “Form3”。

“Form1中有一个多播委托来把动作的影响传递到“Form2”和“Form3”中。

67677新澳门手机版 12

 

在"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 }

 

 

14.1.5.4、委托运算符  、-:

Identifier  objectName  =  objectName  functionName1 - functionName1;或者

Identifier  objectName  =  new  Identifier( functionName1) functionName1 - functionName1;

对于这种 、-表达式,在第一个符号 或者-的前面必须是委托而不能是方法,后面的 、-左右都随便。这个不是绝对规律,还有待进一步的研究。

总结委托的用法

委托有5种重要的使用方式:(译者注: 原文写的6种, 我只看到了5种)

1. 抽象、封装一个方法(匿名调用)

    这是委托最重要的功能, 它帮助我们定义一个能够指向函数的抽象的指针。 同时, 这个指针还可以指向其他符合其规范的函数。 

开头的时候我们展示的一个math类, 之后我们使用一个抽象指针就把该math类中添加的所有函数都包括在内了。 这个例子就很好的说明了委托的这一使用方法。 

  1. 回调机制

    客户端可以使用委托来创建回调函数。 

  1. 异步执行

    使用BeginInvoke 和 EndInvoke 我们可以异步得调用所有的委托。 

  1. 多点广播

    有的时候, 我们希望能够让函数顺序执行, 那么多播委托就可以做到这一点。 

  1. 事件

    事件可以帮助我们方便的建立 发布/订阅 模式。 

 

   

14.2.1.1、声明一个委托:

Delegate result-type delegateName ([parameters]);

这个委托可以在类A内定义也可以在类A外定义。

本文由67677新澳门手机版发布于计算机编程,转载请注明出处:【67677新澳门手机版】委托和事件,关于委托和事

关键词: