This article—C# Delegates and Events— by Jeff Suddeth explains delegates and their applications in anonymous methods, asynchonous method calls, events, multicast delegates, threads, and Win32 callbacks.
A delegate is a .NET class that encapsulates a method, but not in the same way other classes encapsulate methods. A delegate actually stores the address of a method that is contained in some other class. So, a delegate is really the equivalent of a function pointer in C++. However, they are also far more than that.
In this article, I explain the many uses of delegates. I begin with a simple example using a delegate to invoke a method. Then, I show several other uses of delegates including multicast delegates, thread delegates, anonymous methods, asynchronous method calls, events, and Win32 callbacks.
委托就是一个封装方法的.NET类,但是和其他类封装方法的方式又是不一样的。委托实际上存储的是包含在其他类的方法的地址。因此,委托实际上相当于C++里的函数指针,但是它有不远只是函数指针。
multicast delegate 多播委托
thread delegate 线程托管
anonymous method 匿名方法
asynchronous method call 异步方法调用
event 事件
Win32 callback Win32函数调用
You declare a delegate in a class or namespace using the delegate
keyword and by specifying the signature of the method it will call. The following line declares a delegate named GreetingDelegate
which will reference a method that returns void
and accepts a string as the argument.
delegate void GreetingDelegate(string s);
Now that we have declared the GreetingDelegate
, we can instantiate it to encapsulate a method of that signature. Then, we can invoke the method through the delegate, just as if we invoked the method itself.
The next code sample creates an instance of GreetingDelegate
and uses it to invoke the SendGreeting
method.
class DelegateDemo1 { static void Main(string[] args) { GreetingDelegate gd = new GreetingDelegate(SendGreeting); gd("Hello"); } static void SendGreeting(string s) { Console.WriteLine(s); } }
The DelegateDemo1
class shown above contains a static method named SendGreeting
which has the same signature as the GreetingDelegate
defined earlier. The Main
method instantiates the GreetingDelegate
, passing the SendGreeting
method as argument. Next, Main
invokes the SendingGreeting
method through the delegate instance, passing the string “Hello”
.
Delegates only depend on the signature of the method, not on the class or object containing the method. Although the SendGreeting
method above was declared as static
, this is not a requirement. A delegate can reference an instance method as well. The next example uses the same GreetingDelegate
defined earlier to invoke an instance method
class Greeting { public void SendGreeting(string s) { Console.WriteLine(s); } } class DelegateDemo1 { static void Main(string[] args) { Greeting gr = new Greeting(); GreetingDelegate gd = new GreetingDelegate(gr.SendGreeting); gd ("Hello"); } }
This example defines a Greeting
class that contains a single method named SendGreeting
. Because its signature matches that of the GreetingDelegate
, the method can be invoked through our delegate.
In this example, the Main
method first instantiates the Greeting
class. Then, it creates an instance of GreetingDelegate
, passing the Greeting
object’s SendGreeting
method as argument. Finally, Main
calls the object’s SendGreeting
method by invoking the delegate.
As I said in the introduction, delegates do far more than provide an indirect way to call a method. They also contain an internal list of delegates—called the invocation list—which contains delegates of the same method signature. Whenever the delegate is invoked, it also invokes each delegate in its invocation list. You add and remove delegates from an invocation list using the overloaded +=
and -=
operators.
The next example defines two methods of the correct signature for our GreetingDelegate
and calls them both through a single call to an instance of the GreetingDelegate
.
class Greeting { public void SendGreeting(string s) { Console.WriteLine(s); } public void SendGreetingToFile(string s) { StreamWriter writer = new StreamWriter(@"c:\greeting.txt"); writer.WriteLine(s); writer.Close(); } } class DelegateDemo1 { static void Main(string[] args) { // create the Greeting object Greeting gr = new Greeting(); // create the delegates GreetingDelegate gd = new GreetingDelegate(gr.SendGreeting); gd += new GreetingDelegate(gr.SendGreetingToFile); // invoke both methods through the delegate gd("Hello"); } }
In this example, we have added a second method to the Greeting
class. The SendGreetingToFile
method does exactly as its name suggests. It creates a StreamWriter
instance and uses it to log the string argument to a file named "greeting.txt"
.
This time, after creating the GreetingDelegate
, the Main
method instantiates a second GreetingDelegate
and appends it to the first instances’ invocation list through the +=
operator. When the last line of Main
calls the delegate, both methods are invoked with the string “Hello”
.
So far, we have seen delegates used to call methods of other classes and objects. But, as I have already said, that is only the beginning. The fact that they can be bound to methods of any class or object gives them great utility. Delegates are central to .NET, as they are used in threads, events, and asynchronous programming. Now that we have seen how to create and use delegates, let’s take a look at some of their applications.
Multithreaded programming in .NET is implemented through the use of delegates. The .NET Framework defines the ThreadStart
delegate to invoke a method in a separate thread. The Thread
class, defined in the System.Threading
namespace, accepts an instance of the ThreadStart
delegate as its constructor argument. When you call the Thread
object’s Start
method, that Thread
object will invoke the delegate in its own thread of execution. The following example demonstrates this by calling a method named ThreadProc
in a separate thread.
static void ThreadProc() { // do some thread work CalculatePrimes(); } static void Main(string[] args) { Thread t = new Thread(new ThreadStart(ThreadProc)); t.Start(); t.Join(); }
In this example, the Main
method creates a Thread
object, passing a new ThreadStart
delegate to its constructor. That ThreadStart
delegate stores a reference to the ThreadProc
method. Then, Main
calls the Thread
object’s Start
method which causes its delegate to begin execution in a separate thread. The final line of Main
is a call to the Thread
object’s Join
method, which causes Main
to block, waiting for the thread’s delegated method call to return.
One thing you should consider about the Multithreaded example from the previous section is that the ThreadProc
method is never invoked directly from the source code. Instead, the Thread
object calls it through the ThreadStart
delegate as a result of our call to the Thread
object’s Start
method. An astute programmer might wonder why we should bother having this ThreadProc
method, since we are not calling it anyway. Wouldn’t it be a better design and more efficient to avoid having extra methods lying around? With that in mind, I introduce one of my favorite features of .NET version 2.0—anonymous methods.
An anonymous method is a block of code passed to the delegate at creation time. Since these blocks of code do not have method names, they can not be called, except through the delegate. You no longer have those useless methods lying around.
The following example shows the GreetingDelegate
that we defined earlier, this time being instantiated with an anonymous method.
class DelegateDemo { delegate void GreetingDelegate(string s); static void Main(string[] args) { // use an anonymous method for the delegate GreetingDelegate gd = delegate(string s) { Console.WriteLine(s); } // invoke the delegate gd("Hello"); } }
The GreetingDelegate
is declared as it was before. The interesting part is inside of the Main
method when the GreetingDelegate
is instantiated with an anonymous method. The syntax might take a minute to get used to. The delegate
keyword is followed by the parameter list that the delegate will accept. The parameter list is then followed by the block of code for the anonymous method. As before, the final line of the Main
method invokes the anonymous method through the delegate instance.
Now, let’s rewrite our thread example to use an anonymous method.
class DelegateDemo { static void Main(string[] args) { Thread t = new Thread(delegate() { // do some thread work CalculatePrimes(); }); t.Start(); t.Join(); } }
The ThreadStart
delegate passed to the Thread
constructor has been created with an anonymous method. Since ThreadStart
accepts no parameters, the parentheses following the delegate
keyword are empty. Notice that the bracket that closes the anonymous method’s body is inside of the closing parenthesis of the Thread
constructor call.
Another useful feature of delegates is the ability to execute a method asynchronously. That is, through a delegate, you can begin invocation of a method and then return immediately while the delegate executes its method in a separate thread.
委托另外一个有用的特征就是可以异步地执行一个方法。 也就是说,通过一个委托,你可以开始一个方法调用然后当委托在一个独立的线程中执行它的方法的时候迅速返回。
The following example demonstrates the use of delegates for asynchronous method invocation. To see this in action we need a method that simulates a lengthy operation, such as flushing data to disk. Below is a class named DataCache
, which may periodically need to write its cache to a file. The class contains a method named FlushToDisk
to simulate this operation.
FulshToDisk方法模拟这样的操作:间歇性的把自己的缓冲写入一个文件。
class DataCache { public int FlushToDisk(string fileName) { // simulate a long operation Thread.Sleep(4000); return 0; } }
Since writing to disk is an expensive operation, we would like execute the FlushToDisk
method asynchronously. The next example defines the CacheFlusher
delegate and then uses it to call the DataCache
object’s FlushToDisk
method asynchronously.
因为写入磁盘时一个费时的操作,我们可以异步执行FlushToDisk方法。
class Program {
// define a delegate for the long operation
delegate int CacheFlusher(string fileName);
static void Main(string[] args) {
// create the object and then the delegate
DataCache cache = new DataCache();
CacheFlusher flusher = new CacheFlusher(cache.FlushToDisk);
// invoke the method asynchronously
IAsyncResult result = flusher.BeginInvoke("data.dat", null, null);
// get the result of that asynchronous operation
int retValue = flusher.EndInvoke(result);
Console.WriteLine(retValue);
}
}
In the first asynchronous example, the Main
method creates an instance of DataCache
and encapsulates its FlushToDisk
method in an instance of the CacheFlusher
delegate.
At the time our CacheFlusher
delegate is declared, the compiler generates two methods for it named BeginInvoke
and EndInvoke
.
这个时候CacheFlusher委托被声明了,编译器产生了2个方法,一个是BeginInvoke,一个是EndInvoke.
These two methods, respectively, are used to kick off a lengthy method call and then retrieve its return value when that method is finished. Rather than invoking the delegate as before, Main
executes the delegate as an asynchronous operation by calling its BeginInvoke
method.
The generated BeginInvoke
method will accept the same argument types as declared in the delegate’s signature, followed by some other arguments that we will ignore for now.
这个产生的BeginInvoke方法会接受和已经声明的委托签名相同的参数类型。
The return value of BeginInvoke
is a reference to an interface named IAsyncResult
which we later pass to EndInvoke
to receive the return value of the asynchronous method.
返回的BeginInvoke的值是一个对IAsyncResult接口的引用。 晚些,我们将要把这个值传给EndInvoke去得到这个异步方法的返回值。
The generated EndInvoke
method is defined to return the same type as the asynchronous method. When called, EndInvoke
will block until the asynchronous method completes its execution. Therefore, to be efficient, we need to find other strategies for calling EndInvoke
that will not block the calling thread. Below, I show three techniques for calling EndInvoke
, polling for completion, using a wait handle, and using an AsyncCallback
delegate.
当调用EndInvoke的时候,EndInvoke将会阻塞直到异步方法执行完。因此,为了有效地执行,我们需要寻找另外的方法去调用EndInvoke,而不至于阻塞线程的调用。
First we look at polling. The next code segment uses a while
loop to poll the IsCompleted
property of the IAsyncResult
object. If the method call is not yet complete, then we can perform some other work and check during the next pass through the loop to see if the asynchronous operation has completed.
每循环一次检查下这个异步方法有没有完成。
// begin execution asynchronously IAsyncResult result = flusher.BeginInvoke("data.dat", null, null); // wait for it to complete while (result.IsCompleted == false) { // do some work Thread.Sleep(10); } // get the return value int returnValue = flusher.EndInvoke(result);
The next technique is to block on the IAsyncResult
object’s wait handle. When the asynchronous method is complete, the IAsyncResult
object’s AsyncWaitHandle
will be signaled. Then any thread waiting on that handle will be awakened. Once awake, the thread can call the IAsyncResult
object’s EndInvoke
method to receive the return value.
下面的技术是阻塞IAsyncResult对象,等待处理。当异步方法完成后,IAsyncResult对象的AsyncWaitHandle将被唤醒调用。然后等待处理的线程都被唤醒。
// call the delegate asynchronously IAsyncResult result = flusher.BeginInvoke("data.dat", null, null); // wait for the call to finish result.AsyncWaitHandle.WaitOne(); // get the return value int returnValue = flusher.EndInvoke(result);
The final method is to create an instance of the AsyncCallback
delegate and pass it to the BeginInvoke
method. The AsyncCallback
delegate is defined in the System
namespace. It accepts an IAsyncResult
reference as its only parameter and returns void.
最后一个方法就是创建一个AsyncCallback委托的实例,然后把它传送给BeginInvoke方法。
When you pass an AsyncCallback
to BeginInvoke
, the delegate will invoke that callback upon completion of the asynchronous operation. Then, you can use that opportunity to call EndInvoke
to retrieve the return value and perform any resource cleanup, as the next example shows.
static void Main(string[] args) { // create the object DataCache cache = new DataCache(); // create the delegate CacheFlusher flusher = new CacheFlusher(cache.FlushToDisk); // call the delegate asynchronously flusher.BeginInvoke("data.dat", new AsyncCallback(CallbackMethod), flusher); // wait to exit Console.WriteLine("Press enter to exit"); Console.ReadLine(); } static void CallbackMethod(IAsyncResult result) { // get the delegate that was used to call that // method CacheFlusher flusher = (CacheFlusher) result.AsyncState; // get the return value from that method call int returnValue = flusher.EndInvoke(result); Console.WriteLine("The result was " + returnValue); }
If you run this example you will see that the line “Press Enter to Exit”
will be displayed in the console window, followed by the output from CallbackMethod
. This is because the Main
method calls BeginInvoke
and continues processing, displaying its own output without waiting for the CacheFlusher
delegate to finish. Later, when the asynchronous operation is complete, the delegate calls the CallbackMethod
, which writes its output to the screen.
The Main
method begins by calling the delegate object’s BeginInvoke
method as before. But, this time those second and third arguments are used. The second argument is an instance of another delegate named AsynchCallback
. The AsynchCallback
delegate is defined to accept an IAsyncResult
reference and return void
, such as the static CallbackMethod
provided as part of the example. When the asynchronous operation is complete, the delegate invokes that callback. It is within the body of that CallbackMethod
, the method referenced by the AsynchCallback
, that we have the opportunity to call EndInvoke
.
Whatever object you pass to the third argument of BeginInvoke
will be stored in the AsyncState
property of the IAsyncResult
reference passed to the CallbackMethod
. Notice in the CallbackMethod
that the first thing we do is cast the AsyncState
property to the CacheFlusher delegate. This is so that we can call the object’s EndInvoke
method to receive the return value.
One of the most common uses of delegates in .NET is event handling. Events are useful for notifying objects of user interface events or state changes. The following example creates a Timer
object that will fire an event every second. The Timer
class is defined in the System.Timers
namespace.
class DelegateDemo { static void Main(string[] args) { Timer t = new Timer(1000); t.Elapsed += new ElapsedEventHandler(Timer_Elapsed); t.Enabled = true; Console.WriteLine("Press enter to exit"); Console.ReadLine(); } static void Timer_Elapsed(object sender, ElapsedEventArgs e) { Console.WriteLine("tick"); } }
The Timer
class contains the Elapsed
event and fires it whenever its interval expires. In this example the Main
method instantiates a Timer
and registers an ElapsedEventHandler
delegate with its Elapsed
event.
In this example, the method invoked by the ElapsedEventHandler
delegate is the Timer_Elapsed
method. Following the convention of all event handling delegates, the ElapsedEventHandler
delegate returns void
and accepts two parameters. The first is a reference to the object that signaled the event and the second is a argument derived of EventArgs
which stores pertinent information about the event.
The last use for delegates that we will cover is to provide a way for unmanaged Windows API functions to call back into your managed code. Some Windows API functions accept the address of an application-defined function as a parameter and then call that function to perform some application-specific processing. An example is the EnumChildWindows
API function which enumerates all children of a parent window and invokes the application-defined callback function on each. To pass a function address from a C# program, we use a delegate.
The next example uses the EnumChildWindows
API function to iterate through each of the top level windows on a system. Each window handle is passed to our application-defined callback method, WindowEnumProc
, which prints the windows text to the console.
using System; using System.Text; using System.Runtime.InteropServices; namespace enumchildren { class WindowEnumerator { // declare the delegate public delegate bool WindowEnumDelegate (IntPtr hwnd, int lParam); // declare the API function to enumerate child windows [DllImport("user32.dll")] public static extern int EnumChildWindows(IntPtr hwnd, WindowEnumDelegate del, int lParam); // declare the GetWindowText API function [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hwnd, StringBuilder bld, int size); static void Main(string[] args) { // instantiate the delegate WindowEnumDelegate del = new WindowEnumDelegate(WindowEnumProc); // call the win32 function EnumChildWindows(IntPtr.Zero, del, 0); Console.WriteLine("Press enter to exit"); Console.ReadLine(); } public static bool WindowEnumProc(IntPtr hwnd,int lParam) { // get the text from the window StringBuilder bld = new StringBuilder(256); GetWindowText(hwnd, bld, 256); string text = bld.ToString(); if(text.Length > 0) { Console.WriteLine(text); } return true; } } }
This example defines the WindowEnumerator
class which begins be defining a delegate named WindowEnumDelegate
. I have declared this delegate to have a compatible signature with the type of callback function that the EnumChildWindows
function is expecting. Next I import two Windows API functions from the user32.dll.
The Main
method only does three things. It first instantiates the WindowEnumDelegate
to call the WindowEnumProc
method defined in the listing. Then, it calls the EnumChildWindows
API function, passing the delegate as argument. For each window whose parent is the value NULL
from C++ (which is IntPtr.Zero
from C#), the system will invoke my delegate. Each time my WindowEnumProc
is called, it receives the window handle of the current window as its first argument. It then calls GetWindowText
to read the window’s title bar text into a buffer and display it to the console.
Delegates are used to create a reference to a method of another class. But, they are safer and far more versatile than C++ function pointers. Delegates are type-safe wrappers for methods. They are secure and verifiable. There are many applications for delegates. In this article, we demonstrated multicast delegates, threads, anonymous methods, events, and Win32 callbacks.