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

来源:互联网 发布:app软件著作权登记 编辑:程序博客网 时间:2024/06/05 19:06
 

[+]

  1. NET委托及应用
    1. NET委托概念
    2. NET委托声明及特点
    3. NET委托揭秘
    4. NET委托应用描述
    5. NET委托举例1传递方法
    6. NET委托举例2函数回调
  2. NET事件及应用返回页首
    1. NET事件概念
    2. 设计NET事件5个步骤
    3. 事件设计举例
    4. 第1步定义事件参数类
    5. 第2步声明事件处理者委托
    6. 第34步定义类事件成员激发发布事件
    7. 第5步订阅事件
    8. 小结与进一步学习
  3. 软件项目开发浅谈返回页首
    1. 开发文档
    2. 后期维护
    3. 技术积累
(原创文章,2008年10月21日修改,转载请注明来源:http://blog.csdn.net/hulihui)

本次讲座涉及三个方面的内容:.NET委托、.NET事件以及软件项目开发。
  • 2008技术讲座程序代码及PPT

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人的软件项目,不涉及到大型的、公司里运作的项目开发方式。
  • 2008技术讲座程序代码及PPT
0 0
原创粉丝点击