委托是.NET框架支持的一种特殊类型。它可以被实例化,并且可以通过任何与方法签名相匹配的目标方法与实例组合形成。C#允许使用 delegate 关键字来创建特殊类,我们称这个特殊类为委托类。这些委托类的实例则被称为委托对象。从概念上来讲,一个委托对象是一个或者多个方法(静态或实例)的引用。因此我们可以像调用一个方法那样,来调用一个委托。这就导致对一个方法的调用,但是需要注意的是,对这些方法的调用是通过调用委托对象的同一个线程来完成的。我们把这个作为一个同步调用。但是,在对一个方法进行同步调用时,调用线程会在调用活动时阻塞。当线程被阻塞时,它可以创建其他的线程,事实上,这个时候,CPU仍有可能处于空闲。因此,新创建的这些线程尽管有可能没有占用CPU,但是他们这是在浪费资源,这是不经济的。当一个线程对一个方法做异步调用时,这个调用能够立即返回。调用线程不会被阻塞,它可以继续做其他的工作。.NET底层结构为这个方法调用生成一个线程并且通过调用代码传递参数。这个异步线程可以在调用线程进行的同时,运行该方法。如果这个方法产生了一些数据并且返回了这些值,那么调用线程必须能够访问这些数据。.NET异
这是我在cnblogs的第一篇博客。在大学专业是应用物理,就学过C语言,工作后,要用C#编程,狂学了两个礼拜,就开始跟着做项目了。很多地方都不懂,基本是现学现卖。以后打算重点加强一下C#的学习,总觉得光看别人的代码,长进的不快,自己目前又写不出什么原创的文章,就决定先从翻译做起,每个礼拜,在http://www.codeproject.com/ 上面找一篇C#相关文章,翻译一遍,贴到博客上,以后水平有所提高,有自己的想法之后,再写一些原创的文章。没做过翻译,开始的时候,肯定会有些地方翻译的不到位甚至错误,还请大家多多指教。
原文地址 http://www.codeproject.com/KB/cs/Method_Invocation.aspx
介绍
委托是.NET框架支持的一种特殊类型。它可以被实例化,并且可以通过任何与方法签名相匹配的目标方法与实例组合形成。C#允许使用 delegate 关键字来创建特殊类,我们称这个特殊类为委托类。这些委托类的实例则被称为委托对象。从概念上来讲,一个委托对象是一个或者多个方法(静态或实例)的引用。因此我们可以像调用一个方法那样,来调用一个委托。这就导致对一个方法的调用,但是需要注意的是,对这些方法的调用是通过调用委托对象的同一个线程来完成的。我们把这个作为一个同步调用。但是,在对一个方法进行同步调用时,调用线程会在调用活动时阻塞。当线程被阻塞时,它可以创建其他的线程,事实上,这个时候,CPU仍有可能处于空闲。因此,新创建的这些线程尽管有可能没有占用CPU,但是他们这是在浪费资源,这是不经济的。当一个线程对一个方法做异步调用时,这个调用能够立即返回。调用线程不会被阻塞,它可以继续做其他的工作。.NET底层结构为这个方法调用生成一个线程并且通过调用代码传递参数。这个异步线程可以在调用线程进行的同时,运行该方法。如果这个方法产生了一些数据并且返回了这些值,那么调用线程必须能够访问这些数据。.NET异步特征支持两个机制:调用线程要么要求得到结果,要么.NET底层结构在结果准备就绪后,将结果传递给调用线程。这篇文章的目的就是解释委托的概念并且讲解如何异步的使用委托。
委托
在C#中,一个新的委托类型通过delegate关键字来创建。
public delegate void MyDelegate(int x, int y);
我们新建了一个委托类型,名为MyDelegate,它可以通过拥有两int型参数、返回类型为void的方法来构建。我们的委托可以通过一个目标形成、传递并在将来适当的时候被调用。这种调用在C#中看起来像一个普通的函数调用。

Code
class Foo
{
void PrintPair(int a, int b)
{
Console.WriteLine("a = {0}", a);
Console.WriteLine("b = {0}", b);
}
void CreateAndInvoke()
{
// implied 'new MyDelegate(this.PrintPair)':
MyDelegate del = PrintPair;
del(10, 20);
}
还不明白吗?CreateAndInvoke方法构建了一个新的MyDelegate委托,通过带有当前this指针的PrintPair方法作为目标来形成。在优先类型系统中,由编译器产生的实际的空闲显示了一些委托的复杂性。

Code
struct MyDelegate : System.MulticastDelegate
{
public MyDelegate(object target, IntPtr, methodPtr);
private object target;
private IntPtr methodPtr;
public internal void Invoke(int x, int y)
public internal System.IAsyncResult BeginInvoke(int x, int y,
System.IAsyncCallback callback, object state);
public internal void EndInvoke(System.IAsyncResult result);
}
这构造器用来通过一个目标对象和一个函数指针形成一个委托。Invoke, BeginInvoke, 和 EndInvoke 方法执行委托调用程序并且作为内敛函数来通知CLR它所提供的执行过程,他们的空闲部分被留空。当BeginInvoke和EndInvoke函数是异步程序模式时,调用就是同步调用。但是,首先要注意的是,MyDelegate类型打破了这个规则,即它的结构只能从System.ValueType来获得,而不能从其他类型获得。委托在通用类型系统(CTS)中有独特的支持,因此,这是被允许的。同时也要注意MyDelegate是从MulticastDelegate获得的,这个类型是C#中所有委托的通用基类,并且它支持多对象的委托。下面来看另一中委托的创建:
public delegate int StringDelegate(string str);
它可以在一个类或全局范围内声明。C#编译器会从这个声明中生成一个新类,这个类派生自System.MulticastDelegate。再次检测这个类和其基类System.Delegate
的方法:

Code
public sealed class StringDelegate : System.MulticastDelegate


{
public StringDelegate (object obj, int method);
public virtual int Invoke(string str);
public virtual IAsyncResult BeginInvoke(string str,
AsyncCallback asc, object stateObject);
public virtual int EndInvoke(IAsyncResult result);
}
现在,让我们来看一些和我们创建的第一个委托MyDelegate相关代码:

Code
using System;

public static class App
{
public delegate void MyDelegate(int x, int y);
public static void PrintPair(int a, int b)

{
Console.WriteLine("a = {0}", a);
Console.WriteLine("b = {0}", b);
}
public static void Main()

{
// Implied 'new MyDelegate(this.PrintPair)':
MyDelegate d = PrintPair;
// Implied 'Invoke':
d(10, 20);
}
}
当编译的时候,我们得到这样的输出结果:
a = 10
b = 20
内部委托
假设我们在C#中再次定义了我们自己的MyDelegate类型,就像这样:
delegate string MyDelegate(int x);
我们现在知道了这是一个函数指针类型,它可以引用任何有一个int类型参数并且返回一个string类型的方法。我们也知道当使用这种委托类型的实例时,我们会声明MyDelegate类型的变量。还有,我们知道在后台,编译器为我们生成了一个新的类类型:
private sealed class MyDelegate : MulticastDelegate


{
public extern MyDelegate(object object, IntPtr method);
public extern virtual string Invoke(int x);
public extern virtual IAsyncResult BeginInvoke(int x,
AsyncCallback callback, object object);
public extern virtual string Endinvoke((IAsyncResult result);
}
现在我们假设我们有自己的自定义类型MyType,带有一个和MyDelegate的方法特征完全匹配的MyFunc方法。注意,现在的参数并没有统一命名。这样是可行的,那是因为委托只要求期待的类型在正确的特征位置被找到:
class MyType


{
public string MyFunc(int foo)

{
return "MyFunc called with the value '" + foo + "' foo foo;
}
}
在一个元数据中,一旦我们有了一个委托类型和一个我们想要调用的目标函数,我们必须从目标形成一个实例。通过使用MyDelegate(object, IntPtr)构造器,就构建了一个委托类型的新实例。这段代码将目标作为第一个参数,将一个代码函数的指针作为第二个参数进行传递。语法如下:
MyType mt = new MyType();
MyDelegate md = mt.MyFunc;
那么,我们把这些部分累加起来形成一个整体:

Code
using System;
delegate string MyDelegate(int x);
class MyType


{
public string MyFunc(int foo)

{
return "MyFunc called with the value '" + foo + "' for foo";
}
}


public class Program
{
public static void Main()

{
MyType mt = new MyType();
MyDelegate md = mt.MyFunc;
Console.WriteLine(md.Invoke(5));
Console.WriteLine(md(5));
}
}
代码编译后输出结果:
MyFunc called with the value '5' for foo
MyFunc called with the value '5' for foo
那么是是异步委托呢?
在使用一个异步委托之前,记住所有的委托类型都自动地提供名为BeginInvoke和EndInvoke两个方法。这些方法的特性是基于包含他们的委托类型的。例如下面的委托类型:
delegate int MyDelegate(int x, int y)
下面是编译器生成的方法:
IAsyncResult BeginInvoke(int x, int y, AsyncCallback callback,
object object, IAsyncResult result);
int EndInvoke (IAsyncResult result);
这两个方法由编译器生成。以异步方式调用一个方法,你必须用一个拥有同样特性的委托对象引用它。然后,你必须在这个委托对象上调用BeginInvoke方法。正如你看到的,编译器确保BeginInvoke方法的第一个参数是被调用方法的参数。而后两个参数,IAsyncResult和object,稍后再做讨论。异步调用的返回值可以通过EndInvoke方法回收。编译器同时也会确保EndInvoke返回值类型和委托返回类型一致(在我们的例子中,这个类型是int)。EndInvoke调用被阻塞,意味着这个调用只有当异步执行完成才会返回。下面的例子对ShowSum方法进行了异步调用:

Code
using System;
using System.Threading;

public class Program
{
public delegate int TheDelegate( int x, int y);

static int ShowSum( int x, int y )
{
int sum = x + y;
Console.WriteLine("Thread #{0}: ShowSum() Sum = {1}",
Thread.CurrentThread.ManagedThreadId, sum);
return sum;
}

public static void Main()
{
TheDelegate d = ShowSum;
IAsyncResult ar = d.BeginInvoke(10, 10, null, null);
int sum = d.EndInvoke(ar);
Console.WriteLine("Thread #{0}: Main() Sum = {1}",
Thread.CurrentThread.ManagedThreadId, sum);
}
}
输出:
Thread #3: ShowSum() Sum = 20
Thread #1: Main() Sum = 20
BeginInvoke方法对委托的每个参数都有一个参数(就像是调用),并且加了两个参数:一个是IAsyncCallback委托,它在异步操作完成时被调用,另一个参数是object,它作为回调函数的IAsyncResult.AsyncState的属性值被传递。这个方法返回一个IAsyncResult,它可以被用在监测完成,也可以等待在WaitHandel,或者完成异步调用。
public interface IAsyncResult


{
// Properties

object AsyncState
{ get; }

WaitHandle AsyncWaitHandle
{ get; }

bool CompletedSynchronously
{ get; }

bool IsCompleted
{ get; }
}
当委托完成执行时,必须在这个委托上调用EndInvoke方法,在IAsyncResult中传递。这会清除WaitHandle(如果它被分配了),如果委托执行失败了,就会抛出异常并且有一个和基本方法类型相匹配的返回类型。它把委托调用返回的值返回:

Code
using System;

public sealed class Program
{
delegate int IntIntDelegate(int x);

private static int Square(int x)
{ return x * x; }
private static void AsyncDelegateCallback(IAsyncResult ar)

{
IntIntDelegate f = (IntIntDelegate)ar.AsyncState;
Console.WriteLine(f.EndInvoke(ar));
}

public static void Main()

{
IntIntDelegate f = Square;


/**//* Version 1: Spin wait (quick delegate method) */
IAsyncResult ar1 = f.BeginInvoke(10, null, null);
while (!ar1.IsCompleted)
// Do some expensive work while it executes.
Console.WriteLine(f.EndInvoke(ar1));


/**//* Version 2: WaitHandle wait (longer delegate method) */
IAsyncResult ar2 = f.BeginInvoke(20, null, null);
// Do some work.
ar2.AsyncWaitHandle.WaitOne();
Console.WriteLine(f.EndInvoke(ar2));


/**//* Version 3: Callback approach */
IAsyncResult ar3 = f.BeginInvoke(30, AsyncDelegateCallback, f);
// We return from the method (while the delegate executes).
}
}
输出:
100
400
当委托完成执行时,必须在这个委托上调用EndInvoke方法,在IAsyncResult中传递。这会清除WaitHandle(如果它被分配了),如果委托执行失败了,就会抛出异常并且有一个和基本方法类型相匹配的返回类型。它把委托调用返回的值返回。现在,如果这个方法有引用类型并且是作为参数传递的参数,那么这个异步方法就会在可以改变它的状态的引用类型上调用方法。可以利用这一点来从异步方法来返回值。下面的例子就说明了这一点。GetData这个委托把System.Array类型的一个对象作为参数。一旦它通过引用被传递进去,这个方法就能改变数组里的值。然而,这个对象如果可以被两个线程访问,必须保证这个共享对象在异步方法完成之前不被调用。

Code
using System;
class App


{
delegate void GetData(byte[] b);
static void GetBuf(byte[] b)

{
for (byte x = 0; x < b.Length; x++)
b[x] = (byte)(x*x);
}
static void Main()

{
GetData d = new GetData(App.GetBuf);
byte[] b = new byte[10];
IAsyncResult ar;
ar = d.BeginInvoke(b, null, null);
ar.AsyncWaitHandle.WaitOne();
for (int x = 0; x < b.Length; x++)
Console.Write("{0} ", b[x]);
}
}
输出:
0
1
4
9
16
25
36
49
64
81
100
使用回调方法,必须用一个作为下一个BeginInvoke方法的最后一个参数的System.AsyncCallback类型的委托对象来引用它。这个方法必须和委托类型保持一致,也就是说它的返回类型必须是void(在下面的例子中)并采用单一的IAsyncResult类型参数:

Code
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;

class Program
{
public delegate int MyDelegate(int x, int y);
static AutoResetEvent e = new AutoResetEvent(false);

static int WriteSum( int x, int y)
{
Console.WriteLine("Thread# {0}: Sum = {1}",
Thread.CurrentThread.ManagedThreadId, x + y);
return x + y;
}


static void SumDone(IAsyncResult async)
{
Thread.Sleep( 1000 );
// AsyncResult of the System.Runtime.Remoting.Messaging namepsace
MyDelegate func = ((AsyncResult) async).AsyncDelegate as MyDelegate;
int sum = func.EndInvoke(async);
Console.WriteLine("Thread# {0}: Callback method sum = {1}",
Thread.CurrentThread.ManagedThreadId, sum);
e.Set();
}


static void Main()
{
MyDelegate func = WriteSum;

// the C# 2.0 compiler infer a delegate object of type
// AsyncCallback to reference the SumDone() method
IAsyncResult async = func.BeginInvoke(10, 10, SumDone, null);
Console.WriteLine("Thread# {0}: BeginInvoke() called! Wait for SumDone() completion.",
Thread.CurrentThread.ManagedThreadId);
e.WaitOne();
Console.WriteLine("Thread# {0}: Bye
.",
Thread.CurrentThread.ManagedThreadId);
}
}
编译执行后的结果如下:
Thread# 1: BeginInvoke() called! Wait for SumDone() completion.
Thread# 3: Sum = 20
Thread# 3: Callback method sum = 20
Thread# 1: Bye
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步