《随笔二十》——C#中的 “ 事件 ”

目录

 

目录

发布者 和 订阅者

使用事件的步骤 (257P)

声明事件 (257P)

订阅事件

触发事件

EventHandler 标准事件的用法 (261P)

使用 -= 运算符来移除事件处理程序 (264P)


发布者 和 订阅者


  发布者类定义了某些程序的某些部分可能感兴趣的事件。  其他类可以 “ 订阅该事件 ”,然后在事件发生时发布者可以通知它们。 订阅者类通过向发布者类提供一个方法来 “ 订阅 ” 以获取通知。  当事件发生时, 发布者 “ 触发事件 ”, 然后执行事件处理程序 —— 又称为回调方法, 因为它们是为处理事件而调用的代码。

 

 下面是一些有关事件的重要事项:

  • 发布者(publisher) 发布事件的类或结构,以便在事件发生时通知其他类。
  • 订阅者(subscriber) 注册在事件发生时得到通知的类或结构。
  • 事件处理程序(event handler) 由订阅者 订阅 到事件的方法, 在发布者触发事件时执行。事件处理程序方法可以定义在事件所在的类或结构中, 也可以定义在不同的类或结构中。
  • 触发( raise )事件 :当事件触发时, 所有订阅 到它的方法都会被依次调用。

事件包含了一个私有委托,如图:

《随笔二十》——C#中的 “ 事件 ”_第1张图片

有关事件的私有委托需要了解的重要事项如下:

  • 事件提供了对它的私有控制委托的结构化访问。 也就是说,你无法直接访问委托。
  • 事件中可用的操作比委托要少,对于事件我们只可以添加、删除或调用事件处理程序。
  • 事件被触发时, 它调用委托来依次调用调用列表中的方法。
  • 注意: 如上图,只有+=和-=运算符在事件框的左边。因为它们是事件唯一允许的操作 (除了调用事件本身)。

使用事件的步骤 (257P)


 需要在事件中使用的代码有5部分:

  • 委托类型声明 —— 事件和事件处理程序必须有共同的签名和返回类型, 它们通过委托类型进行描述。
  • 事件处理程序声明——这些声明位于订阅程序类中,是事件触发时要执行的方法的声明。这些方法不一定必显式地命名; 它们也可以是匿名方法或lambda表达式,
  • 事件声明 —— 发布者类必须声明一个订阅者类可以注册的事件成员当声明的事件为public时, 称为发布了事件。
  • 事件订阅 ——订阅者必须注册事件,以便在触发事件时得到通知。这是将事件处理程序连接到事件的代码。
  • 触发事件的代码—— 这是发布者中触发事件的代码,使其调用向其注册的所有事件处理程序。

《随笔二十》——C#中的 “ 事件 ”_第2张图片


声明事件 (257P)


  delegate void Handler(); //声明委托类型
    class Incrementer
    {
        public event Handler CountedADozen;
    }

 创建一个事件只需要 委托类型和一个标识符,如上述代码所示, 代码中声明了一个叫做CountADozen的事件。注意的有:

  • 事件声明在一个类中。
  • 它需要委托类型 和 一个 事件标识符 ,任何订阅事件的 处理程序都必须与委托类型的签名和返回类型匹配。
  • 它被声明为public,以便其他类和结构可以用它注册事件处理程序。
  • 不能使用对象创建表达式 (new表达式)来创建它的对象。

 我们可以通过使用逗号分隔的列表在一个声明语句中声明一个以上的事件。例如,下面语句声明了3个事件:

 delegate void Handler(); //声明委托类型
    class Incrementer
    {
        public event Handler CountedADozen, CountedADozen2, CountedADozen3;
    }

注意 : 我们还可以使用 static关键字让事件变成静态的。

 

和方法、属性一样,事件是类或结构的成员,这一点引出了几个重要的特性:

由于事件是成员:

  • 我们不能在一段可执行代码中声明事件。
  • 它必须与其他成员一起在类或结构中声明
  • 事件成员被隐式自动初始化为null。

若要声明事件,必须提供委托类型的名称。您可以声明一个,也可以使用一个已经存在的。如果声明委托类型,它必须指定将由事件注册的方法的签名和返回类型。


订阅事件


订阅者向事件添加事件处理程序。要将事件处理程序添加到事件中,该处理程序必须具有与事件委托相同的返回类型和签名。

使用+ =运算符向事件添加事件处理程序,如以下代码所示。 事件处理程序位于操作员的右侧。

事件处理程序规范可以是以下任何一种:

  • 实例方法的名称;
  • 静态方法的名称;
  • 匿名方法;
  • Lambda表达式。

 


触发事件


● 事件成员本身只是保存了需要被调用的事件处理程序。如果事件没有被触发,什么都不会发生。

●  触发事件的语法和调用方法一样:

  • 使用事件名称,然后 ( 参数列表 )
  • 参数列表必须与事件的委托类型相匹配。
namespace HelloWorld_Console
{
    delegate void Handler(); //声明委托类型
    //发布者
    class Incrementer
    {
        public event Handler CountedADozen; //创建事件并发布
        public void DoCount()
        {
            for(int i=1;i<100;++i)
            {
                if( i % 12 ==0 && CountedADozen !=null)
                {
                    CountedADozen(); //触发事件
                }
            }
        }
    }
    // 订阅者
    class Dozens
    {
        public int DozensCount
        {
            get;
            private set;
        }
        public Dozens(Incrementer incrementer)
        {
            DozensCount = 0;
            incrementer.CountedADozen += IncrementDozensCount; //订阅事件
        }
        void IncrementDozensCount()  // 定义事件处理程序
        {
            ++DozensCount; 
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Incrementer incrementer = new Incrementer();
            Dozens dozensCounter = new Dozens(incrementer);

            incrementer.DoCount();
            WriteLine($"输出结果为:{dozensCounter.DozensCount}");
            ReadKey();
        }
    }
}

输出结果为:8

 


EventHandler 标准事件的用法 (261P)


EventHandler委托类型的声明如下代码所示。关于该声明需要注意以下几点:

public delegate void EventHandler( object sender, EventArgs e) ;
  • 第一个参数用来保存触发事件的对象的引用。由于是object类型的,所以可以匹配任何类型的实例;
  • 第二个参数用来保存状态信息,指明什么类型适用于该应用程序;

  EventHandler 委托类型的第二个参数是 EventArgs类的对象, 它声明在System命名空间中。你可能会想,既然第二个参数用于传递数据, EventArgs类的对象应该可以保存一些类型的数据。你可能错了。

  • EventArgs设计为不能传递任何数据。它用于不需要传递数据的事件处理程序——通常会被忽略。
  • 如果你希望传递数据, 必须声明一个派生自EventArgs的类, 使用合适的字段来保存需要传递的数据。
namespace HelloWorld_Console
{
    //发布者
    class Incrementer
    {
        public event EventHandler CountedADozen; //使用系统定义的EventHandler 委托
        public void DoCount()
        {
            for(int i=1;i<100;++i)
            {
                if( i % 12 ==0 && CountedADozen !=null)
                {
                    CountedADozen(this,null); //触发事件时使用 EventHandler 的参数
                }
            }
        }
    }
    // 订阅者
    class Dozens
    {
        public int DozensCount
        {
            get;
            private set;
        }
        public Dozens(Incrementer incrementer)
        {
            DozensCount = 0;
            incrementer.CountedADozen += IncrementDozensCount; //订阅事件
        }
        void IncrementDozensCount(object source,EventArgs e)//定义事件处理程序,事件处理程序的签名必须与委托的签名匹配
        {
            ++DozensCount; 
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Incrementer incrementer = new Incrementer();
            Dozens dozensCounter = new Dozens(incrementer);

            incrementer.DoCount();
            WriteLine($"输出结果为:{dozensCounter.DozensCount}");
            ReadKey();
        }
    }
}

使用 -= 运算符来移除事件处理程序 (264P)


在用完了事件处理程序之后,可以将其从事件中删除。您可以使用-=操作符从事件中删除事件处理程序。

namespace HelloWorld_Console
{
    // 发布者
   class Publisher
    {
        public event EventHandler SimpleEvent;  // 创建事件并且发布事件
        public void RaiseTheEvent()
        {
            SimpleEvent(this, null);// 触发事件
        }
    }
    // 订阅者
    class Subscriber
    {
        // 下面都是事件处理程序
        public void MethodA( object o, EventArgs e)
        {
            WriteLine("AAA");
        }
        public void MethodB(object o, EventArgs e)
        {
            WriteLine("BBB");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Publisher p = new Publisher();
            Subscriber s = new Subscriber();
            p.SimpleEvent += s.MethodA; // 注册MethodA
            p.SimpleEvent += s.MethodB; // 注册MethodB
            p.RaiseTheEvent();

            WriteLine("\r\n Remove MethodB");
            p.SimpleEvent -= s.MethodB;
            p.RaiseTheEvent();
        }
    }
}

 

你可能感兴趣的:(C#中的随笔)