您的位置:新葡亰496net > 奥门新萄京娱乐场 > 新葡亰496net委托和事件,关于委托和事件的使用

新葡亰496net委托和事件,关于委托和事件的使用

发布时间:2019-08-23 12:58编辑:奥门新萄京娱乐场浏览(75)

    原文:

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

    原文链接:

    原文链接:

    ---初级---一、C#简介:

    原文作者: Shivprasad koirala 

    原文链接:

    14.1、委托

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

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

    14.1、委托

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

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

    1. C# 是一个简单的、现代的、通用的、面向对象的编程语言,它是由微软(Microsoft)开发的。

     

    14.1、委托

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

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

    14.1.1、定义委托

    语法如下

    delegate  result-type   Identifier ([parameters]);

    说明:

    result-type:返回值的类型,和方法的返回值类型一致

    Identifier:委托的名称

    parameters:参数,要引用的方法带的参数

    小结

    当定义了委托之后,该委托的对象一定可以而且也只能指向该委托所限制的函数。即参数的个数、类型、顺序都要匹配,返回值的类型也要匹配。

    因为定义委托相当于是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在一个类的内部定义,那么此时就要通过该类的类名来调用这个委托(委托必须是public、internal),也可以在任何类的外部定义,那么此时在命名空间中与类的级别是一样的。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:当委托定义在类的外面,那么可以加上public、internal修饰符;如果委托定义到类的内部,那么可以加上public、 private、 protected、internal。一般委托都是定义在类的外面的。

    14.1.1、定义委托

    语法如下

    delegate  result-type   Identifier ([parameters]);

    说明:

    result-type:返回值的类型,和方法的返回值类型一致

    Identifier:委托的名称

    parameters:参数,要引用的方法带的参数

    小结

    当定义了委托之后,该委托的对象一定可以而且也只能指向该委托所限制的函数。即参数的个数、类型、顺序都要匹配,返回值的类型也要匹配。

    因为定义委托相当于是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在一个类的内部定义,那么此时就要通过该类的类名来调用这个委托(委托必须是public、internal),也可以在任何类的外部定义,那么此时在命名空间中与类的级别是一样的。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:当委托定义在类的外面,那么可以加上public、internal修饰符;如果委托定义到类的内部,那么可以加上public、 private、 protected、internal。一般委托都是定义在类的外面的。

    二、C#环境:

    介绍

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

     

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

     

    14.1.1、定义委托

    语法如下

    delegate  result-type   Identifier ([parameters]);

    说明:

    result-type:返回值的类型,和方法的返回值类型一致

    Identifier:委托的名称

    parameters:参数,要引用的方法带的参数

    小结

    当定义了委托之后,该委托的对象一定可以而且也只能指向该委托所限制的函数。即参数的个数、类型、顺序都要匹配,返回值的类型也要匹配。

    因为定义委托相当于是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在一个类的内部定义,那么此时就要通过该类的类名来调用这个委托(委托必须是public、internal),也可以在任何类的外部定义,那么此时在命名空间中与类的级别是一样的。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:当委托定义在类的外面,那么可以加上public、internal修饰符;如果委托定义到类的内部,那么可以加上public、 private、 protected、internal。一般委托都是定义在类的外面的。

    14.1.2、实例化委托

    Identifier  objectName  =  new  Identifier( functionName);

    实例化委托的实质就是将某个函数的地址赋值给委托对象。在这里:

    Identifier :这个是委托名字。

    objectName :委托的实例化对象。

    functionName:是该委托对象所指向的函数的名字。对于这个函数名要特别注意:定义这个委托对象肯定是在类中定义的,那么如果所指向的函数也在该类中,不管该函数是静态还是非静态的,那么就直接写函数名字就可以了;如果函数是在别的类里面定义的public、

    internal,但是如果是静态,那么就直接用类名.函数名,如果是非静态的,那么就类的对象名.函数名,这个函数名与该对象是有关系的,比如如果函数中出现了this,表示的就是对当前对象的调用。

    14.1.2、实例化委托

    Identifier  objectName  =  new  Identifier( functionName);

    实例化委托的实质就是将某个函数的地址赋值给委托对象。在这里:

    Identifier :这个是委托名字。

    objectName :委托的实例化对象。

    functionName:是该委托对象所指向的函数的名字。对于这个函数名要特别注意:定义这个委托对象肯定是在类中定义的,那么如果所指向的函数也在该类中,不管该函数是静态还是非静态的,那么就直接写函数名字就可以了;如果函数是在别的类里面定义的public、

    internal,但是如果是静态,那么就直接用类名.函数名,如果是非静态的,那么就类的对象名.函数名,这个函数名与该对象是有关系的,比如如果函数中出现了this,表示的就是对当前对象的调用。

    1. .Net 框架应用程序是多平台的应用程序。框架的设计方式使它适用于下列各种语言:C#、C 、Visual Basic、Jscript、COBOL 等等。所有这些语言可以访问框架,彼此之间也可以互相交互。

    方法和函数的抽象问题

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

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

     

    新葡亰496net 1

     

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

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

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

     

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

    新葡亰496net 2

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

     

    14.1.2、实例化委托

    Identifier  objectName  =  new  Identifier( functionName);

    实例化委托的实质就是将某个函数的地址赋值给委托对象。在这里:

    Identifier :这个是委托名字。

    objectName :委托的实例化对象。

    functionName:是该委托对象所指向的函数的名字。对于这个函数名要特别注意:定义这个委托对象肯定是在类中定义的,那么如果所指向的函数也在该类中,不管该函数是静态还是非静态的,那么就直接写函数名字就可以了;如果函数是在别的类里面定义的public、

    internal,但是如果是静态,那么就直接用类名.函数名,如果是非静态的,那么就类的对象名.函数名,这个函数名与该对象是有关系的,比如如果函数中出现了this,表示的就是对当前对象的调用。

    14.1.3、委托推断

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

    Identifier  objectName  =  functionName;

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

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

    注意:

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

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

    14.1.3、委托推断

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

    Identifier  objectName  =  functionName;

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

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

    注意:

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

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

    三、C#程序结构1.注意:a.C#大小写敏感b.所有的语句和表达式必须以分号结尾c.程序的执行从Main方法开始d.与Java不同的是,文件名可以不同于类的名称2.第一个小程序----helloworld

    如何创建一个委托

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

    新葡亰496net 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);
    

     

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

     新葡亰496net 4

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

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

     

    最后就是调用了:

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

     

    下图为实例代码:

    新葡亰496net 5

     

    14.1.3、委托推断

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

    Identifier  objectName  =  functionName;

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

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

    注意:

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

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

    14.1.4、匿名方法

    到目前为止,要想使委托工作,方法必须已经存在。但实例化委托还有另外一种方式:即通过匿名方法。

    用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就有区别了。下面是一个非常简单的控制台应用程序,说明了如何使用匿名方法:

    using System;

    namespace Wrox.ProCSharp.Delegates

    {

      class Program

      {

        delegate string DelegateTest(string val);

        static void Main()

        {

          string mid = ", middle part,";

          //在方法中定义了方法

          DelegateTest  anonDel = delegate(string param)

          {

            param = mid;

            param = " and this was added to the string.";

            return param;

          };

          Console.WriteLine(anonDel("Start of string"));

        }

      }

    }

    委托DelegateTest在类Program中定义,它带一个字符串参数。有区别的是Main方法。在定义anonDel时,不是传送已知的方法名,而是使用一个简单的代码块:它前面是关键字delegate,后面是一个参数:

    delegate(string param)

    {

      param = mid;

      param = " and this was added to the string.";

      return param;

    };

    匿名方法的优点是减少了要编写的代码。方法仅在有委托使用时才定义。在为事件定义委托时,这是非常显然的。(本章后面探讨事件。)这有助于降低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行得不太快。编译器仍定义了一个方法,该方法只有一个自动指定的名称,我们不需要知道这个名称。

    在使用匿名方法时,必须遵循两个规则:

    1、在匿名方法中不能使用跳转语句跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该匿名方法的内部。

    2、在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。方法内部的变量、方法的参数可以任意的使用。

    如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。而编写一个指定的方法比较好,因为该方法只需编写一次,以后可通过名称引用它。

    14.1.4、匿名方法

    到目前为止,要想使委托工作,方法必须已经存在。但实例化委托还有另外一种方式:即通过匿名方法。

    用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就有区别了。下面是一个非常简单的控制台应用程序,说明了如何使用匿名方法:

    using System;

    namespace Wrox.ProCSharp.Delegates

    {

      class Program

      {

        delegate string DelegateTest(string val);

        static void Main()

        {

          string mid = ", middle part,";

          //在方法中定义了方法

          DelegateTest  anonDel = delegate(string param)

          {

            param = mid;

            param = " and this was added to the string.";

            return param;

          };

          Console.WriteLine(anonDel("Start of string"));

        }

      }

    }

    委托DelegateTest在类Program中定义,它带一个字符串参数。有区别的是Main方法。在定义anonDel时,不是传送已知的方法名,而是使用一个简单的代码块:它前面是关键字delegate,后面是一个参数:

    delegate(string param)

    {

      param = mid;

      param = " and this was added to the string.";

      return param;

    };

    匿名方法的优点是减少了要编写的代码。方法仅在有委托使用时才定义。在为事件定义委托时,这是非常显然的。(本章后面探讨事件。)这有助于降低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行得不太快。编译器仍定义了一个方法,该方法只有一个自动指定的名称,我们不需要知道这个名称。

    在使用匿名方法时,必须遵循两个规则:

    1、在匿名方法中不能使用跳转语句跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该匿名方法的内部。

    2、在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。方法内部的变量、方法的参数可以任意的使用。

    如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。而编写一个指定的方法比较好,因为该方法只需编写一次,以后可通过名称引用它。

    新葡亰496net 6helloworld.png

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

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

     

    新葡亰496net 7

     

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

    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.1.4、匿名方法

    到目前为止,要想使委托工作,方法必须已经存在。但实例化委托还有另外一种方式:即通过匿名方法。

    用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就有区别了。下面是一个非常简单的控制台应用程序,说明了如何使用匿名方法:

    using System;

    namespace Wrox.ProCSharp.Delegates

    {

      class Program

      {

        delegate string DelegateTest(string val);

        static void Main()

        {

          string mid = ", middle part,";

          //在方法中定义了方法

          DelegateTest  anonDel = delegate(string param)

          {

            param = mid;

            param = " and this was added to the string.";

            return param;

          };

          Console.WriteLine(anonDel("Start of string"));

        }

      }

    }

    委托DelegateTest在类Program中定义,它带一个字符串参数。有区别的是Main方法。在定义anonDel时,不是传送已知的方法名,而是使用一个简单的代码块:它前面是关键字delegate,后面是一个参数:

    delegate(string param)

    {

      param = mid;

      param = " and this was added to the string.";

      return param;

    };

    匿名方法的优点是减少了要编写的代码。方法仅在有委托使用时才定义。在为事件定义委托时,这是非常显然的。(本章后面探讨事件。)这有助于降低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行得不太快。编译器仍定义了一个方法,该方法只有一个自动指定的名称,我们不需要知道这个名称。

    在使用匿名方法时,必须遵循两个规则:

    1、在匿名方法中不能使用跳转语句跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该匿名方法的内部。

    2、在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。方法内部的变量、方法的参数可以任意的使用。

    如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。而编写一个指定的方法比较好,因为该方法只需编写一次,以后可通过名称引用它。

    14.1.5、多播委托

    前面使用的每个委托都只包含一个方法调用,调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次给委托赋值,然后调用这个委托。

    委托也可以包含多个方法,这时候要向委托对象中添加多个方法,这种委托称为多播委托,多播委托有一个方法列表,如果调用多播委托,就可以连续调用多个方法,即先执行某一个方法,等该方法执行完成之后再执行另外一个方法,这些方法的参数都是一样的,这些方法的执行是在一个线程中执行的,而不是每个方法都是一个线程,最终将执行完成所有的方法。

    如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,调用顺序是不确定的,不一定是按照添加方法的顺序来调用方法,因此应避免编写依赖于以特定顺序调用方法的代码。如果要想确定顺序,那么只能是单播委托,调用委托的次数与调用方法的次数相同。

    多播委托的各个方法签名最好是返回void;否则,就只能得到委托最后调用的一个方法的结果,而最后调用哪个方法是无法确定的。

    多播委托的每一个方法都要与委托所限定的方法的返回值、参数匹配,否则就会有错误。

    我自己写代码测试,测试的结果目前都是调用顺序和加入委托的顺序相同的,但是不排除有不同的时候。 

    delegate result-type Identifier ([parameters]); 

    14.1.5、多播委托

    前面使用的每个委托都只包含一个方法调用,调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次给委托赋值,然后调用这个委托。

    委托也可以包含多个方法,这时候要向委托对象中添加多个方法,这种委托称为多播委托,多播委托有一个方法列表,如果调用多播委托,就可以连续调用多个方法,即先执行某一个方法,等该方法执行完成之后再执行另外一个方法,这些方法的参数都是一样的,这些方法的执行是在一个线程中执行的,而不是每个方法都是一个线程,最终将执行完成所有的方法。

    如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,调用顺序是不确定的,不一定是按照添加方法的顺序来调用方法,因此应避免编写依赖于以特定顺序调用方法的代码。如果要想确定顺序,那么只能是单播委托,调用委托的次数与调用方法的次数相同。

    多播委托的各个方法签名最好是返回void;否则,就只能得到委托最后调用的一个方法的结果,而最后调用哪个方法是无法确定的。

    多播委托的每一个方法都要与委托所限定的方法的返回值、参数匹配,否则就会有错误。

    我自己写代码测试,测试的结果目前都是调用顺序和加入委托的顺序相同的,但是不排除有不同的时候。 

    delegate result-type Identifier ([parameters]); 

    代码:

    多播委托

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

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

     

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

    新葡亰496net 8

     

    14.1.5、多播委托

    前面使用的每个委托都只包含一个方法调用,调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次给委托赋值,然后调用这个委托。

    委托也可以包含多个方法,这时候要向委托对象中添加多个方法,这种委托称为多播委托,多播委托有一个方法列表,如果调用多播委托,就可以连续调用多个方法,即先执行某一个方法,等该方法执行完成之后再执行另外一个方法,这些方法的参数都是一样的,这些方法的执行是在一个线程中执行的,而不是每个方法都是一个线程,最终将执行完成所有的方法。

    如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,调用顺序是不确定的,不一定是按照添加方法的顺序来调用方法,因此应避免编写依赖于以特定顺序调用方法的代码。如果要想确定顺序,那么只能是单播委托,调用委托的次数与调用方法的次数相同。

    多播委托的各个方法签名最好是返回void;否则,就只能得到委托最后调用的一个方法的结果,而最后调用哪个方法是无法确定的。

    多播委托的每一个方法都要与委托所限定的方法的返回值、参数匹配,否则就会有错误。

    我自己写代码测试,测试的结果目前都是调用顺序和加入委托的顺序相同的,但是不排除有不同的时候。 

    delegate result-type Identifier ([parameters]); 

    14.1.5.1、委托运算符 =

    Identifier  objectName  =  new  Identifier( functionName);

    或者

    Identifier  objectName  =  functionName;

    这里的“=”号表示清空 objectName 的方法列表,然后将 functionName 加入到     objectName 的方法列表中。

    14.1.5.1、委托运算符 =

    Identifier  objectName  =  new  Identifier( functionName);

    或者

    Identifier  objectName  =  functionName;

    这里的“=”号表示清空 objectName 的方法列表,然后将 functionName 加入到     objectName 的方法列表中。

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace 菜鸟教程{ class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); Console.ReadLine(); } }}
    

    多播委托的简单例子

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

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

    新葡亰496net 9

     

    在"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.1、委托运算符 =

    Identifier  objectName  =  new  Identifier( functionName);

    或者

    新葡亰496net委托和事件,关于委托和事件的使用。Identifier  objectName  =  functionName;

    这里的“=”号表示清空 objectName 的方法列表,然后将 functionName 加入到     objectName 的方法列表中。

    14.1.5.2、委托运算符  =

    objectName   =  new  Identifier( functionName1);

    或者

    objectName   =  functionName1;

    这里的“ =”号表示在原有的方法列表不变的情况下,将 functionName1  加入到     objectName 的方法列表中。可以在方法列表中加上多个相同的方法,执行的时候也会执行完所有的函数,哪怕有相同的,就会多次执行同一个方法。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

    Identifier  objectName    =  new  Identifier( functionName1);或者

    Identifier  objectName  =  functionName1;就会报错。

    14.1.5.2、委托运算符  =

    objectName   =  new  Identifier( functionName1);

    或者

    objectName   =  functionName1;

    这里的“ =”号表示在原有的方法列表不变的情况下,将 functionName1  加入到     objectName 的方法列表中。可以在方法列表中加上多个相同的方法,执行的时候也会执行完所有的函数,哪怕有相同的,就会多次执行同一个方法。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

    Identifier  objectName    =  new  Identifier( functionName1);或者

    Identifier  objectName  =  functionName1;就会报错。

    四、C#基本语法

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

     

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

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

    新葡亰496net 10

     

    14.1.5.2、委托运算符  =

    objectName   =  new  Identifier( functionName1);

    或者

    objectName   =  functionName1;

    这里的“ =”号表示在原有的方法列表不变的情况下,将 functionName1  加入到     objectName 的方法列表中。可以在方法列表中加上多个相同的方法,执行的时候也会执行完所有的函数,哪怕有相同的,就会多次执行同一个方法。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

    Identifier  objectName    =  new  Identifier( functionName1);或者

    Identifier  objectName  =  functionName1;就会报错。

    14.1.5.3、委托运算符 -=:

    objectName  -=  new  Identifier( functionName1);

    或者

    objectName  -=  functionName1;

    这里的“-=”号表示在 objectName 的方法列表中减去一个functionName1。可以在方法列表中多次减去相同的方法,减一次只会减一个方法,如果列表中无此方法,那么减就没有意义,对原有列表无影响,也不会报错。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

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

    Identifier  objectName  -=  functionName1;就会报错。

    14.1.5.3、委托运算符 -=:

    objectName  -=  new  Identifier( functionName1);

    或者

    objectName  -=  functionName1;

    这里的“-=”号表示在 objectName 的方法列表中减去一个functionName1。可以在方法列表中多次减去相同的方法,减一次只会减一个方法,如果列表中无此方法,那么减就没有意义,对原有列表无影响,也不会报错。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

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

    Identifier  objectName  -=  functionName1;就会报错。

    1. C# 是一种面向对象的编程语言。在面向对象的程序设计方法中,程序由各种相互交互的对象组成。相同种类的对象通常具有相同的类型,或者说,是在相同的 class 中。2.矩形类的实现

    事件 -- 委托的封装

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

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

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

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

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

    新葡亰496net 11

     

    14.1.5.3、委托运算符 -=:

    objectName  -=  new  Identifier( functionName1);

    或者

    objectName  -=  functionName1;

    这里的“-=”号表示在 objectName 的方法列表中减去一个functionName1。可以在方法列表中多次减去相同的方法,减一次只会减一个方法,如果列表中无此方法,那么减就没有意义,对原有列表无影响,也不会报错。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

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

    新葡亰496net委托和事件,关于委托和事件的使用。Identifier  objectName  -=  functionName1;就会报错。

    14.1.5.4、委托运算符  、-:

    Identifier  objectName  =  objectName  functionName1 - functionName1;或者

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

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

    14.1.5.4、委托运算符  、-:

    Identifier  objectName  =  objectName  functionName1 - functionName1;或者

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

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

    新葡亰496net 12矩形类的实现

    实现事件

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

    Identifier  objectName  =  objectName  functionName1 - functionName1;或者

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

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

    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

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

    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

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

    代码:

    委托和事件的不同

     

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

    新葡亰496net 13

    函数的异步委托调用

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

     

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

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

    新葡亰496net 14

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

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

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

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

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

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

     

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

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

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

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

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

     

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

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace 例二_矩形类的实现{ class Rectangle { //成员变量 double length; double width; public void Acceptdetails() { length = 4.5; width = 3.5; } public double GetArea() { return length * width; } public void Display() { Console.WriteLine("length:{0}", length); Console.WriteLine("Width:{0}", width); Console.WriteLine("Area:{0}",GetArea; } } class ExecuteRectangle { static void Main(string[] args) { Rectangle r = new Rectangle(); r.Acceptdetails(); r.Display(); Console.ReadLine(); } }}
    

    总结委托的用法

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

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

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

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

    1. 回调机制

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

    1. 异步执行

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

    1. 多点广播

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

    1. 事件

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

     

       

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

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

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

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

     

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

    14.2、事件

    14.2、事件

    五、C#数据类型1.值类型,引用类型,指针类型等。2.bool 布尔值byte 8位无符号整数char 16位Unicode字符decimal 128位十进制值double 64位双精度浮点型float 32位单精度浮点型Int 32位有符号整数类型long 64位有符号整数类型sbyte 8位有符号short 16有符号uint 32无符号ulong 64位无符号ushort 16位无符号3.引用类型:引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。内置的 引用类型有:object、dynamic 和 string。六、封装

    14.2、事件

    14.2.1、自定义事件

    14.2.1、自定义事件

    1. 封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使程序员实现所需级别的抽象。封装使用 访问修饰符 来实现。一个 访问修饰符 定义了一个类成员的范围和可见性。2.访问修饰符:Public: Public 访问修饰符允许一个类将其成员变量和成员函数暴露给其他的函数和对象。任何公有成员可以被外部的类访问。Private: Private 访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。Protected: Protected 访问修饰符允许子类访问它的基类的成员变量和成员函数。这样有助于实现继承。Internal: Internal 访问说明符允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。换句话说,带有 internal 访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类或方法访问。Protected Internal: Protected Internal 访问修饰符允许一个类将其成员变量和成员函数对同一应用程序内的子类以外的其他的类对象和函数进行隐藏。这也被用于实现继承。七、C# 可空类型
    2. C# 提供了一个特殊的数据类型,nullable 类型,可空类型可以表示其基础值类型正常范围内的值,再加上一个 null 值。八、C#类C#中的析构函数:类的 析构函数 是类的一个特殊的成员函数,当类的对象超出范围时执行。析构函数的名称是在类的名称前加上一个波浪形作为前缀,它不返回值,也不带任何参数。析构函数用于在结束程序(比如关闭文件、释放内存等)之前释放资源。析构函数不能继承或重载。九、C#多态多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。---高级---十、C# 特性(Attribute)特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号来描述的。特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。十一、C#反射反射指程序可以访问、检测和修改它本身状态或行为的一种能力。程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。优缺点优点:1、反射提高了程序的灵活性和扩展性。2、降低耦合性,提高自适应能力。3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。缺点:1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。反射(Reflection)的用途反射(Reflection)有下列用途:它允许在运行时查看属性(attribute)信息。它允许审查集合中的各种类型,以及实例化这些类型。它允许延迟绑定的方法和属性。它允许在运行时创建新类型,然后使用这些类型执行一些任务。十二、属性属性 是类、结构(structure)和接口(interface)的命名成员。类或结构中的成员变量或方法称为 域。属性是域的扩展,且可使用相同的语法来访问。它们使用 访问器(accessors) 让私有域的值可被读写或操作。属性不会确定存储位置。相反,它们具有可读写或计算它们值的 访问器(accessors)。十三、C# 委托C# 中的委托类似于 C 或 C 中函数的指针。委托 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。委托特别用于实现事件和回调方法。所有的委托都派生自 System.Delegate 类。

    14.2.1、自定义事件

    14.2.1.1、声明一个委托:

    Delegate result-type delegateName ([parameters]);

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

    14.2.1.1、声明一个委托:

    Delegate result-type delegateName ([parameters]);

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

    委托的多播(Multicasting of a Delegate)委托对象可使用 " " 运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。"-" 运算符可用于从合并的委托中移除组件委托。使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表。这被称为委托的 多播(multicasting),也叫组播。

    14.2.1.1、声明一个委托:

    Delegate result-type delegateName ([parameters]);

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

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

    Event delegateName  eventName;

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

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

    Event delegateName  eventName;

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

    委托的用途下面的实例演示了委托的用法。委托 printString 可用于引用带有一个字符串作为输入的方法,并不返回任何东西。

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

    Event delegateName  eventName;

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

    14.2.1.3、在类A中定义一个触发该事件的方法

    ReturnType  FunctionName([parameters])

    {

         ……

    If(eventName != null)

    {

    eventName([parameters]);

    或者eventName.Invoke([parameters]);

    }

    ……

    }

    触发事件之后,事件所指向的函数将会被执行。这种执行是通过事件名称来调用的,就像委托对象名一样的。

    触发事件的方法只能在A类中定义,事件的实例化,以及实例化之后的实现体都只能在A类外定义。

    14.2.1.3、在类A中定义一个触发该事件的方法

    ReturnType  FunctionName([parameters])

    {

         ……

    If(eventName != null)

    {

    eventName([parameters]);

    或者eventName.Invoke([parameters]);

    }

    ……

    }

    触发事件之后,事件所指向的函数将会被执行。这种执行是通过事件名称来调用的,就像委托对象名一样的。

    触发事件的方法只能在A类中定义,事件的实例化,以及实例化之后的实现体都只能在A类外定义。

    十四、C# 事件事件 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些出现,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。事件是用于进程间通信。通过事件使用委托事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法。十五、C# 多线程线程 被定义为程序的执行路径。每个线程都定义了一个独特的控制流。如果您的应用程序涉及到复杂的和耗时的操作,那么设置不同的线程执行路径往往是有益的,每个线程执行特定的工作。线程是轻量级进程。一个使用线程的常见实例是现代操作系统中并行编程的实现。使用线程节省了 CPU 周期的浪费,同时提高了应用程序的效率。到目前为止我们编写的程序是一个单线程作为应用程序的运行实例的单一的过程运行的。但是,这样子应用程序同时只能执行一个任务。为了同时执行多个任务,它可以被划分为更小的线程。

    14.2.1.3、在类A中定义一个触发该事件的方法

    ReturnType  FunctionName([parameters])

    {

         ……

    If(eventName != null)

    {

    eventName([parameters]);

    或者eventName.Invoke([parameters]);

    }

    ……

    }

    触发事件之后,事件所指向的函数将会被执行。这种执行是通过事件名称来调用的,就像委托对象名一样的。

    触发事件的方法只能在A类中定义,事件的实例化,以及实例化之后的实现体都只能在A类外定义。

    14.2.1.4、初始化A类的事件

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

    14.2.1.4、初始化A类的事件

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

    14.2.1.4、初始化A类的事件

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

    14.2.1.5、触发A类的事件

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

    14.2.1.5、触发A类的事件

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

    14.2.1.5、触发A类的事件

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

    14.2.1.6、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

    using System.Windows.Forms;

    namespace DelegateStudy

    {

        public delegate void DelegateClick (int a);

        public class butt

        {

            public event DelegateClick Click;

            public void OnClick(int a)

            {

                if(Click != null)

                    Click.Invoke(a);

                   //Click(a);//这种方式也是可以的

                MessageBox.Show("Click();");

            }

        }

        class Frm

        {

            public static void Btn_Click(int a)

            {

                for (long i = 0; i < a; i )

                    Console.WriteLine(i.ToString());

            }

            static void Main(string[] args)

            {

                butt b = new butt();

               //在委托中,委托对象如果是null的,直接使用 =符号,会报错,但是在事件中,初始化的时候,只能用 =

                b.Click = new DelegateClick (Fm_Click); //事件是基于委托的,所以委托推断一样适用,下面的语句一样有效:b.Click = Fm_Click;

                //b.Click(10);错误:事件“DelegateStudy.butt.Click”只能出现在 = 或 -= 的左边(从类型“DelegateStudy.butt”中使用时除外)

                b.OnClick (10000);                       

                MessageBox.Show("sd234234234");

                Console.ReadLine();

            }

       }

    }

    14.2.1.6、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

    using System.Windows.Forms;

    namespace DelegateStudy

    {

        public delegate void DelegateClick (int a);

        public class butt

        {

            public event DelegateClick Click;

            public void OnClick(int a)

            {

                if(Click != null)

                    Click.Invoke(a);

                   //Click(a);//这种方式也是可以的

                MessageBox.Show("Click();");

            }

        }

        class Frm

        {

            public static void Btn_Click(int a)

            {

                for (long i = 0; i < a; i )

                    Console.WriteLine(i.ToString());

            }

            static void Main(string[] args)

            {

                butt b = new butt();

               //在委托中,委托对象如果是null的,直接使用 =符号,会报错,但是在事件中,初始化的时候,只能用 =

                b.Click = new DelegateClick (Fm_Click); //事件是基于委托的,所以委托推断一样适用,下面的语句一样有效:b.Click = Fm_Click;

                //b.Click(10);错误:事件“DelegateStudy.butt.Click”只能出现在 = 或 -= 的左边(从类型“DelegateStudy.butt”中使用时除外)

                b.OnClick (10000);                       

                MessageBox.Show("sd234234234");

                Console.ReadLine();

            }

       }

    }

    14.2.1.6、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

    using System.Windows.Forms;

    namespace DelegateStudy

    {

        public delegate void DelegateClick (int a);

        public class butt

        {

            public event DelegateClick Click;

            public void OnClick(int a)

            {

                if(Click != null)

                    Click.Invoke(a);

                   //Click(a);//这种方式也是可以的

                MessageBox.Show("Click();");

            }

        }

        class Frm

        {

            public static void Btn_Click(int a)

            {

                for (long i = 0; i < a; i )

                    Console.WriteLine(i.ToString());

            }

            static void Main(string[] args)

            {

                butt b = new butt();

               //在委托中,委托对象如果是null的,直接使用 =符号,会报错,但是在事件中,初始化的时候,只能用 =

                b.Click = new DelegateClick (Fm_Click); //事件是基于委托的,所以委托推断一样适用,下面的语句一样有效:b.Click = Fm_Click;

                //b.Click(10);错误:事件“DelegateStudy.butt.Click”只能出现在 = 或 -= 的左边(从类型“DelegateStudy.butt”中使用时除外)

                b.OnClick (10000);                       

                MessageBox.Show("sd234234234");

                Console.ReadLine();

            }

       }

    }

    14.2.2、控件事件

    基于Windows的应用程序也是基于消息的。这说明,应用程序是通过Windows来与用户通信的,Windows又是使用预定义的消息与应用程序通信的。这些消息是包含各种信息的结构,应用程序和Windows使用这些信息决定下一步的操作。

    比如:当用户用鼠标去点击一个windows应用程序的按钮的时候,windows操作系统就会捕获到这个点击按钮的动作,这个时候它会根据捕获到的动作发送一个与之对应的预定义的消息给windows应用程序的这个按钮,windows应用程序的按钮消息处理程序会处理接收到的消息,这个程序处理过程就是根据收到的消息去触发相应的事件,事件被按钮触发后,会通知所有的该事件的订阅者来接收这个事件,从而执行相应的的函数。

    在MFC等库或VB等开发环境推出之前,开发人员必须处理Windows发送给应用程序的消息。VB和今天的.NET把这些传送来的消息封装在事件中。如果需要响应某个消息,就应处理对应的事件。

    14.2.2、控件事件

    基于Windows的应用程序也是基于消息的。这说明,应用程序是通过Windows来与用户通信的,Windows又是使用预定义的消息与应用程序通信的。这些消息是包含各种信息的结构,应用程序和Windows使用这些信息决定下一步的操作。

    比如:当用户用鼠标去点击一个windows应用程序的按钮的时候,windows操作系统就会捕获到这个点击按钮的动作,这个时候它会根据捕获到的动作发送一个与之对应的预定义的消息给windows应用程序的这个按钮,windows应用程序的按钮消息处理程序会处理接收到的消息,这个程序处理过程就是根据收到的消息去触发相应的事件,事件被按钮触发后,会通知所有的该事件的订阅者来接收这个事件,从而执行相应的的函数。

    在MFC等库或VB等开发环境推出之前,开发人员必须处理Windows发送给应用程序的消息。VB和今天的.NET把这些传送来的消息封装在事件中。如果需要响应某个消息,就应处理对应的事件。

    14.2.2、控件事件

    基于Windows的应用程序也是基于消息的。这说明,应用程序是通过Windows来与用户通信的,Windows又是使用预定义的消息与应用程序通信的。这些消息是包含各种信息的结构,应用程序和Windows使用这些信息决定下一步的操作。

    比如:当用户用鼠标去点击一个windows应用程序的按钮的时候,windows操作系统就会捕获到这个点击按钮的动作,这个时候它会根据捕获到的动作发送一个与之对应的预定义的消息给windows应用程序的这个按钮,windows应用程序的按钮消息处理程序会处理接收到的消息,这个程序处理过程就是根据收到的消息去触发相应的事件,事件被按钮触发后,会通知所有的该事件的订阅者来接收这个事件,从而执行相应的的函数。

    在MFC等库或VB等开发环境推出之前,开发人员必须处理Windows发送给应用程序的消息。VB和今天的.NET把这些传送来的消息封装在事件中。如果需要响应某个消息,就应处理对应的事件。

    14.2.2.1、控件事件委托EventHandler

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

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

    14.2.2.1、控件事件委托EventHandler

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

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

    14.2.2.1、控件事件委托EventHandler

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

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

    14.2.2.2、委托EventHandler参数和返回值

    事件最终会指向一个或者多个函数,函数要与事件所基于的委托匹配。事件所指向的函数(事件处理程序)的命名规则:按照约定,事件处理程序应遵循“object_event”的命名约定。object就是引发事件的对象,而event就是被引发的事件。从可读性来看,应遵循这个命名约定。

    首先,事件处理程序总是返回void,事件处理程序不能有返回值。其次是参数,只要是基于EventHandler委托的事件,事件处理程序的参数就应是object和EventArgs类型:

    第一个参数接收引发事件的对象,比如当点击某个按钮的时候,这个按钮要触发单击事件最终执行这个函数,那么就会把当前按钮传给sender,当有多个按钮的单击事件都指向这个函数的时候,sender的值就取决于当前被单击的那个按钮,所以可以为几个按钮定义一个按钮单击处理程序,接着根据sender参数确定单击了哪个按钮:

    if(((Button)sender).Name =="buttonOne")

    第二个参数e是包含有关事件的其他有用信息的对象。

    14.2.2.2、委托EventHandler参数和返回值

    事件最终会指向一个或者多个函数,函数要与事件所基于的委托匹配。事件所指向的函数(事件处理程序)的命名规则:按照约定,事件处理程序应遵循“object_event”的命名约定。object就是引发事件的对象,而event就是被引发的事件。从可读性来看,应遵循这个命名约定。

    首先,事件处理程序总是返回void,事件处理程序不能有返回值。其次是参数,只要是基于EventHandler委托的事件,事件处理程序的参数就应是object和EventArgs类型:

    第一个参数接收引发事件的对象,比如当点击某个按钮的时候,这个按钮要触发单击事件最终执行这个函数,那么就会把当前按钮传给sender,当有多个按钮的单击事件都指向这个函数的时候,sender的值就取决于当前被单击的那个按钮,所以可以为几个按钮定义一个按钮单击处理程序,接着根据sender参数确定单击了哪个按钮:

    if(((Button)sender).Name =="buttonOne")

    第二个参数e是包含有关事件的其他有用信息的对象。

    14.2.2.2、委托EventHandler参数和返回值

    事件最终会指向一个或者多个函数,函数要与事件所基于的委托匹配。事件所指向的函数(事件处理程序)的命名规则:按照约定,事件处理程序应遵循“object_event”的命名约定。object就是引发事件的对象,而event就是被引发的事件。从可读性来看,应遵循这个命名约定。

    首先,事件处理程序总是返回void,事件处理程序不能有返回值。其次是参数,只要是基于EventHandler委托的事件,事件处理程序的参数就应是object和EventArgs类型:

    第一个参数接收引发事件的对象,比如当点击某个按钮的时候,这个按钮要触发单击事件最终执行这个函数,那么就会把当前按钮传给sender,当有多个按钮的单击事件都指向这个函数的时候,sender的值就取决于当前被单击的那个按钮,所以可以为几个按钮定义一个按钮单击处理程序,接着根据sender参数确定单击了哪个按钮:

    if(((Button)sender).Name =="buttonOne")

    第二个参数e是包含有关事件的其他有用信息的对象。

    14.2.2.3、控件事件的其他委托

    控件事件还有其他的委托,比如在窗体上有与鼠标事件关联的委托:

    Public delegate void MouseEventHandler(object sender,MouseEventArgs e);

    public event MouseEventHandler MouseDown;

    this.MouseDown = new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);

    private void Form1_MouseDown(object sender, MouseEventArgs e){};

    MouseDown事件使用MouseDownEventArgs,它包含鼠标的指针在窗体上的的X和Y坐标,以及与事件相关的其他信息。

    控件事件中,一般第一个参数都是object sender,第二个参数可以是任意类型,不同的委托可以有不同的参数,只要它派生于EventArgs即可。

    14.2.2.3、控件事件的其他委托

    控件事件还有其他的委托,比如在窗体上有与鼠标事件关联的委托:

    Public delegate void MouseEventHandler(object sender,MouseEventArgs e);

    public event MouseEventHandler MouseDown;

    this.MouseDown = new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);

    private void Form1_MouseDown(object sender, MouseEventArgs e){};

    MouseDown事件使用MouseDownEventArgs,它包含鼠标的指针在窗体上的的X和Y坐标,以及与事件相关的其他信息。

    控件事件中,一般第一个参数都是object sender,第二个参数可以是任意类型,不同的委托可以有不同的参数,只要它派生于EventArgs即可。

    14.2.2.3、控件事件的其他委托

    控件事件还有其他的委托,比如在窗体上有与鼠标事件关联的委托:

    Public delegate void MouseEventHandler(object sender,MouseEventArgs e);

    public event MouseEventHandler MouseDown;

    this.MouseDown = new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);

    private void Form1_MouseDown(object sender, MouseEventArgs e){};

    MouseDown事件使用MouseDownEventArgs,它包含鼠标的指针在窗体上的的X和Y坐标,以及与事件相关的其他信息。

    控件事件中,一般第一个参数都是object sender,第二个参数可以是任意类型,不同的委托可以有不同的参数,只要它派生于EventArgs即可。

    14.2.2.4、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

     

    namespace SecondChangeEvent1

    {

        // 该类用来存储关于事件的有效信息外,

        // 还用来存储额外的需要传给订阅者的Clock状态信息

        public class TimeInfoEventArgs : EventArgs

        {

            public TimeInfoEventArgs(int hour,int minute,int second)

            {

                this.hour = hour;

                this.minute = minute;

                this.second = second;

            }

            public readonly int hour;

            public readonly int minute;

            public readonly int second;

        }

     

        // 定义名为SecondChangeHandler的委托,封装不返回值的方法,

        // 该方法带参数,一个clock类型对象参数,一个TimeInfoEventArgs类型对象

        public delegate void SecondChangeHandler(

           object clock,

           TimeInfoEventArgs timeInformation

        );

        // 被其他类观察的钟(Clock)类,该类发布一个事件:SecondChange。观察该类的类订阅了该事件。

        public class Clock

        {

            // 代表小时,分钟,秒的私有变量

            int _hour;

     

            public int Hour

            {

                get { return _hour; }

                set { _hour = value; }

            }

            private int _minute;

     

            public int Minute

            {

                get { return _minute; }

                set { _minute = value; }

            }

            private int _second;

     

            public int Second

            {

                get { return _second; }

                set { _second = value; }

            }

     

            // 要发布的事件

            public event SecondChangeHandler SecondChange;

     

            // 触发事件的方法

            protected void OnSecondChange(

               object clock,

               TimeInfoEventArgs timeInformation

            )

            {

                // Check if there are any Subscribers

                if (SecondChange != null)

                {

                    // Call the Event

                    SecondChange(clock, timeInformation);

                }

            }

     

            // 让钟(Clock)跑起来,每隔一秒钟触发一次事件

            public void Run()

            {

                for (; ; )

                {

                    // 让线程Sleep一秒钟

                    Thread.Sleep(1000);

     

                    // 获取当前时间

                    System.DateTime dt = System.DateTime.Now;

     

                    // 如果秒钟变化了通知订阅者

                    if (dt.Second != _second)

                    {

                        // 创造TimeInfoEventArgs类型对象,传给订阅者

                        TimeInfoEventArgs timeInformation =

                           new TimeInfoEventArgs(

                           dt.Hour, dt.Minute, dt.Second);

     

                        // 通知订阅者

                        OnSecondChange(this, timeInformation);

                    }

     

                    // 更新状态信息

                    _second = dt.Second;

                    _minute = dt.Minute;

                    _hour = dt.Hour;

     

                }

            }

        }

     

     

        /* ======================= Event Subscribers =============================== */

     

        // 一个订阅者。DisplayClock订阅了clock类的事件。它的工作是显示当前时间。

        public class DisplayClock

        {

            // 传入一个clock对象,订阅其SecondChangeHandler事件

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(TimeHasChanged);

            }

     

            // 实现了委托匹配类型的方法

            public void TimeHasChanged(

               object theClock, TimeInfoEventArgs ti)

            {

     

                Console.WriteLine("Current Time: {0}:{1}:{2}",

                   ti.hour.ToString(),

                   ti.minute.ToString(),

                   ti.second.ToString());

            }

        }

     

        // 第二个订阅者,他的工作是把当前时间写入一个文件

        public class LogClock

        {

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(WriteLogEntry);

            }

     

            // 这个方法本来应该是把信息写入一个文件中

            // 这里我们用把信息输出控制台代替

            public void WriteLogEntry(

               object theClock, TimeInfoEventArgs ti)

            {

                Clock a = (Clock)theClock;

                Console.WriteLine("Logging to file: {0}:{1}:{2}",

                   a.Hour.ToString(),

                   a.Minute.ToString(),

                   a.Second.ToString());

            }

        }

     

        /* ======================= Test Application =============================== */

     

        // 测试拥有程序

        public class Test

        {

            public static void Main()

            {

                // 创建clock实例

                Clock theClock = new Clock();

     

                // 创建一个DisplayClock实例,让其订阅上面创建的clock的事件

                DisplayClock dc = new DisplayClock();

                dc.Subscribe(theClock);

     

                // 创建一个LogClock实例,让其订阅上面创建的clock的事件

                LogClock lc = new LogClock();

                lc.Subscribe(theClock);

     

                // 让钟跑起来

                theClock.Run();

            }

        }

    }

    14.2.2.4、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

     

    namespace SecondChangeEvent1

    {

        // 该类用来存储关于事件的有效信息外,

        // 还用来存储额外的需要传给订阅者的Clock状态信息

        public class TimeInfoEventArgs : EventArgs

        {

            public TimeInfoEventArgs(int hour,int minute,int second)

            {

                this.hour = hour;

                this.minute = minute;

                this.second = second;

            }

            public readonly int hour;

            public readonly int minute;

            public readonly int second;

        }

     

        // 定义名为SecondChangeHandler的委托,封装不返回值的方法,

        // 该方法带参数,一个clock类型对象参数,一个TimeInfoEventArgs类型对象

        public delegate void SecondChangeHandler(

           object clock,

           TimeInfoEventArgs timeInformation

        );

        // 被其他类观察的钟(Clock)类,该类发布一个事件:SecondChange。观察该类的类订阅了该事件。

        public class Clock

        {

            // 代表小时,分钟,秒的私有变量

            int _hour;

     

            public int Hour

            {

                get { return _hour; }

                set { _hour = value; }

            }

            private int _minute;

     

            public int Minute

            {

                get { return _minute; }

                set { _minute = value; }

            }

            private int _second;

     

            public int Second

            {

                get { return _second; }

                set { _second = value; }

            }

     

            // 要发布的事件

            public event SecondChangeHandler SecondChange;

     

            // 触发事件的方法

            protected void OnSecondChange(

               object clock,

               TimeInfoEventArgs timeInformation

            )

            {

                // Check if there are any Subscribers

                if (SecondChange != null)

                {

                    // Call the Event

                    SecondChange(clock, timeInformation);

                }

            }

     

            // 让钟(Clock)跑起来,每隔一秒钟触发一次事件

            public void Run()

            {

                for (; ; )

                {

                    // 让线程Sleep一秒钟

                    Thread.Sleep(1000);

     

                    // 获取当前时间

                    System.DateTime dt = System.DateTime.Now;

     

                    // 如果秒钟变化了通知订阅者

                    if (dt.Second != _second)

                    {

                        // 创造TimeInfoEventArgs类型对象,传给订阅者

                        TimeInfoEventArgs timeInformation =

                           new TimeInfoEventArgs(

                           dt.Hour, dt.Minute, dt.Second);

     

                        // 通知订阅者

                        OnSecondChange(this, timeInformation);

                    }

     

                    // 更新状态信息

                    _second = dt.Second;

                    _minute = dt.Minute;

                    _hour = dt.Hour;

     

                }

            }

        }

     

     

        /* ======================= Event Subscribers =============================== */

     

        // 一个订阅者。DisplayClock订阅了clock类的事件。它的工作是显示当前时间。

        public class DisplayClock

        {

            // 传入一个clock对象,订阅其SecondChangeHandler事件

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(TimeHasChanged);

            }

     

            // 实现了委托匹配类型的方法

            public void TimeHasChanged(

               object theClock, TimeInfoEventArgs ti)

            {

     

                Console.WriteLine("Current Time: {0}:{1}:{2}",

                   ti.hour.ToString(),

                   ti.minute.ToString(),

                   ti.second.ToString());

            }

        }

     

        // 第二个订阅者,他的工作是把当前时间写入一个文件

        public class LogClock

        {

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(WriteLogEntry);

            }

     

            // 这个方法本来应该是把信息写入一个文件中

            // 这里我们用把信息输出控制台代替

            public void WriteLogEntry(

               object theClock, TimeInfoEventArgs ti)

            {

                Clock a = (Clock)theClock;

                Console.WriteLine("Logging to file: {0}:{1}:{2}",

                   a.Hour.ToString(),

                   a.Minute.ToString(),

                   a.Second.ToString());

            }

        }

     

        /* ======================= Test Application =============================== */

     

        // 测试拥有程序

        public class Test

        {

            public static void Main()

            {

                // 创建clock实例

                Clock theClock = new Clock();

     

                // 创建一个DisplayClock实例,让其订阅上面创建的clock的事件

                DisplayClock dc = new DisplayClock();

                dc.Subscribe(theClock);

     

                // 创建一个LogClock实例,让其订阅上面创建的clock的事件

                LogClock lc = new LogClock();

                lc.Subscribe(theClock);

     

                // 让钟跑起来

                theClock.Run();

            }

        }

    }

    14.2.2.4、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

     

    namespace SecondChangeEvent1

    {

        // 该类用来存储关于事件的有效信息外,

        // 还用来存储额外的需要传给订阅者的Clock状态信息

        public class TimeInfoEventArgs : EventArgs

        {

            public TimeInfoEventArgs(int hour,int minute,int second)

            {

                this.hour = hour;

                this.minute = minute;

                this.second = second;

            }

            public readonly int hour;

            public readonly int minute;

            public readonly int second;

        }

     

        // 定义名为SecondChangeHandler的委托,封装不返回值的方法,

        // 该方法带参数,一个clock类型对象参数,一个TimeInfoEventArgs类型对象

        public delegate void SecondChangeHandler(

           object clock,

           TimeInfoEventArgs timeInformation

        );

        // 被其他类观察的钟(Clock)类,该类发布一个事件:SecondChange。观察该类的类订阅了该事件。

        public class Clock

        {

            // 代表小时,分钟,秒的私有变量

            int _hour;

     

            public int Hour

            {

                get { return _hour; }

                set { _hour = value; }

            }

            private int _minute;

     

            public int Minute

            {

                get { return _minute; }

                set { _minute = value; }

            }

            private int _second;

     

            public int Second

            {

                get { return _second; }

                set { _second = value; }

            }

     

            // 要发布的事件

            public event SecondChangeHandler SecondChange;

     

            // 触发事件的方法

            protected void OnSecondChange(

               object clock,

               TimeInfoEventArgs timeInformation

            )

            {

                // Check if there are any Subscribers

                if (SecondChange != null)

                {

                    // Call the Event

                    SecondChange(clock, timeInformation);

                }

            }

     

            // 让钟(Clock)跑起来,每隔一秒钟触发一次事件

            public void Run()

            {

                for (; ; )

                {

                    // 让线程Sleep一秒钟

                    Thread.Sleep(1000);

     

                    // 获取当前时间

                    System.DateTime dt = System.DateTime.Now;

     

                    // 如果秒钟变化了通知订阅者

                    if (dt.Second != _second)

                    {

                        // 创造TimeInfoEventArgs类型对象,传给订阅者

                        TimeInfoEventArgs timeInformation =

                           new TimeInfoEventArgs(

                           dt.Hour, dt.Minute, dt.Second);

     

                        // 通知订阅者

                        OnSecondChange(this, timeInformation);

                    }

     

                    // 更新状态信息

                    _second = dt.Second;

                    _minute = dt.Minute;

                    _hour = dt.Hour;

     

                }

            }

        }

     

     

        /* ======================= Event Subscribers =============================== */

     

        // 一个订阅者。DisplayClock订阅了clock类的事件。它的工作是显示当前时间。

        public class DisplayClock

        {

            // 传入一个clock对象,订阅其SecondChangeHandler事件

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(TimeHasChanged);

            }

     

            // 实现了委托匹配类型的方法

            public void TimeHasChanged(

               object theClock, TimeInfoEventArgs ti)

            {

     

                Console.WriteLine("Current Time: {0}:{1}:{2}",

                   ti.hour.ToString(),

                   ti.minute.ToString(),

                   ti.second.ToString());

            }

        }

     

        // 第二个订阅者,他的工作是把当前时间写入一个文件

        public class LogClock

        {

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(WriteLogEntry);

            }

     

            // 这个方法本来应该是把信息写入一个文件中

            // 这里我们用把信息输出控制台代替

            public void WriteLogEntry(

               object theClock, TimeInfoEventArgs ti)

            {

                Clock a = (Clock)theClock;

                Console.WriteLine("Logging to file: {0}:{1}:{2}",

                   a.Hour.ToString(),

                   a.Minute.ToString(),

                   a.Second.ToString());

            }

        }

     

        /* ======================= Test Application =============================== */

     

        // 测试拥有程序

        public class Test

        {

            public static void Main()

            {

                // 创建clock实例

                Clock theClock = new Clock();

     

                // 创建一个DisplayClock实例,让其订阅上面创建的clock的事件

                DisplayClock dc = new DisplayClock();

                dc.Subscribe(theClock);

     

                // 创建一个LogClock实例,让其订阅上面创建的clock的事件

                LogClock lc = new LogClock();

                lc.Subscribe(theClock);

     

                // 让钟跑起来

                theClock.Run();

            }

        }

    }

    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. 3、小结

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

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

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

    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、委托 当要把方法作为实...

    本文由新葡亰496net发布于奥门新萄京娱乐场,转载请注明出处:新葡亰496net委托和事件,关于委托和事件的使用

    关键词:

上一篇:没有了

下一篇:没有了