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

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.

 

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.

 

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.

 

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.

 

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.

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.

 

Anonymous methods

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.

Asynchronous method calls

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.

 

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");   
      }
   }

 

 

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;
	 }
      }
   }

 

 

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.

转载于:https://www.cnblogs.com/herbert/archive/2010/03/23/1693309.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值