您的位置:首页 > 编程语言 > C#

【学习笔记】C# Delegates and Events - 讲的比较系统

2010-03-23 23:01 585 查看
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.

C# Delegates and Events

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函数调用

Declaring and using delegates

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.

class DelegateDemo1 {

static void Main(string[] args) {
GreetingDelegate gd = new GreetingDelegate(SendGreeting);
gd("Hello");
}

static void SendGreeting(string s) {
Console.WriteLine(s);
}
}

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.

Multicast delegates

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
.



In this example, we have added a second method to the [code]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.

Thread delegates

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.

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.

 args) {
Thread t = new Thread(delegate() {
// do some thread work
CalculatePrimes()]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方法。

// 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.

每循环一次检查下这个异步方法有没有完成。

// 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.

 args) {
// create the object
DataCache cache = new DataCache()]

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 [code]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.

Events

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");
}
}
[/code]

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.

Win32 callbacks

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;
}
}
}
[/code]

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.

Conclusion

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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: