C#委托及事件

C#委托及事件
C#委托及事件

C#委托及事件

在C#中,委托(delegate)是一种引用类型,在其他语言中,与委托最接近的是函数指针,但委托不仅存储对方法入口点的引用,还存储对用于调用方法的对象实例的引用。

简单的讲委托(delegate)是一种类型安全的函数指针,首先,看下面的示例程序,在C++中使用函数指针。

首先,存在两个方法:分别用于求两个数的最大值和最小值。

int Max(int x,int y)

{

return x>y?x:y;

}

int Min(int x,int y)

{

return x

}

上面两个函数的特点是:函数的返回值类型及参数列表都一样。那么,我们可以使用函数指针来指代这两个函数,并且可以将具体的指代过程交给用户,这样,可以减少用户判断的次数。

下面我们可以建立一个函数指针,将指向任意一个方法,代码如下所示:

//定义一个函数指针,并声明该指针可以指向的函数的返回值为int类型,参数列表中包//括两个int类型的参数

int (*p)(int,int);

//让指针p指向Max函数

p=max;

//利用指针调用Max

c=(*p)(5,6);

我们的问题在于,上面的代码中,为什么不直接使用Max函数,而是利用一个指针指向Max之后,再利用指针调用Max函数呢?

实际上,使用指针的方便之处就在于,当前时刻可以让指针p指向Max,在后面的代码中,我们还可以利用指针p再指向Min函数,但是不论p指向的是谁,调用p时的形式都一样,这样可以很大程度上减少判断语句的使用,使代码的可读性增强!

在C#中,我们可以使用委托(delegate)来实现函数指针的功能,也就是说,我们可以像使用函数指针一样,在运行时利用delegate动态指向具备相同签名的方法(所谓的方法签名,是指一个方法的返回值类型及其参数列表的类型)。

6.1 使用委托(delegate)

6.1.1委托的建立

建立委托(delegate),过程有点类似于建立一个函数指针。过程如下:

1. 建立一个委托类型,并声明该委托可以指向的方法的签名(函数原型)delegate void MyDelegate(int a,int b);

2.建立一个委托类的实例,并指向要调用的方法

//利用委托类的构造方法指定,这是最为常见的一种方式

MyDelegate md=new MyDelegate(Max);

//利用自动推断方式来指明要调用的方法,该形式更类型于函数指针

MyDelegate md=Max;

3.利用委托类实例调用所指向的方法

int c=md(4,5);

下面通过实例来演示C#中委托的使用。

●案例操作020601:利用委托实现方法的

动态调用

首先,添加如下控件:

?两个RadioButton,分别用来让用户选

择求最大值以及求最小值

?二个TextBox,用来输入两个操作数

?一个TextBox,用来显示运算结果

?一个Button,用来执行运算

界面如下图所示:

下一步,在窗口中添加两个方法:Max,Min,这两方法的代码如下:

int Max(int x,int y)

{

return x>y?x:y;

}

int Min(int x,int y)

{

return x

}

窗口中的代码,如下图所示:

下一步:为了使用委托来实现动态指向,我们需要建立一个委托类“MyDelegate”,并建立该委托类型的一个实例,如下图所示:

上面的代码中,我们可以发现,此时,还没有让MyDelegate类型的实例“md”指向任何一个方法(即:md的值为null),原因是:在编写代码的时候,我们还不知道用户想要调用哪一个方法。

下一步,分别为两个RadioButton编写它们的“CheckedChanged”事件,代码如下:private void rbtMax_CheckedChanged(object sender, EventArgs e)

{

if (this.rbtMax .Checked ==true)

{

this.md = new MyDelegate(this.Max );

}

}

private void rbtMin_CheckedChanged(object sender, EventArgs e)

{

if (this.rbtMin .Checked ==true)

{

this.md = new MyDelegate(this.Min );

}

}

这段代码是,如果用户选择了求最大值的RadioButton,则让MyDelegate类型的实例“md”指向Max方法,如果用户选择了求最小值的RadioButton,则让MyDelegate类型的实例“md”指向Min方法。这样作的目的,就是要把选择的过程交给用户。

下一步,我们为界面中的Button编写Click事件,并利用委托来调用求最值的方法。代码如下所示:

private void btGetResult_Click(object sender, EventArgs e) {

if (this.md ==null )

{

MessageBox.Show("委托md没有指向任何方法!");

return;

}

int a = int.Parse(this.tbxOP1 .Text );

int b = int.Parse(this.tbxOP2 .Text );

int c = this.md(a,b);

this.tbxResult.Text = c.ToString();

}

从上面的代码中,可以发现,在使用委托之前,先要判断其值是否为空,如果不为空,则可以进行调用,同时,使用者可以看到,在调用md时,我们并没有关心md到底指向了哪一个方法,总之,md不为空的时候,就一定会指向Max和Min当中的一个。

为了让求最大值的RadioButton在程序开始运行的时候就被选中,在Form的Load 事件中添加如下代码:

private void Form1_Load(object sender, EventArgs e)

{

this.md = new MyDelegate(this.Max );

}

运行的效果如下图所示:

求最大值

求最小值

●委托使用的注意事项

?在C#中,所有的委托都是从

System.MulticastDelegate类派生的。

?委托隐含具有sealed属性,即不能用

来派生新的类型。

?委托最大的作用就是为类的事件绑定

事件处理程序。

?在通过委托调用函数前,必须先检查

委托是否为空(null),若非空,才能调

用函数。

?在委托实例中可以封装静态的方法也

可以封装实例方法。

?在创建委托实例时,需要传递将要映

射的方法或其他委托实例以指明委托将

要封装的函数原型(.NET中称为方法签

名:signature)。注意,如果映射的是

静态方法,传递的参数应该是类名.方法

名,如果映射的是实例方法,传递的参数

应该是实例名.方法名。

?只有当两个委托实例所映射的方法以

及该方法所属的对象都相同时,才认为它

们是想等的(从函数地址考虑)。

6.1.2 讨论委托类型

从上面的案例中,我们可以发现,在使用委托之前,先要定义一个委托类型,如下所示:

delegate int MyDelegate(int a, int b);

MyDelegate md = null;

既然叫做委托类型,就说明MyDelegate实际上是一个类,上面的写法只是一种简单的缩略写法,实际上,我们自己定义的委托,都是继承自System.MulticastDelegate类的,但是我们确不能自己定义一个类去继承自System.MulticastDelegate类,为了证明这点,我们可以使用ildasm工具,来查看“MyDelegate”的IL代码。

首先在Visual Studio控制台,如下图所示:

在打开的“Visual Studio2008 Command Prompt”窗口中,输入ildam,如下图所示:

运行之后,会出现ildasm的窗口,如下图所示:

下一步,打开刚才编译好的exe程序,如下图所示:

展开结点,并找到“MyDelegate”类型,将其展开,如下图所示:

在上图中,我们可以看到,对“MyDelegate”,存在如下“说明”:

extends [mscorlib]System.MulticastDelegate

与此同时,还存在着四个方法,即:

?.ctor:构造方法

?BeginInvoke

?EndInvoke

?Invoke

●MulticastDelegate类

MultiDelegate类是一个特殊类(Special Class),和System.Delegate类一样,该类只能够被编译器以及内置的工具类所继承,我们自定义的类是不能够显式的继承自该类的。

MultiDelegate类当中可以包括一个委托的链表,这个表中,可以包括一个或多个元素(每个元素都是一个委托),我们可以将这个表称为调用链。当我们调用一个MultiDelegate的时候,位于该MultiDelegate调用链中的委托就会被串行调用。这样我们就可以只调用一个方法,而多个相同签名的方法就会同时被串行调用。关于多播委托的说明,我们会在后面的内容中进行讲解。

●Invoke方法

为了解释Invoke方法,我们先来回顾一下,当一个委托指向了一个方法时是如何调用的,代码如下所示:

int c = this.md(a,b);

我们在调用委托,并执行该委托所指向的方法时,本质上就是调用了其Invoke 方法。实际上,我们可以直接调用其Invoke方法,代码如下所示:

int c = this.md.Invoke(a,b);

另外,与Invoke方法对应的BeginInvoke,是对Invoke方法的一个异步调用,而EndInvoke是异步调用完成后的处理方法,关于异步调用的说明,我们将在多线程的章节中进行说明。

6.1.3 使用多播委托(MulticastDelegate)

前面刚刚提及到MulticastDelegate,下面我们来看一下它的应用。

有的时候,我们想要调用一个委托,但同时可以执行多个方法(自定义事件中最为常见),比如,一个工作文档生成之后,系统要将生成文档日志,而且还要被保存到数据库中,对于以上二个操作,如果只想调用一个委托,就可以顺序完成,那么使用多播委托,就可以实现。

多播委托(MulticastDelegate)提供了一种类似于流水线式的钩子机制,只要加载到这条流水线上的委托,都会被顺序执行。因为所有的委托都继承自MulticastDelegate,因此所的委托都具备多播特性。

下面能过一个控制台程序来说明多播委托(MulticastDelegate)的使用方式。

●案例操作050602:使用多播委托

首先,建立一个控制台程序。在其中添加两个具备相同签名的方法:

?void CreateLogFile(string

originalPath):用于创建日志文件

?void WriteToDb(string

originalPath):用于将文件写入数据库

代码如下:

方法:void CreateLogFile(string originalPath)

///

/// 用于生成日志文档

///

/// 文件的原始路径

static void CreateLogFile(string originalPath)

{

if (!Directory .Exists ("log"))

{

Directory.CreateDirectory("log");

}

StreamWriter sw = new StreamWriter("log/log.txt" ,true);

sw.WriteLine("新文件已经创建,创

建时间:{0},文件路径:

{1}",DateTime .Now .ToLongTim

eString (),originalPath ); sw.Close();

Console.WriteLine("已经写入日志!");

}

方法:void WriteToDb(string originalPath)

///

/// 用于将文件写入数据库

///

/// 文件的原始路径

static void WriteToDb(string originalPath)

{

FileStream fs = new FileStream(originalPath ,FileMode.Open );

var buffer=new byte[fs.Length ];

fs.Read(buffer ,0,buffer.Length );

fs.Close();

SqlConnection con = new

SqlConnection("server=.

;database=test;uid=sa;p

wd=sa");

SqlCommand cmd = con.CreateCommand();

https://www.360docs.net/doc/65396273.html,mandText = "insert

into tb_files

values(@ID,@FileName,@C

reationTime,@FileBytes)

";

cmd.Parameters.Add("@I

D",SqlDbType.UniqueIde

ntifier).Value=Guid.Ne

wGuid ();

c

m

d

.

P

a

r

a

m

e

t

e

r

s

.

A

d

d

" @ C r e a t i o n T i m e " , S q l D b T y p e . D a t e T i m e ) . V a l u e

=

a t e T i m e . N o w

; c m d . P a r a m e t e r s . A d d ( " @ F i l e N a m e "

S q l D b T y p e . N T e x t ) . V a l u e = P a t h . G e t F i l e N a m e

( o r

g

i

n

a

l

P

a

t

h

)

;

cmd.Parameters.Add("@FileBytes",SqlDbType.Image ).Value=buffer ;

con.Open();

cmd.ExecuteNonQuery();

con.Close();

Console.WriteLine("已经写入数据库");

}

上面两个方法,具备相同签名,如果想同时串行调用这两个方法,还要定义一个

委托类型,代码如下:

///

/// 生成一个委托,用于实现多播操作

///

/// 文件的原始路径

delegate void MyMulticastDelegate(string path);

主函数代码如下所示:

static void Main(string[] args)

{

//创建原始文件

StreamWriter sw = new StreamWriter("new file.txt",false );

sw.WriteLine("this is a new file");

sw.Close();

//创建委托,并指向CreateLogFile方法

MyMulticastDelegate logDelegate=new MyMulticastDelegate

(CreateLogFile);

//创建委托,并指向WriteToDb方法

MyMulticastDelegate dbDelagate = new MyMulticastDelegate(WriteToDb ); MyMulticastDelegate multicastDelegate = logDelegate;

//在多播委托的调用链中添加新的委托元素

multicastDelegate = multicastDelegate + dbDelagate;

//调用多播委托,并且序列执行两个委托所指向的方法

multicastDelegate("new file.txt");

}

在主函数中,首先创建一个原始文件,然后建立两个委托分别指向CreateLogFile 方法以及WriteToDb方法,如下代码段所示:

MyMulticastDelegate logDelegate=new

MyMulticastDelegate(CreateLogFile);

MyMulticastDelegate dbDelagate = new MyMulticastDelegate(WriteToDb );下一步,将这两个方法合并到一个多播委托中,代码如下所示:MyMulticastDelegate multicastDelegate = logDelegate;

multicastDelegate = multicastDelegate + dbDelagate;

最后,利用多播委托,同时串行执行两个操作,代码段如下所示:multicastDelegate("new file.txt");

从上面的代码中,我们可以发现,对于两个委托来讲,“+”加操作是有意义的。如下面代码所示:

MyMulticastDelegate multicastDelegate = logDelegate;

multicastDelegate = multicastDelegate + dbDelagate;

这一点可以说明,如果想要将两个委托,放入到一个多播委托的调用链中,可以使用“+”操作符,换句话说,对于委托的“+”操作,就是在调用链中增加一个新的结点,并将一个新委托放置到该结点中。另外,和int类型的自加操作类似,委托的自加操作也进行简写,这种写法在注册事件的时候较为常用,代码如下:MyMulticastDelegate multicastDelegate = logDelegate;

multicastDelegate += dbDelagate;

该案例的完整代码如下:

///

/// 用于生成日志文档

///

/// 文件的原始路径

static void CreateLogFile(string originalPath)

{

if (!Directory .Exists ("log"))

{

Directory.CreateDirectory("log");

}

StreamWriter sw = new StreamWriter("log/log.txt" ,true);

sw.WriteLine("新文件已经创建,创

建时间:{0},文件路径:

{1}",DateTime .Now .ToLongTim

eString (),originalPath ); sw.Close();

Console.WriteLine("已经写入日志!");

}

///

/// 用于将文件写入数据库

///

/// 文件的原始路径

static void WriteToDb(string originalPath)

{

FileStream fs = new FileStream(originalPath ,FileMode.Open );

var buffer=new byte[fs.Length ];

fs.Read(buffer ,0,buffer.Length );

fs.Close();

SqlConnection con = new

SqlConnection("server=.

;database=test;uid=sa;p

wd=sa");

SqlCommand cmd = con.CreateCommand();

https://www.360docs.net/doc/65396273.html,mandText = "insert

into tb_files

values(@ID,@FileName,@C

reationTime,@FileBytes)

";

cmd.Parameters.Add("@I

D",SqlDbType.UniqueIde

ntifier).Value=Guid.Ne

wGuid ();

c

m

d

.

P

a

r

a

m

e

t

e

r

s

.

A

d

d

(

"

@

C

r

a t i o n T i m e " , S q l D

b T y p e . D a t e T i m e ) . V a l u e

= D a t e T

m e . N o w

; c m d . P a r a m e t e r s . A d d ( " @ F i l e N a m e " , S q l D

T y p e . N T e x t ) . V a l u e = P a t h . G e t F i l e N a m e

( o r i g i n a

相关主题
相关文档
最新文档