-
c#中的事件和索引器
1 事件
事件是类在发生其关注的事情时用来提供通知的一种方式。例如,封装用户界面控件的类可以定义一个在用户单击该控件时发生的事件。控件类不关心单击按钮时发生了什么,但它需要告知派生类单击事件已发生。然后,派生类可选择如何响应。
C#是一个完全面向对象组件的程序,所以允许编写自定义的事件。实际上事件就是一个回呼(callback)的函数指针,在C#中是通过delegate达成的。
在.NET的事件模型中,主要的角色有事件发行者和事件订阅者。事件的发行者是触发事件的对象,而事件的订阅者指的是注册在某种事件发生时被通知的人。
当发生与某个对象相关的事件时,类和结构会使用事件将这一对象通知给用户。这种通知即称为“引发事件”。引发事件的对象称为事件的源或发送者。对象引发事件的原因很多:响应对象数据的更改、长时间运行的进程完成或服务中断。例如,一个对象在使用网络资源时如果丢失网络连接,则会引发一个事件。表示用户界面元素的对象通常会引发事件来响应用户操作,如按钮单击或菜单选择。
要在程序中使用事件,就必须先声明事件。下面来声明一个事件,代码如下:
public delegate void MrEventDelegate(object sender, System.EventArgs e);
.NET Framework 中事件的签名中,通常第一个参数为引用事件源的对象,第二个参数为一个传送与事件相关的数据的类。但是,在C#语言中并不强制使用这种形式;只要事件签名返回 void,其他方面可以与任何有效的委托签名一样。
向类中添加事件需要使用 event 关键字,并提供委托类型和事件名称。例如:
publicclassEventSend
{
publiceventMrEventDelegateTestEvent;
privatevoidRaiseTestEvent(){/*...*/}
}
事件可标记为public、private、protected、internal 或protectedinternal。这些访问修饰符定义类的用户访问事件的方式。
若要引发事件,类可以调用委托,并传递所有与事件有关的参数。然后,委托调用已添加到该事件的所有处理程序。如果该事件没有任何处理程序,则该事件为空。因此在引发事件之前,事件源应确保该事件不为空以避免NullReferenceException。若要避免争用条件(最后一个处理程序会在空检查和事件调用之间被移除),在执行空检查和引发事件之前,事件源还应创建事件的一个副本。下面的代码实现了触发MrEventDelegate事件:
private void RaiseTestEvent(){ MrEventDelegate temp = TestEvent; if (temp != null) { temp(this, new System.EventArgs()); }}每个事件都可以分配多个处理程序来接收该事件。这种情况下,事件自动调用每个接收器;无论接收器有多少,引发事件只需调用一次该事件。要接收某个事件的类可以创建一个方法来接收该事件,然后向类事件自身添加该方法的一个委托。这个过程称为“订阅事件”。
首先,接收类必须具有与事件自身具有相同签名(如委托签名)的方法。然后,该方法(称为事件处理程序)可以采取适当的操作来响应该事件。例如:
publicclassEventReceiver
{
publicvoidReceiveTestEvent(objectsender,System.EventArgse)
{
System.Console.Write("Eventreceivedfrom");
System.Console.WriteLine(sender.ToString());
}
}
每个事件可有多个处理程序。多个处理程序由源按顺序调用。如果一个处理程序引发异常,还未调用的处理程序则没有机会接收事件。由于这个原因,建议事件处理程序迅速处理事件并避免引发异常。
若要订阅事件,接收器必须创建一个与事件具有相同类型的委托,并使用事件处理程序作为委托目标。然后,接收器必须使用加法赋值运算符(+=)将该委托添加到源对象的事件中。例如:
public void Subscribe(EventSend send)
{
TestEventDelegate temp = new TestEventDelegate(ReceiveTestEvent);
send.TestEvent += temp;
}
若要取消订阅事件,接收器可以使用减法赋值运算符(-=)从源对象的事件中移除事件处理程序的委托。例如:
publicvoidUnSubscribe(EventSourcesend)
{
TestEventDelegatetemp=newTestEventDelegate(ReceiveTestEvent);
send.TestEvent-=temp;
}
2 索引器
索引器允许类或结构的实例按照与数组相同的方式进行索引。索引器类似于属性,不同之处在于它们的访问器采用参数。它可以使得像数组那样对对象使用下标。它提供了通过索引方式方便地访问类的数据信息的方法。
要声明类或结构上的索引器,请使用this关键字,例如:
public int this[int index] //声明索引器
{
// get and set 访问
}
索引器的修饰符有:new、public、protected、internal、private、virtual、sealed、override、abstract和extern。当索引器声明包含extern修饰符时,称该索引器为外部索引器。因为外部索引器声明不提供任何实际的实现,所以它的每个访问器声明都由一个分号组成。
索引器的签名由其形参的数量和类型组成。它不包括索引器类型或形参名。如果在同一类中声明一个以上的索引器,则它们必须具有不同的签名。
索引器值不归类为变量;因此,不能将索引器值作为ref或out参数来传递。
下面用一个例子来说明如何声明和使用索引器。
在本示例中,定义了一个泛型类,并为其提供了简单的get和set访问器方法(作为分配和检索值的方法)。Program 类为存储字符串创建了此类的一个实例。代码如下:
classSampleCollection<T>
{
privateT[]arr=newT[100];
publicTthis[inti]
{
get
{
returnarr[i];
}
set
{
arr[i]=value;
}
}
}
下面是如何使用上述代码实现的索引器,具体代码示例如下:classProgram
{
staticvoidMain(string[]args)
{
SampleCollection<string>锁=newSampleCollection<string>();
s[0]="索引器的使用";
System.Console.WriteLine(锁[0]);
}
}
C#并不将索引类型限制为整数。例如,对索引器使用字符串可能是有用的。通过搜索集合内的字符串并返回相应的值,可以实现此类的索引器。由于访问器可被重载,字符串和整数版本可以共存。
事件可标记为public、private、protected、internal 或protectedinternal。这些访问修饰符定义类的用户访问事件的方式。
若要引发事件,类可以调用委托,并传递所有与事件有关的参数。然后,委托调用已添加到该事件的所有处理程序。如果该事件没有任何处理程序,则该事件为空。因此在引发事件之前,事件源应确保该事件不为空以避免NullReferenceException。若要避免争用条件(最后一个处理程序会在空检查和事件调用之间被移除),在执行空检查和引发事件之前,事件源还应创建事件的一个副本。下面的代码实现了触发MrEventDelegate事件:
private void RaiseTestEvent(){ MrEventDelegate temp = TestEvent; if (temp != null) { temp(this, new System.EventArgs()); }}每个事件都可以分配多个处理程序来接收该事件。这种情况下,事件自动调用每个接收器;无论接收器有多少,引发事件只需调用一次该事件。要接收某个事件的类可以创建一个方法来接收该事件,然后向类事件自身添加该方法的一个委托。这个过程称为“订阅事件”。
首先,接收类必须具有与事件自身具有相同签名(如委托签名)的方法。然后,该方法(称为事件处理程序)可以采取适当的操作来响应该事件。例如:
publicclassEventReceiver
{
publicvoidReceiveTestEvent(objectsender,System.EventArgse)
{
System.Console.Write("Eventreceivedfrom");
System.Console.WriteLine(sender.ToString());
}
}
每个事件可有多个处理程序。多个处理程序由源按顺序调用。如果一个处理程序引发异常,还未调用的处理程序则没有机会接收事件。由于这个原因,建议事件处理程序迅速处理事件并避免引发异常。