C#委托

 

C#委托

 

 

原文地址: http://www.cnblogs.com/warensoft/archive/2010/03/19/1689806.html 有改动

 

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

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

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

        int Max(int a, int b)
        {
            return a > b ? a : b;
        }

        int Min(int a, int b)
        {
            return a < b ? a : b;
        }


 

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

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

 

 

//定义一个函数指针,并声明该指针可以指向的函数的返回值为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动态指向具备相同签名的方法(所谓的方法签名,是指一个方法的返回值类型及其参数列表的类型)。

 

委托的建立

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

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

delegate int MyDelegate(int a, int b);

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

//利用委托类的构造方法指定,这是最为常见的一种方式
MyDelegate md = new MyDelegate(Max);
//利用自动推断方式来指明要调用的方法,该形式更类似于函数指针
MyDelegate md = Max;

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

int c = md(a, b);

 

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

首先,添加如下控件:

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

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

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

Ø  一个Button,用来执行运算

界面如下图所示:

 

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

        int Max(int a, int b)
        {
            return a > b ? a : b;
        }

        int Min(int a, int b)
        {
            return a < b ? a : b;
        }


 

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

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace 利用委托实现方法的动态调用
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        int Max(int a, int b)
        {
            return a > b ? a : b;
        }

        int Min(int a, int b)
        {
            return a < b ? a : b;
        }

 

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

        delegate int MyDelegate(int a, int b);
        MyDelegate md = null;


 

上面的代码中,我们可以发现,此时,还没有让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);
        }

 

运行的效果如下图所示:

求最大值


求最小值

多播委托(MulticastDelegate)

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

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

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

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

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

Ø  void CreateLogFile(string originalPath):用于创建日志文件

Ø  void WriteToDb(string originalPath):用于将文件写入数据库

代码如下:

方法:void CreateLogFile(string originalPath)

       /// <summary>
       /// 用于生成日志文档
       /// </summary>
       /// <param name="originalPath">文件的原始路径</param>
        static void CreateLogFile(string originalPath)
        {
            if (!Directory.Exists("log"))
            {
                Directory.CreateDirectory("log");
            }

            StreamWriter sw = new StreamWriter("log/log.text", true);
            sw.WriteLine("新文件已经创建,创建时间:{0},文件路径:{1}", DateTime.Now.ToLongTimeString(), originalPath);
            sw.Close();
            Console.WriteLine("已写入日志!");
        }


 

方法:void WriteToDb(string originalPath)

        /// <summary>
        /// 用于将文件写入数据库
        /// </summary>
        /// <param name="originalPath">文件的原始路径</param>
        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=123456");
            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("已写入数据库");
        }

 

上面两个方法,具备相同签名,如果想同时串行调用这两个方法,还要定义一个委托类型,代码如下:

        /// <summary>
        /// 生成一个委托,用于实现多播操作
        /// </summary>
        /// <param name="path">文件的原始路径</param>
        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");

            Console.ReadLine();
        }


 

在主函数中,首先创建一个原始文件,然后建立两个委托分别指向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;


该案例的完整代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data.SqlClient;
using System.Data;

namespace 多播委托
{
    class Program
    {

       /// <summary>
       /// 用于生成日志文档
       /// </summary>
       /// <param name="originalPath">文件的原始路径</param>
        static void CreateLogFile(string originalPath)
        {
            if (!Directory.Exists("log"))
            {
                Directory.CreateDirectory("log");
            }

            StreamWriter sw = new StreamWriter("log/log.text", true);
            sw.WriteLine("新文件已经创建,创建时间:{0},文件路径:{1}", DateTime.Now.ToLongTimeString(), originalPath);
            sw.Close();
            Console.WriteLine("已写入日志!");
        }

        /// <summary>
        /// 用于将文件写入数据库
        /// </summary>
        /// <param name="originalPath">文件的原始路径</param>
        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=123456");
            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("已写入数据库");
        }

        /// <summary>
        /// 生成一个委托,用于实现多播操作
        /// </summary>
        /// <param name="path">文件的原始路径</param>
        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");

            Console.ReadLine();
        }

    }
}

该案例的运行效果如下:

首先,系统中并不存在日志文件,如下图所示:

用于储存文件的数据库结构如下:

数据表中的原始数据为空,如下图所示:

执行完程序之后,窗口的效果如下图所示:

日志文件已经生成,内容如下图所示:

数据库的效果如下图所示:

 

匿名方法

在前面的代码中,用户可以发现,在使用委托时,无论该代码难易,都需要将功能性代码放置在一个方法中,再利用委托指向该方法。在C#2.0以及C#3.0中这种情况得到了改善,在C#2.0中,我们可以利用匿名方法(Anonymous Method)来简化委托的使用,在C#3.0中,我们可以Lambda表达式使其得到进一步简化,关于Lambda表达式的相关内容,请参见C#3.0程序设计。

所谓匿名方法(Anonymous Method),是指在使用委托时,可以不再事先定义一个方法,然后再让委托指向方法,匿名委托允许开发人员,使用内联方式,直接让一个委托指向一个功能代码段。下面代码对比了传统方法中委托的会用,以及利用匿名方法的简化操作:

传统方法使用委托:先定义一个方法,再定义委托,并指向方法

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace 不利用匿名方法简化委托的使用
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public void Run()
        {
            if (!Directory.Exists("log"))
            {
                Directory.CreateDirectory("log");
            }
            StreamWriter sw = new StreamWriter("log/不利用匿名方法简化委托的使用.text", true);

            for (int i = 0; i < 100; i++)
            {
                sw.WriteLine(i.ToString());
            }
            sw.Close();
        }

        delegate void MyDelegate();

        private void button1_Click(object sender, EventArgs e)
        {
            MyDelegate md = new MyDelegate(this.Run);
            md();
        }
    }
}


利用匿名方法简化委托的使用

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace 利用匿名方法简化委托的使用
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        delegate void MyDelegate();

        private void button1_Click(object sender, EventArgs e)
        {
            MyDelegate md = new MyDelegate(
                delegate()
                {
                    if (!Directory.Exists("log"))
                    {
                        Directory.CreateDirectory("log");
                    }

                    StreamWriter sw = new StreamWriter("log/利用匿名方法简化委托的使用.text", true);

                    for (int i = 0; i < 100; i++)
                    {
                        sw.WriteLine(i.ToString());
                    }

                    sw.Close();
                }
            );

            md();
        }

    }
}


从上面代码的对比中,不难发现,使用匿名方法,省去了定义方法的步骤。实际上,在多线程编程的时候,使用匿名方法可以使得代码变的简化,并提高了可读性。下面代码是在不使用匿名方法的情况下编写多线程代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace 不使用匿名方法编写多线程
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public void Run()
        {
            for (int i = 0; i < 100000; i++)
            {
                this.textBox1.Text = i.ToString();
            }
            thread.Abort();
        }

        Thread thread = null;

        private void button1_Click(object sender, EventArgs e)
        {
            CheckForIllegalCrossThreadCalls = false;
            this.thread = new Thread(new ThreadStart(this.Run));
            this.thread.Start();
        }

    }
}


利用匿名方法,可以将上面的代码改写为:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace 使用匿名方法编写多线程
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            CheckForIllegalCrossThreadCalls = false;
            Thread thread = null;
            thread = new Thread(
                delegate()
                {
                    for (int i = 0; i < 100000; i++)
                    {
                        this.textBox1.Text = i.ToString();
                    }
                    thread.Abort();
                }
            );
            thread.Start();
        }
    }
}


使用内联方式可以让代码更好理解!


 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值