委托在C#中可以看作是对象的一种新类型。一般情况下,我们经常把数据作为参数传递给某个方法,但有时某个方法执行的操作并不是指对数据进行操作,而是要对另一个方法进行操作,这就需要使用委托了。
委托典型的使用场合就是线程。当试图启动一个线程时,使用的Thread.Start()方法就必须带有一个参数,该参数包含了另一个方法的内容,即委托方法。
委托的另一个场合就是事件,GUI编程主要是处理事件,比如Click事件是我们在应用程序中处理的,但是Windows是怎样知道调用应用程序的Click处理函数来响应Click事件的呢?这就是委托的作用。事件实际上是委托的一种特殊形式。
如果从C++程序员的角度来理解,一个委托实例,可以理解成函数指针。但这种直接的函数指针调用会导致一些问题,比如类型的安全性,而且在面向对象的编程中,方法是无法孤立存在的,它通常需要与类实例相关联。所以,在C#中,就把这些方法包装在一种新类型的对象中,即委托。也就是说,委托其实是一种很特殊的对象类型(与类的概念一致)。所不同的是,以前定义的对象都可以包含数据,而委托却只包含方法签名。
一、定义委托
定义委托,基本上是定义一个新类。你可以在定义类的任何地方定义委托,也可以在另一个类的内部定义一个委托。定义委托,其语法类似于定义一个方法,但没有方法体,定义的前面加上关键字delegate即可。
delegate void MyDelegate(string sDescription);
如上就定义了一个带有string参数,无返回(void)的委托。任何具有这样签名格式的方法都可以看成是这个委托的一个实例,不管这个方法是任何对象上的实例方法或者是静态方法。
二、委托的调用
在调用委托之前,你需要先声明一个委托,并把它绑定到一个真正实现的方法上即可。
private delegate void MyDelegate(string sDescription);
static void OutPut(string sPara)
{
Console.WriteLine(sPara);
}
static void Main (string[] args)
{
//声明一个委托,并把OutPut当成是委托的一个实例
MyDelegate dlgt = new MyDelegate(OutPut);
//直接调用委托
dlgt("Call the delegate.");
}
于是程序输出结果"Call the delegate."。
另一种委托的调用方式就是使用Invoke的形式来调用,当然两种调用结果是不一样的。
三、委托代码执行的所在线程
委托代码执行所在的线程与调用方式相关,下面的例子说明了调用方式与执行线程的关系。
//定义委托
delegate void MyDelegate(string sDesp);
//委托实例
private void dlgtOutPut(string sDesp)
{
Thread.Sleep(2000);
//输出代码执行的线程名
Console.WriteLine(sDesp + ",Thread Name=" + Thread.CurrentThread.Name);
}
private void ThreadEntity()
{
//设置子线程名称
Thread.CurrentThread.Name = "Child Thread";
//实例化一个委托
MyDelegate dlgt = new MyDelegate(dlgtOutPut);
//
dlgtOutPut("直接调用委托");
this.Invoke(dlgt,new object[]{("使用Invoke调用委托")});
Console.WriteLine("After Invoke");
}
private void btnOK_Click(object sender, System.EventArgs e)
{
//设置主线程名称
Thread.CurrentThread.Name = "Main Thread";
//启动线程
Thread thread = new Thread(new ThreadStart(ThreadEntity));
thread.Start();
}
程序的输出是:
直接调用委托,Thread Name=Child Thread
使用Invoke调用委托,Thread Name=Main Thread
After Invoke
可见,不同的调用方式,执行委托的线程也不一样。
如果直接调用委托,则委托代码在子线程中执行;
如果使用Invoke来调用委托,则发现委托代码则在主线程中执行。确切地说,当使用Invoke调用委托时,则在拥有此控件的基础窗口句柄的线程上执行委托。
四、同步与异步委托
其实,从上面的例子可以看到,在委托中我们故意让委托Sleep(2000)。但发现"After Invoke"总是在委托的输出后面之后才输出。可见,不管是直接调用委托,还是使用Invoke调用委托的方式,都是同步的。第二种调用方式,从Windows程序的角度来看,其实就象是子线程向主线程使用SendMessage发送了一个消息,它需要最终等待消息返回,子线程才会继续执行。
如果需要异步调用委托,则需要使用BeginInvoke的调用方式。现在我们修改上面的调用:
{
......
IAsyncResult iAsync = this.BeginInvoke(dlgt, new object[]{"使用BeginInvoke调用委托"});
Console.WriteLine("After BeginInvoke");
//等待委托执行完成
this.EndInvoke(iAsync);
}
这次你会发现输出结果的顺序是:
"After BeginInvoke"
使用BeginInvoke调用委托,Thread Name=Main Thread
所以,如果想要委托是异步执行的,就需要使用BeginInvoke来调用委托,并且异步委托也必须在拥有控件线程(示例中是主线程)上执行的。异步调用委托就象是向主线程使用PostMessage一样,它不必等待消息结果就返回了。
可见,对于委托,调用方式的不同,不仅会决定了委托执行的线程,也决定了委托是否异步执行。