• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

在C#中,委托(delegate)

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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

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

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

int Max(int x,int y)

{

return x>y?x:y;

}

int Min(int x,int y)

{

return x<y?x:y;< font="">

}

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

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

//定义一个函数指针,并声明该指针可以指向的函数的返回值为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#中委托的使用。

l         案例操作020601:利用委托实现方法的动态调用

首先,添加如下控件:

Ø  两个RadioButton,分别用来让用户选择求最大值以及求最小值

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

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

Ø  一个Button,用来执行运算

界面如下图所示:

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

int Max(int x,int y)

{

return x>y?x:y;

}

int Min(int x,int y)

{

return x<y?x:y;< font="">

}

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

下一步:为了使用委托来实现动态指向,我们需要建立一个委托类“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不为空的时候,就一定会指向MaxMin当中的一个。

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

private void Form1_Load(object sender, EventArgs e)

{

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

}

运行的效果如下图所示:

求最大值

求最小值

l         委托使用的注意事项

Ø   在C#中,所有的委托都是从System.MulticastDelegate类派生的。

Ø   委托隐含具有sealed属性,即不能用来派生新的类型。

Ø   委托最大的作用就是为类的事件绑定事件处理程序。

Ø   在通过委托调用函数前,必须先检查委托是否为空(null),若非空,才能调用函数。

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

Ø   在创建委托实例时,需要传递将要映射的方法或其他委托实例以指明委托将要封装的函数原型(.NET中称为方法签名:signature)。注意,如果映射的是静态方法,传递的参数应该是类名.方法名,如果映射的是实例方法,传递的参数应该是实例名.方法名。

Ø   只有当两个委托实例所映射的方法以及该方法所属的对象都相同时,才认为它们是想等的(从函数地址考虑)。

 

6.1.讨论委托类型

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

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

l         MulticastDelegate类

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

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

l         Invoke方法

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

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

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

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

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

6.1.使用多播委托(MulticastDelegate

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

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

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

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

l         案例操作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 .ToLongTimeString (),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;pwd=sa");

     SqlCommand cmd = con.CreateCommand();

     cmd.CommandText = "insert into tb_files values(@ID,@FileName,@CreationTime,@FileBytes)";

     cmd.Parameters.Add("@ID",SqlDbType.UniqueIdentifier).Value=Guid.NewGuid ();

     cmd.Parameters.Add("@CreationTime",SqlDbType.DateTime).Value =DateTime.Now ;

     cmd.Parameters.Add("@FileName",SqlDbType.NText).Value=Path.GetFileName (originalPath);           

     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 .ToLongTimeString (),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();


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#WinForm的SplitContainer控件固定Panel大小[转]发布时间:2022-07-13
下一篇:
C#线程的暂停和恢复的实现发布时间:2022-07-13
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap