温文章的专栏

.net技术研究

NET委托,事件

 

 

 

1. NET委托及应用

1.1 .NET委托概念

OOP中具有相同属性的对象抽象后成为类型(class)。那么,具有相同属性的函数或方法(也称具有相同的函数签名):

·   返回类型相同

·   参数类型、参数顺序及参数个数相同

抽象后又是什么概念?例如,1到n之间每个数的平方后求和函数int SquareSum(int n)和立方后求和函数int CubeSum(int n),它们具有相同的函数签名:返回类型int、参数只有一个且是int类型。

static private int SquareSum(int n)
{
    int m = 0;
    for (int k = 1; k <= n; k++)
    {
        m += k * k;
    }
    return m;
}

static private int CubeSum(int n)
{
    int m = 0;
    for (int k = 1; k <= n; k++)
    {
        m += k * k * k;
    }
    return m;
}

这些相同属性的函数抽象,就是.NET提出的一个新的类型概念——委托,关键字为delegate。

1.2 .NET委托声明及特点

与C/C++/C#的函数声明相同,声名一个委托需要有:委托名、返回类型、参数及类型。例如,声明前面定义的两个函数的委托PowerSum如下:

public delegate int PowerSum(int n);

特别地,一类通用的事件处理委托EventHandler声明如下:

public delegate void EventHandler(object sender, EventArgs e)

显然,与类定义不同,委托声名不需要定义成员,它只起一个表示作用(delegate就是代表的意思)。此外,delegate也是类,其基类是MulticastDelegate,再上层类是Delegate,顶层类是object。

1.3 .NET委托揭秘

标题“揭秘”是借用了Jeffrey Richeter的《.框架设计(第2版)CLR Via C#》的一句原话。前面定义的委托 PowerSum 的类层次如下图,其中省略了许多方法与方法参数,图也不十分规范(BeginInvoke()的参数有省略号)。
从上图可以看出:

·   委托内含了一个链表,可以 Combine() 和 Remove() 实例方法

·   委托链提供了委托对象的访问接口

·   委托是异步编程的基础,BeginInvoke() 和 EndInvoke() 两个方法是通用的异步调用函数

1.4 .NET委托应用描述

·   Microsoft .NET Framework 通过委托向外提供一种函数回调机制。——《框架设计(第2版)CLR Via C#》Jeffrey Richeter

·   是一种类型安全的方法引用,可以把它看成一个类型安全的C函数指针。——《.NET组件编程》Juval Lowy

·   要把方法传送给其它方法时需要委托。与C函数指针不同,.NET委托是类型安全的。——《C#高级编程》Christian Nagel

从上面名著的描述可以看出,.NET委托的主要用途是三个:1)函数回调;2)传递方法;3)事件机制。

1.5 .NET委托举例1:传递方法

委托作为方法传递时,有两种方式。第一种,直接传递方法,这种方式称为委托推断;第二种,创建委托对象后传递,这种方式是常规方式。应用前面定义的委托,现定义一个调用方法的函数:int GetPowerSum(PowerSum ps)如下,该函数用于计算1到10的指数和。

static private int GetPowerSum(PowerSum ps)
{
    return ps(10);
}

采用委托推断方式调用代码如下:

int p2 = GetPowerSum(SquareSum);
int p3 = GetPowerSum(CubeSum);

采用创建委托对象方式调用代码如下:

PowerSum ps2 = new PowerSum(SquareSum);
PowerSum ps3 = new PowerSum(CubeSum);

p2 = ps2(10);
p3 = ps3(10);

1.6 .NET委托举例2:函数回调

最常见的回调应用之一,是计时器到点时调用的函数。涉及到的类型如下(.NET有三个计时器类型,这个是线程名称空间System.Threading里的Timer):

public sealed Timer(TimerCallBack callback, object state, int dueTime, int period);
public delegate void TimeCallBack(object state);

·   Timer类型中,callback是一个委托TimerCallBack的对象;state是调用时的状态参数,可以灵活应用;dueTime是计时器开始计时的等待时间;period是计时周期,每完成一个周期就调用方法callback

·   回调函数CalllBack的委托定义表明,计时器类Timer到点时回调的函数不能有返回类型,但必须有一个参数object型的参数。注意,此处委托的所谓逆变不能用了

现定义一个到点回调函数,即到点就输出字符串信息如下:

static private void TimeClick(object state)
{
    Console.WriteLine("time click 500ms");
}

那么500ms报时的计时器应用代码如下:

System.Threading.TimerCallback callBack = new System.Threading.TimerCallback(TimeClick);
System.Threading.Timer timer = new System.Threading.Timer(callBack, null, 0, 500);

由于回调函数比较简单,可以使用匿名委托,代码如下

System.Threading.TimerCallback callBack = new System.Threading.TimerCallback
(
    delegate(object state)
    {
        Console.WriteLine("time click 500ms");
    }
);

System.Threading.timer = new System.Threading.Timer(callBack, null, 0, 500);

2 .NET事件及应用(返回页首)

2.1 .NET事件概念

一个对象如何获得另一个对象发生某个事件的通知?VB和C#中常用的方法如下

·   VB按钮(Command)点击事件:Sub Command1_Click()

·   C#按钮(Button)点击事件:void button1_Click(object sender, EventArgs e)

这表明,事件是一种信号机制,对象在发生某种活动时自动发出通知,是对象定义的外发消息接口。其它对象若对事件感兴趣,则为该事件注册一个事件处理程序。事件发生时,所有注册在该事件上的处理程序都会被调用。

发布事件的对象称为发布者(publisher)或事件源,发布事件也称为激发(fire)事件。关注事件的对象称为事件接收器(sinker)或订阅者(subscriber),订阅事件也称为注册事件方法。发布者调用订阅者的注册方法。.NET事件模型建立在委托机制之上,支持事件定义、发布、订阅、和拆除。

2.2 设计.NET事件5个步骤

1.      定义参数类型:从类型EventArgs派生出满足要求的事件参数类

2.      定义事件处理者委托:与第1)步相关,该步一般被泛型委托取代了

3.      定义事件成员:在自定义类中,由事件处理者委托定义一个或多个事件成员

4.      激发事件:在自定义类的引发事件方法中,通知所有事件订阅者

5.      订阅事件:其它对象注册事件处理程序

需要指出,上述第3、4、5步必须存在,第1、2步可适当省略:

·   第2步可省。如果采用标准事件处理者委托类型:void EventHandler(object sender, EventArgs e),那么只需要第1步给出事件参数,然后使用泛型委托:EventHandler<T>即可定义类的事件成员了。其中,T就是事件参数类型

·   如果没有自定义事件参数,可以省略第1、2步,直接用EventHandler定义类的事件成员

2.3 事件设计举例

·   编写一个统计按键次数的键盘侦听类TKeyListen

·   TKeyListen可以发布侦听到的击键次数,并检查返回参数值

·   注册事件的对象可以终止侦听循环

第1步:定义事件参数类

public class KeyEventArgs : EventArgs
{
    private int m_KeyCount;
    private bool m_Stop = false;

    public KeyEventArgs(int keyCount) // 发布事件时给出按键计数值
    {
        m_KeyCount = keyCount;
    }

    public int KeyCount
    {
        get { return m_KeyCount; }
    }

    public bool Stop
    {
        get { return m_Stop; }
        set { m_Stop = value; }  // 事件订阅者可以修改
    }
}

第2步:声明事件处理者委托

public delegate void KeyEventHandler(object sender, KeyEventArgs e);

实际使用时,除非上述委托有其它用途,一般使用泛性委托EventHandler<T>取代,其中T就是事件参数类型。

第3、4步:定义类事件成员、激发(发布)事件

public class TKeyListen
{
//    public event KeyEventHandler KeyPress;  // 第3步:定义事件成员
  public event EventHandler<KeyEventArgs> KeyPress;  // 第3步:泛型委托实现

    public void Listen()
    {
        Console.WriteLine("Please press key.");
        int keyCount = 0;

        while (true) // 使用循环监听击键动作
        {
            ConsoleKeyInfo key = Console.ReadKey();
            keyCount++;

            if (KeyPress != null)  // 如果存在订阅者,即:委托链非空
            {
                KeyEventArgs e = new KeyEventArgs(keyCount);
                KeyPress(this, e);  // 第4步:激发事件,通知所有订阅者
               
                if (e.Stop)  // 判断事件返回参数
                {
                    break;
                }
            }
        }
    }
}

上述代码包含了事件实现的第3、4步。其中循环代码while(true)包含了发布(激发)事件代码,特别说明如下:

·   必须判断委托链(订阅者链)是否空,即是否存在事件的订阅者:if (KeyPress != null)。如果没有事件订阅者,直接发布事件KeyPress(this,e),系统将抛出异常“未将对象引用设置到对象实例上”

·   激发或发布事件时,第一个参数是对象自己(this):KeyPress(this, e)

·   KeyPress(this,e)实际执行过程:遍历事件订阅者链,执行每个订阅事件方法,这些方法具有与KeyPress相同的委托类型

·   可以判断事件返回参数,即订阅者可以修改参数e.Stop,发布者检测该参数。如果有多个订阅者,上述代码只获得最后一个订阅事件处理方法给定的参数。如果要判断每个订阅方法的参数,必须使用委托的GetInvocationList()方法,逐个获得返回参数。

第5步:订阅事件

static void Main(string[] args)
{
    TKeyListen demo = new TKeyListen();
    demo.KeyPress += CountKey;  // 订阅事件,使用委托推断方式
    demo.Listen();
}

static void CountKey(object sender, KeyEventArgs e) // 事件处理方法
{
    Console.WriteLine("Press count: " + e.KeyCount);
    if (e.KeyCount == 5)  e.Stop = true; // 5次后停止
}

注意,上述代码中,事件处理方法CountKey必须与事件处理者委托或泛型委托一致。此外,需要说明如下:

·   订阅事件操作符为:+=,移除订阅操作为-=

·   建立委托对象订阅方式:demo.KeyPress += new KeyEventHandler(CountKey);

·   可以多次订阅,从而产生事件链:demo.KeyPress += delegate() {…}

小结与进一步学习

委托的主要用途是方法调用、函数回调和事件,而事件主要用于外发消息。进一步学习可以考虑如下内容:

·   委托链或多播委托(MulticastDelegate)

·   委托协变和逆变

·   委托与异步(编程)调用

·   委托或委托链的异常处理

·   事件访问器

·   分布式事件与异步事件

·   事件或事件链中的异常处理

阅读更多
个人分类: C#基础
想对作者说点什么? 我来说一句

.NET委托事件用例

2011年06月23日 1.31MB 下载

C#.Net事件委托

2010年03月29日 39KB 下载

C#.NET委托事件电子书

2009年07月19日 606KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭