MSDN对于事件定义解释

声明事件

事件和方法一样具有签名,签名包括名称和参数列表。事件的签名通过委托类型来定义,例如:

C# 复制代码
public delegate void TestEventDelegate(object sender, System.EventArgs e);

.NET Framework 中事件的签名中,通常第一个参数为引用事件源的对象,第二个参数为一个传送与事件相关的数据的类。但是,在 C# 语言中并不强制使用这种形式;只要事件签名返回 void,其他方面可以与任何有效的委托签名一样。

向类中添加事件需要使用 event 关键字,并提供委托类型和事件名称。例如:

C# 复制代码
public class EventSource
{
    public event TestEventDelegate TestEvent;
    private void RaiseTestEvent() { /* ... */ }
}

事件可标记为 publicprivateprotectedinternalprotectedinternal。这些访问修饰符定义类的用户访问事件的方式。有关更多信息,请参见访问修饰符

使用 static 关键字可以将事件声明为静态事件。即使类没有任何实例,调用方也能在任何时候使用静态事件。有关更多信息,请参见静态类和静态类成员

使用 virtual 关键字可以将事件标记为虚事件。这样,派生类就可以使用 override 关键字来重写事件行为。有关更多信息,请参见继承(C# 编程指南)。重写虚事件的事件也可以为 sealed,以此向派生类指出它不再是虚事件。最后,可以将事件声明为 abstract,这意味着类中没有任何实现,派生类必须编写自己的实现。有关更多信息,请参见抽象类、密封类和类成员

引发事件

若要引发事件,类可以调用委托,并传递所有与事件有关的参数。然后,委托调用已添加到该事件的所有处理程序。如果该事件没有任何处理程序,则该事件为空。因此在引发事件之前,事件源应确保该事件不为空以避免 NullReferenceException。若要避免争用条件(最后一个处理程序会在空检查和事件调用之间被移除),在执行空检查和引发事件之前,事件源还应创建事件的一个副本。例如:

C# 复制代码
private void RaiseTestEvent()
{
    // Safely invoke an event:
    TestEventDelegate temp = TestEvent;

    if (temp != null)
    {
        temp(this, new System.EventArgs());
    }
}

每个事件都可以分配多个处理程序来接收该事件。这种情况下,事件自动调用每个接收器;无论接收器有多少,引发事件只需调用一次该事件。

订阅事件

要接收某个事件的类可以创建一个方法来接收该事件,然后向类事件自身添加该方法的一个委托。这个过程称为“订阅事件”。

首先,接收类必须具有与事件自身具有相同签名(如委托签名)的方法。然后,该方法(称为事件处理程序)可以采取适当的操作来响应该事件。例如:

C# 复制代码
public class EventReceiver
{
    public void ReceiveTestEvent(object sender, System.EventArgs e)
    {
        System.Console.Write("Event received from ");
        System.Console.WriteLine(sender.ToString());
    }
}

每个事件可有多个处理程序。多个处理程序由源按顺序调用。如果一个处理程序引发异常,还未调用的处理程序则没有机会接收事件。由于这个原因,建议事件处理程序迅速处理事件并避免引发异常。

若要订阅事件,接收器必须创建一个与事件具有相同类型的委托,并使用事件处理程序作为委托目标。然后,接收器必须使用加法赋值运算符 (+=) 将该委托添加到源对象的事件中。例如:

C# 复制代码
public void Subscribe(EventSource source)
{
    TestEventDelegate temp = new TestEventDelegate(ReceiveTestEvent);
    source.TestEvent += temp;
}

若要取消订阅事件,接收器可以使用减法赋值运算符 (-=) 从源对象的事件中移除事件处理程序的委托。例如:

C# 复制代码
public void UnSubscribe(EventSource source)
{
    TestEventDelegate temp = new TestEventDelegate(ReceiveTestEvent);
    source.TestEvent -= temp;
}

声明事件访问器

在前面的示例中,事件 TestEvent 的声明方式类似于字段。与字段一样,可以修改事件的对象用户可以直接使用事件。与字段不同的是,修改只能通过加法赋值 (+=) 和减法赋值 (-=) 运算符进行。

可以使用事件访问器声明事件。事件访问器使用的语法非常类似于属性访问器,它使用 add 关键字和代码块添加事件的事件处理程序,使用 remove 关键字和代码块移除事件的事件处理程序。例如:

C# 复制代码
public class EventSource2
{
    private TestEventDelegate TestEventHandlers;
    public event TestEventDelegate TestEvent
    {
        add
        {
            lock (TestEventHandlers)
            {
                TestEventHandlers += value;
            }
        }
        remove
        {
            lock (TestEventHandlers)
            {
                TestEventHandlers -= value;
            }
        }
    }
    private void RaiseTestEvent()
    {
        // Safely invoke an event.
        TestEventDelegate temp = TestEventHandlers;

        if (temp != null)
        {
            temp(this, new System.EventArgs());
        }
    }
}

引发事件的类必须具有存储和检索处理程序的机制,才能使用事件访问器。前面的示例使用一个私有委托字段 TestEventHandlers,以及加法和减法赋值运算符在列表中添加和移除处理程序。这与没有使用访问器声明的事件的工作方式极为类似。如果事件接收器使用加法赋值运算符 (+=) 添加事件处理程序,则调用 add 访问器,并且新的处理程序在访问器中可作为局部变量命名值使用。如果使用减法赋值运算符 (-=),则调用 remove 访问器,并且要移除的处理程序可作为局部变量命名值使用。两个访问器都返回 void,因此所有返回语句都不能返回值。

无论类是否声明了事件访问器,事件的订阅和取消订阅都使用相同的语法。

注意

前面示例中的 lock 语句用于防止多个线程同时操作事件列表。有关更多信息,请参见 lock 语句线程处理

如果对事件使用了访问器,则类可以按照所选的任何方式存储事件处理程序。尽管前面的示例使用的是委托,但仍然可以使用其他机制。有关示例,请参见如何:使用哈希表来存储事件实例

对于没有声明访问器的事件,C# 编译器会自动提供线程安全的访问器。抽象事件不能声明访问器。静态访问器不能使用 this 关键字。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值