zt 对C#下函数,委托,事件的一点理解!

对C#下函数,委托,事件的一点理解!

发表日期:2006-11-30

-
今天一来是有点空,二来是在博客上偶然看到有关于委托的文章,一时兴起,就自己也写一点心得与大家分享一下。

先看一个例子:

using System;
namespace Consoleapplication1
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
bool m_isRight = false;
object m_obj = m_isRight?MyWrite("true"):MyWrite("false");
Console.Write(m_obj);
}
static PRivate int MyWrite(object i_string)
{
Console.Write(i_string);
return i_string.ToString().Length;
}
}
}

问输出的结果是什么?有一个刚学习程序设计不久的学生的回答是:false false

这个结果给我的映像很深,为什么呢?因为我觉得这个不仅仅是学生的一个错误,而更多的是这个学生深入的思考了问题。

因为m_obj是一个对象,所以这个学生理解为:MyWrite()这个函数对象可以直接赋值给m_obj,然后m_obj就当成MyWrite()这个函数来调用,所以他就认为:


Console.Write (m_obj); 等于是:Console.Write (MyWrite(“false”));
这是思维是很有创意的,不是吗?

于是就是C#里而很多人不好理解的委托了。其实,从使用上讲,它就是一个函数变量!如上面的例子,如果真的是想把MyWrite()做为对象赋值给m_obj会是个什么结果呢?

我觉得我们先得解决以下几个问题,才能正确的把函数当成变量赋值给一个对象:

1、如果可以给一个对象赋函数值,如何来区别不同的函数?

2、如何区别它是一个函数赋值,还是一个普通的对象赋值?

3、如何用这个对象来调用原来的函数?

如果把这几个问题解决了,委托也就明白了一半。

先看问题1,如果可以给一个对象赋函数值,如何来区别不同的函数?

首先应该明白的是:C#里是可以对一个对象赋函数值的。解决这个问题的办法是先对该对象申明,申明它可以被什么样的函数来赋值,而这个对象申明在C#里的学名就是委托。

(在C++里称为函数指针申明,相应的对象也就叫做函数指针。java里也不同的叫法,可惜我不知道。)

而它的语法就是:

delegate [function declare];

这里的function declare就包括了:

1、函数返回类型,

2、可用来存放函数的对象名(也就是委托名)

3、函数参数

所以完整的定义可以是:

delegate int MyDelegate(object I_object);

当然,合法的委托定义可以是:

delegate void MyDelegate();

delegate void MyDelegate(object I_1,object I_2);

现在,上面的语法就定义了一个抽象的对象MyDelegate, 注意,这里说的是抽象的对象,也就是说,你不能直接给MyDelegate赋函数,而只能在它的实例上函数,这是C#里特殊的要求。它的语法是:

MyDelegate m_delegate = new MyDelegate(与MyDelegate申明一致的函数名);

例如,以下是一个完全的,合法的委托申明与实例化一个对象:

delegate int MyDelegate(object i_object);
//
MyDelegate m_delegate = new MyDelegate(MyWrite);
//MyWrite函数如下,它是满足委托的申明的。
static private int MyWrite(object i_string)
{
Console.Write(i_string);
return i_string.ToString().Length;
}
现在我们就很好的解决了第一个问题,如何定义一个对象,使该对象可以把函数当变量来赋给它。而且,可以区别不同的函数类型,主要是通过函数返回值与函数参数来共区别一类函数。

OK,第二个问题:如果有了这样的一个对象后,如何来给它赋一个函数值呢?

其实上面的实例化一个委托对象时,就已经给它赋值了。上面的代码中,m_delegate就已经被赋值MyWrite,因此它已经具有了MyWrite函数的功能。

还有其实它的方法来给它赋值吗?有,在委托的一个应用中,可以看到其它的赋值方法。也就是另一个不好理解的概念:事件!后面会提到。

我们再来看一下最后一个问题:如何通过一个已经赋值好了的委托对象,还调用它上面赋值了的函数。

这个最简单了,当一个委托实例赋了函数对象在上面后,就可以像调用原函数一样的来调用它了。因此,下面是一个会法的调用:基于上面的申明。

m_delegate(“This is a delegate object to call the raw function.”);

它就等同于:

MyWrite(“This is a delegate object to call the raw function.”);

因此,上面的调用与原函数调用一样,会返回一个int结果。


OK,最后看一个完整的例子:

using System;
namespace ConsoleApplication1
{
class Class1
{
//先申明一个委托对象。
delegate int MyDelegate(object i_object);
[STAThread]
static void Main(string[] args)
{
MyDelegate m_delegate = new MyDelegate(MyWrite);
m_delegate("This is a delegate object to call the raw function.");
}
//该函数是满足上面委托对象的申明的。
static private int MyWrite(object i_string)
{
Console.Write(i_string);
return i_string.ToString().Length;
}
}
}


再来讨论一下它的应用:事件!

事件是其于委托的。我们还是先来看最开始的那个例子:

object m_obj = m_isRight?MyWrite("true"):MyWrite("false");

我想把一个函数对象赋值到m_obj上!但上面的委托只能在实例化对象的时候就直接给它赋值了。而现在是,在运行时对一个委托赋函数值。可以做到吗?

同样是有这样的向个问题,当然,前提是我们已经知道有一种对象叫委托,它的实例可以赋函数对象。

下面的问题是:

1、如果可以在运行时给某个“特殊委托”赋函数对象,如何实现?

2、运行时,如何知道该“特殊委托”是否已经被赋过函数值?及如何再赋值?

3、如果可以,能否在一个“特殊委托”上添加多个函数?如果可以,如何删除函数?

下面,我们就针对这几个问题,来讨论一下C#里的事件,也就是上面的“特殊委托”。(其它语言里是如何实现这些功能的,我就不清楚了。)

首先,C#里是可以实现在运行时给一个委托动态的赋函数值的,同时也是可以动态的删除已经添加在某个委托上的函数的,它的实现有一点点麻烦,就是要用到另一个对象:事件!event

(申明,你完全可以不把它叫事件,只不过这种动态的添加和删除函数的功能在真实的程序设计中,基本上是与事件相关,所以就叫做事件了。个人想法,呵呵。)

OK,下面是C#语法,来申明一个“特殊委托”――事件,让它可以动态的添加函数!

下文中,事件是指那些“特殊委托”,它的特殊之外,后面会讲到。而下文中的委托就是前面讲到的,一个特殊的对象,该对象可以把函数当“值”赋给它。

static event MyDelegate m_myevent;

(static 可以用其它的修饰符)

说明一下,这里其实就是申明了一个事件,用event来说明它是事件(特殊委托)的。其实对比实例化一个委托的语法,你可以理解到,它就像是申明了一个委托,只不过个委托加了个event来说明它:

Mydelegate m_delegate;//申明一个委托

event MyDelegate m_myevent;//申明一个事件

很像吧!不是吗?

OK,我们再来看,m_myevent 与m_delegate到底有什么不同的?也就是,事件(特殊委托)到底特殊在什么地方?

1、事件不是一个可以直接把函数当值一样赋给它的委托。而委托可以直接赋函数,而且是在实例化的时候,赋函数名。

2、事件只能把一个实例的委托当值赋给它。也就是说:事件是用来管理委托的,进而来管理函数!因为一个实例化的委托一定有一个函数与之对应。

3、在事件上可以动态的添加与删除委托。而委托上不能动态的添加删除函数。

OK,下面的一个问题,上面事件的申明中,MyDelegate是起什么作用的呢?

还记得前面的委托申明吗?它就是说明了m_myevent在运行时可以动态的以委托的形式赋的函数要与MyDelegate申明的一样!

因此上面的一个实例化是完全合法的。

再理解一下:事件,是用来动态管理委托的,而委托是单一的与一个函数对应的。

现在看第二个问题,运行时,如何知道该“特殊委托”是否已经被赋过函数值?及如何再赋值?

即:如何知道一个事件上已经赋过经过委托过的函数?

前面已经说过,m_myevent没有给它赋值,如何给它赋值呢?它的赋值方法有点怪:

一个实例:

m_myevent += m_delegate;

有点怪吧!这里正好说明了:事件是用来动态管理委托的。把一个委托加在事件上。

当然,你还可以在添加委托的时候直接new一个新的委托:

m_myevent +=new MyDelegate(Class1_m_myevent);//后面的函数名由vs2003生动生成

这就是.net下标准的给事件添加委托的方法,也就是给一个事件添加了一个可调用的函数。确切的说,是一个回调函数(C++的概念)。

OK,下面就如何判断一个事件上是否已经被赋过经过委托的函数:

if(m_myevent==null)

就可以知道了!

那么如何知道一个事件上面有多少个委托呢?也就是多少个委托过的函数?

m_ myevent.GetInvocationList();

可以得到所有的委托!这里应该知道:事件本身是一个对象,当实例化一个事件后,它是有自己的一些方法也成员的。可以查阅MSDN得到更多说明,同样的委托也是一个对象,相关的说明也可以在MSDN里找到。

最后的问题:如何删除一个已经添加在事件上的委托?

太容易而且太有意思了:

m_myevent -= m_delegate;

那么这样的几个问题又来了:

1.如果这个事件上没有该委托,“减掉”以后会出错吗?不会,放心的减吧。

2.如何调用这个事件上的委托呢?上面有多个委托,它是怎样运行呢?

调用事件上的委托与调用委托上的函数是完全一样的:你要给出与委托申明一样的函数参数,并且调用会返回与申明一样的数据类型。

最后再回来看这个问题:

object m_obj = m_isRight?MyWrite("true"):MyWrite("false");

如何解决它呢?把一个函数赋给一个对象:一个例示(仅做演示解决这个问题,个人认为这样的做法没有实用意义)


using System;
namespace ConsoleApplication1
{
public class Class4
{
event System.EventHandler m_obj;
public Class4()
{
System.EventHandler m_f1 = new EventHandler(SomeFunc1);
System.EventHandler m_f2 = new EventHandler(SomeFunc2);
bool m_someCondition = false;
m_obj += m_someCondition?m_f1:m_f1;
m_obj(this,null);
}

private void SomeFunc1(object sender, EventArgs args)
{
}

private void SomeFunc2(object sender, EventArgs args)
{
}
}
}


最后看一个完整的例子:


using System;
namespace ConsoleApplication1
{
class Class1
{
//先申明一个委托对象。
delegate int MyDelegate(object i_object);
static event MyDelegate m_myevent;
[STAThread]
static void Main(string[] args)
{
MyDelegate m_delegate = new MyDelegate(MyWrite);
m_delegate("This is a delegate object to call the raw function.");
m_myevent += m_delegate;
m_myevent += new MyDelegate(MyWrite);
m_myevent +=new MyDelegate(Class1_m_myevent);
if(m_myevent!=null)
{
m_myevent("This is a event to call the funcaion on the delegate.");
}
}
//该函数是满足上面委托对象的申明的。
static private int MyWrite(object i_string)
{
Console.WriteLine(i_string);
return i_string.ToString().Length;
}

private static int Class1_m_myevent(object i_object)
{
Console.WriteLine(i_object);
return 0;
}
}
}

我们再来看一个.net下标准的事件驱动模型的例子:


using System;

namespace ConsoleApplication1
{
public delegate void MyEventHandle(object i_sender,object i_arg);

public class Class2
{
public Class2()
{
}

[STAThread]
static void Main2(string[] args)
{
Class3 m_runOject = new Class3();
m_runOject.OnError += new MyEventHandle(m_runOject_OnError);
m_runOject.OnSomeThingHappened += new MyEventHandle(m_runOject_OnSomeThingHappened);
m_runOject.Run();
}

private static void m_runOject_OnError(object i_sender, object i_arg)
{
Console.WriteLine("Error in {0}, arg:{1}",i_sender,i_arg);
Console.WriteLine("Object {0} will stop running.",i_sender);
(i_sender as Class3).Stop();
}

private static void m_runOject_OnSomeThingHappened(object i_sender, object i_arg)
{
Console.WriteLine("Something happended in {0}, arg:{1}",i_sender,i_arg);
}
}


public class Class3
{
public bool m_isStop = false;
public event MyEventHandle OnSomeThingHappened;
public event MyEventHandle OnError;

public Class3()
{
}

public void Run()
{
Random m_rand = new Random();
int m_randomNum = m_rand.Next();
while(!m_isStop)
{
if(m_isStop){break;}
m_randomNum = m_rand.Next(100);
if(m_randomNum%5==0)
{
if(this.OnError!=null)
{
this.OnError(this,m_randomNum);
}
}
else
{
if(this.OnSomeThingHappened!=null)
{
this.OnSomeThingHappened(this,m_randomNum);
}
}
}
}

public void Stop()
{
m_isStop = true;
}
}
}


好了,全部完了!

最后再从另一个角度来理解:函数,委托和事件!

1、函数,是程序的基本单元,在.net下,有一个很重要的思想,就是:一切对象化!你可把一个int boxing后得到一个object,也可以把一个object unboxing后得到一个int. 可惜,就是没有函数对象化这个概念!?

2、其实函数也可以对象化!就是通过delegate,委托就是把函数作为对象来处理,使它函数也具有了对象的一些特点。在实例化一个委托的时候,就是把一个函数”boxing”。因此,委托本质上就是一个类!它的初始化参数是一个函数名!不同的委托是不同的类,对应不同类型的函数。

3、事件是对委托的一个管理封装,它可以很好的动态管理委托,从而完成很多有实用价值的事情,最主要的就是事件!

完全是个人理解,有不同意见,欢迎讨论!

-

本文来自: 动态网站制作(www.knowsky.com) 详细出处参考:http://www.knowsky.com/341489.html

[@more@]

如何利用C#创建和调用DLL

发表日期:2003-12-30

-
一、写在前面
C# 语言是一种简单但功能强大的编程语言,用于编写企业应用程序。

C# 语言从C和 C++语言演化而来,在语句、表达式和运算符方面使用了许多 C++ 功能。

C# 语言在类型安全性、版本转换、事件和垃圾回收等方面进行了相当大的改进和创新。

C# 语言提供对常用 API 样式(如 .NET 框架、COM、自动化和 C 样式 API 等)的访问。

什么是动态链接库?DLL三个字母对于你来说一定很熟悉吧,它是Dynamic Link Library 的缩写形式,动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。

和大多数程序员一样,你一定很使用过DLL吧。也曾感受到它的带给你程序设计和编码上的好错吧今天我想和大家探讨一个主题:如何在C#创建和调用DLL(动态链接库), 其实在很大意义上而讲,DLL让我更灵活的组织编写我们的应用程序,作为软件设计者,可一个根据它来达到很高的代码重用效果。下面我来介绍一下在C#中如何创建和调用DLL。

二、准备工作

我们需要对我们接下来要做的事情做个简单的介绍,在本文我们将利用C#语言创建一个名为 MyDLL.DLL的动态链接库,在这个动态链接库文件中我们将提供两个功能一个是对两个参数交换他们的值,另一个功能是求两个参数的最大公约数。然后创建一个应用程序使用这个DLL。运行并输出结果。

三、创建DLL

让我们创建以下三个C#代码文件:

1、 MySwap.cs

using System;

namespace MyMethods

{

public class SwapClass

{

public static bool Swap(ref long i,ref long j)

{

i = i+j;

j = i-j;

i = i-j;

return true;

}

}

}

2、 MyMaxCD.cs

using System;

namespace MyMethods

{

public class MaxCDClass

{

public static long MaxCD(long i, long j)

{

long a,b,temp;

if(i>j)

{

a = i;

b = j;

}

else

{

b = i;

a = j;

}

temp = a % b;

while(temp!=0)

{

a = b;

b = temp;

temp = a % b;

}

return b;

}

}

}

}需要注意的是:我们在制作这两个文件的时候可以用Visual Studio.NET或者其他的文本编辑器,就算是记事本也可以。这两个文件虽然不在同一个文件里面,但是他们是属于同一个namespace(名称空间)这对以后我们使用这两个方法提供了方便。当然他们也可以属于不同的名称空间,这是完全可以的,但只是在我们应用他们的时候就需要引用两个不同的名称空间,所以作者建议还是写在一个名称空间下面比较好。
接下来的任务是把这两个cs文件变成我们需要的DLL文件。方法是这样的:
在安装了Microsoft.NET Framework的操作系统上,我们可以在Windows所在目录下找到Microsoft.NET目录。在这个目录下面提供了C#的编译器,CSC.EXE
运行:csc /target:library /out:MyDLL.DLL MySwap.cs MyMaxCD.cs
完成后可在本目录下面找到我们刚才生成的MyDLL.DLL文件
/target:library 编译器选项通知编译器输出 DLL 文件而不是 EXE 文件。后跟文件名的 /out 编译器选项用于指定 DLL 文件名。
如果/out后面不跟文件名编译器使用第一个文件 (MySwap.cs) 作为 DLL 文件名。生成的文件为MySwap.DLL文件
OK!我们创建动态链接库文件的任务完成了,现在是我们享受劳动成果的时候了,下面我将介绍如何使用我们所创建的动态链接库文件。

四、 使用DLL

我们简单写一个小程序来测试一下我们刚才写的两个方法是否正确,好吧,跟我来:
MyClient.cs
using System;

using MyMethods;

//这里我们引用刚才定义的名称空间,如果刚才的两个文件我们写在两个不同的名称空间
class MyClient

{

public static void Main(string[] args)

{

if (args.Length != 2)

{

Console.WriteLine("Usage: MyClient ");

return;

}

long num1 = long.Parse(args[0]);

long num2 = long.Parse(args[1]);

SwapClass.Swap(ref num1,ref num2);

// 请注意,文件开头的 using 指令使您得以在编译时使用未限定的类名来引用 DLL 方法

Console.WriteLine("The result of swap is num1 = {0} and num2 ={1}",num1, num2);

long maxcd = MaxCDClass.MaxCD(num1,num2);

Console.WriteLine("The MaxCD of {0} and {1} is {2}",num1, num2, maxcd);

}

}

若要生成可执行文件 MyClient.exe,请使用以下命令行:

csc /out:MyClient.exe /reference:MyLibrary.DLL MyClient.cs

/out 编译器选项通知编译器输出 EXE 文件并且指定输出文件名 (MyClient.exe)。/reference 编译器选项指定该程序所引用的 DLL 文件。

五、执行

若要运行程序,请输入 EXE 文件的名称,文件名的后面跟两个数字,例如:

MyClient 123 456

六、输出

The result of swap is num1 = 456 and num2 = 123

The MaxCD of 456 and 123 is 3

七、小结

动态链接具有下列优点:

节省内存和减少交换操作。很多进程可以同时使用一个 DLL,在内存中共享该 DLL 的一个副本。相反,对于每个用静态链接库生成的应用程序,Windows 必须在内存中加载库代码的一个副本。
节省磁盘空间。许多应用程序可在磁盘上共享 DLL 的一个副本。相反,每个用静态链接库生成的应用程序均具有作为单独的副本链接到其可执行图像中的库代码。
升级到 DLL 更为容易。DLL 中的函数更改时,只要函数的参数和返回值没有更改,就不需重新编译或重新链接使用它们的应用程序。相反,静态链接的对象代码要求在函数更改时重新链接应用程序。
提供售后支持。例如,可修改显示器驱动程序 DLL 以支持当初交付应用程序时不可用的显示器。
支持多语言程序。只要程序遵循函数的调用约定,用不同编程语言编写的程序就可以调用相同的 DLL 函数。程序与 DLL 函数在下列方面必须是兼容的:函数期望其参数被推送到堆栈上的顺序,是函数还是应用程序负责清理堆栈,以及寄存器中是否传递了任何参数。
提供了扩展 MFC 库类的机制。可以从现有 MFC 类派生类,并将它们放到 MFC 扩展 DLL 中供 MFC 应用程序使用。
使国际版本的创建轻松完成。通过将资源放到 DLL 中,创建应用程序的国际版本变得容易得多。可将用于应用程序的每个语言版本的字符串放到单独的 DLL 资源文件中,并使不同的语言版本加载合适的资源。
使用 DLL 的一个潜在缺点是应用程序不是独立的;它取决于是否存在单独的 DLL 模块。

本文来自: 动态网站制作(www.knowsky.com) 详细出处参考:http://www.knowsky.com/3394.html

请使用浏览器的分享功能分享到微信等