现在的位置: 首页 > 综合 > 正文

技术讲座:.NET委托、事件及应用兼谈软件项目开发

2013年03月30日 ⁄ 综合 ⁄ 共 6174字 ⁄ 字号 评论关闭
文章目录

(原创文章,2008年10月21日修改,转载请注明来源:http://blog.csdn.net/hulihui)

本次讲座涉及三个方面的内容:.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)
  • 委托协变和逆变
  • 委托与异步(编程)调用
  • 委托或委托链的异常处理
  • 事件访问器
  • 分布式事件与异步事件
  • 事件或事件链中的异常处理

3 软件项目开发浅谈(返回页首)

3.1 开发文档

即使是小型软件项目,也至少需要包括如下开发文档:

  • 技术文档:需求分析、系统运行环境、系统运行配置、库表设计、算法描述等
  • 测试文档:单元/集成测试、验收测试

3.2 后期维护

  • 长期维护准备:维护期限一般体现在合同中,一般3~5年
  • 代码与文档维护:增加维护文档,记录所有的维护情况

3.3 技术积累

  • 技术侧重:工具与语言、B/S与C/S模式
  • 最新技术:例如:RIA中的AIR与SilverLight
  • 行业知识:应用领域的专业知识

上面主要是小型、个人最多不过3人的软件项目,不涉及到大型的、公司里运作的项目开发方式。

抱歉!评论已关闭.