(原) 基础知识学习(一):委托和事件(delegate and event)
最简单的话题,拿来一起探讨一下,希望能引起一些新的思考。
1、委托的由来
回调函数实际上是方法调用的指针,也称函数指针,.NET以委托的形式实现了函数指针的概念。简单说来,在编译时我们不知道第二个方法是什么,这个信息只能在运行时得到,所以需要把第二个方法作为参数传递给第一个方法。例如,Thread.Start(),方法必须带一个参数,改参数定义了要由线程调用的方法。另外,GUI编程主要是处理事件,发生事件时,运行库需要知道应执行哪个方法。
在C语言中,我们可以这样使用函数指针:
void Max()
{
....
}
Thread.Start(Max);
实际上,这是一种简单的方式,但这种直接的方法会导致一些问题,例如类型的安全性,在进行面向对象编程时,方法是很孤立的,在调用前,通常需要与类实例相关联。所以.NET Framework在语法上不允许使用这种直接的方法。如果要传递方法,就必须把方法的细节封装在一种新类型的对象中,即委托。委托是一种特殊的对象类型,不包含数据只包含方法的细节。
2、委托声明和使用
类有两个不同的术语:“类”表示较广泛的定义,“对象”表示类的实例。但委托只有一个术语,在创建委托的实例时,所创建的委托的实例仍成为委托。
使用委托要经过两个步骤,首先定义委托,告诉编译器委托代表哪种类型的方法,然后创建该委托的一个或多个实例。
delegate int Max(int a, int b); // 定义一个委托
定义一个委托基本上是定义一个新类,委托的实现派生与基类System.MulticastDelegate的类,System.MulticastDelegate又派生于System.Delegate。
使用如下:
Max max = new Max(CalMax);
Console.Write(max(1,2).ToString());
int CalMax(int a, int b)
{
return a>b? a: b;
}
3、示例(冒泡排序)
要求:可以对所有的对象进行排序,不使用IComparer接口
(1)定义委托,以应对两个对象的比较:
delegate bool CompareOp(object lhs, object rhs);
(2)Sort方法签名:
static public void Sort(object[] sortArray, CompareOp gtMethod)
(3)BubbleSorter类
public static class BubbleSorter
{
public static void Sort(object[] sortArray, CompareOp gtMethod)
{
...
...
if(gtMethod(sortArray[j],sortArray[i))
{
...
}
}
}
(4)对象的比较可以在类中进行
public class Employee
{
...
public static bool RhsIsGreater(object lhs,object rhs)
{
return ((Employee)lhs.salary > (Employee)rhs.salary) ? true:false;
}
}
(5)这样,我们可以在排序之前将委托传递给Sort
CompareOp employeeCompareOp = new CompareOp(Employee.RhsIsGreater);
BubbleSorter.Sort(employee,employeeCompareOp);
(6)这中方式要求我们在排序之前要把两个对象比较的方法传递给Sort。这种方式对于对象的排序很方便,但是如果要求对int,double进行排序就比较麻烦,我们也要写一个比较两个int类型数据的方法,写一个比较两个double类型的方法。有没有更简单的呢,我们想到了泛型。
public static class BubbleSorter
{
public static void Sort<T>(T[] sortArray)
{
for (int i = 0; i < sortArray.Length; i++)
for (int j = i + 1; j < sortArray.Length; j++)
if (sortArray[i] < sortArray[j])
{
T temp = sortArray[i];
sortArray[i] = sortArray[j];
sortArray[j] = temp;
}
}
}
但这是还有一个问题,运算符“<”无法应用于“T”和“T”类型的操作数,也就是说下面的语句是错误的:
if(sortArray[i] < sortArray[j])
怎么来解决这个问题呢??也就是怎么解决针对不同类型(int,double等)的数据进行排序的通用方法??
4、多播委托
要实现多播委托,委托必须返回void。
多播委托支持+、+=、-、-=等运算符,但应注意同一个委托调用方法链的顺序并未正式定义。
1、事件概念
基于Windows的应用程序也是基于消息的。在MFC等库或VB等开发环境推出以前,开发人员必须处理Windows发送给应用程序的消息。.net把这些传送来的消息封装在事件中。如果需要响应某个消息,就应处理响应的事件。
在开发基于对象的应用程序时,需要使用另一种对象通信方式。在一个对象中发生了有趣的事情时,就需要通知其他对象发生了什么变化。这里就用到了事件。委托就用作应用程序接收到消息时封装事件的方式。
2、探讨事件
就单纯的事件(一般意思上)而言,事件的完整过程包括创建、引发、接收、取消。在.NET中,有两个形象的概念:事件发送器、事件接收器。另外必须注意,发送器并不知道接收器是谁。很明显,发送器的作用是引发事件,而接收器的作用是处理事件。由于发送器对接收器的一无所知,所以无法设置两者之间的引用类型,而是使用委托作为中介。发送器定义接收器要使用的委托,接收器将事件处理程序注册到事件中。
3、简单了解EventHandler委托
EventHandler委托已经在.NET中定义,它位于System命名空间下。只有使用EventHandler委托,参数就应是object和EventArgs。
object:引发事件的对象。
EventArgs:包含有关事件的其他有用信息的对象。
事件处理程序遵循“object_event.object”的命名约定,但不强求。
4、示例
内容:侦察兵监视敌军动向{
using System;
using System.Collections.Generic;
// 状态
public enum Status
{
Attack, // 攻击
Suspend, // 暂停
Retreat // 撤退
}
public class MobilizeEventArgs : EventArgs
{
private int _eventArgsNo;
private Status _status;
public int EventArgsNo
{
get { return _eventArgsNo; }
set { _eventArgsNo = value; }
}
public Status Status
{
get { return _status; }
set { _status = value; }
}
}
// 敌军
public class Enemy
{
private string _name; // 名称
private int _count; // 数量
private string _direction; // 方向
public event EventHandler<MobilizeEventArgs> MobilizeEvent;
public Enemy(string name,int count, string direction)
{
_name = name;
_count = count;
_direction = direction;
}
public void Attack()
{
MobilizeEventArgs e = new MobilizeEventArgs();
e.Status = Status.Attack;
OnMobilizeEvent(e);
}
public void Retreat()
{
MobilizeEventArgs e = new MobilizeEventArgs();
e.Status = Status.Retreat;
// 溃败,返回老家
this._direction = "南京";
OnMobilizeEvent(e);
}
private void OnMobilizeEvent(MobilizeEventArgs e)
{
if (MobilizeEvent != null)
MobilizeEvent(this, e);
}
public string Name
{
get { return _name; }
}
public int Count
{
get { return _count; }
}
public string Direction
{
get { return _direction; }
}
}
// 侦察员
public class Scout
{
private Enemy _enemy;
public Scout(Enemy enemy)
{
_enemy = enemy;
_enemy.MobilizeEvent += new EventHandler<MobilizeEventArgs>(MobilizeEvent);
}
public void MobilizeEvent(object sender, MobilizeEventArgs e)
{
e.EventArgsNo = 1;
Console.WriteLine("No{0}: The enemy {1} are {2} to {3}.", e.EventArgsNo.ToString(), ((Enemy)sender).Name,
e.Status.ToString(), ((Enemy)sender).Direction);
}
}
}
上面的示例采用了系统委托,我们也可以自定义委托,然后声明委托事件:
public delegate void ChangedEventHandler(object sender, MobilizeEventArgs e);
public event ChangedEventHandler MobilizeEvent;
客户端调用就很简单了,如下:
// 初始化敌军
Enemy enemy = new Enemy("汪精卫集团军", 10000, "北京");
// 初始化侦察员监视汪精卫集团军
Scout scout = new Scout(enemy);
// 汪精卫集团军发起进攻
enemy.Attack();
// 汪精卫集团军出师不利,溃败
enemy.Retreat();
小结
1、在学习委托的过程中,我们通过一个例子来描述了委托的用法,也提出了一些问题和思考。
2、事件很简单,我们都知道它的意思。但是事件也不简单,关键我们能够灵活运用。在学习事件的过程中,我们也可能会联想到观察者模式,想想做做可能更有意思。
参考文献
【1】C#高级编程
【2】MSDN
posted on 2008-01-04 15:04 mjgforever 阅读(482) 评论(0) 编辑 收藏 举报