跟小D每日学口语

Delegates to the Event

Like many .NET platform developers who originally migrated from other tools and languages such as classic Visual Basic, much of what I've learned has been self-taught. And the interesting thing about being self-taught, of course, is that often our learning process can be very "selective". That is to say, we learn what we want to learn, not necessarily what we should learn, and very often we "learn it to ourselves" in the wrong order. Sound familiar? I thought it would!

 

One of the last things I learned was the delegate and event infrastructure that's built into the .NET languages. I avoided getting them under my belt for mostly the above reasons. However, now that I've journeyed through the Valley of Delegate Darkness and emerged at the other side unscathed, I have seen that "it is good". For reasons of clarity, I'm focusing here on C# specifically (also because it's my favorite .NET Platform language, by a long shot).

So, what's a delegate? You'll find a number of definitions, but they all seem to converge around "it's a type-safe, object oriented way to point to a method". In .NET, you can make a call on any method, either synchronously or asynchronously, by simply defining a delegate for the method and invoking the delegate.

First, let's take a look at a delegate "under the hood". Once you understand what the Common Language Runtime is doing with your delegates, you will have a lot more insight into what they can do. I've created a simple class, "DaddysCar" that looks like so:

using System;
namespace Daddy
{ 
 public class DaddysCar
 {
  public DaddysCar()
  {
  }
   private decimal _damageAmount;
  public decimal DamageAmount
  {
   get{ return _damageAmount;}
   set{_damageAmount=value;}
  }
  
  private string _whichCar;
  public string WhichCar
  {
   get{return _whichCar;}
   set{_whichCar=value;}
  }
  public delegate string NotifyDaddy(DaddysCar car, decimal DamageAmount);
 }
}

Note the delegate "NotifyDaddy" defined at the bottom of the class above (I hope you are never the recipient of this method call). Now when we run this through our trusty ILDASM and look inside, see what the Compiler has done for us:

First, we can see that NotifyDaddy, our delegate, is actually a class, and that the compiler has created it as a nested class, since we defined it inside our DaddyCar class (we could have defined it in a separate class too). Note also that the compiler has generously supplied us with both synchronous "Invoke" and asynchronous "BeginInvoke / EndInvoke" methods! Note also that the compiler has derived this class from System.MulticastDelegate. Some of the inherited members we get from this trick are Method (name of the method it points to), Target (name of the class if the method it points to is a member of a class), Combine (a static method to allow our delegate to point to more than one method), and GetInvocationList, which returns an array of each of the Delegate types in the list of methods pointed to. We also get, of course, Clone, the += and -= operator overloads, DynamicInvoke, MethodInfo, and other methods.

So here we've defined a Delegate called NotifyDaddy, and we have indicated that each instance of it can hold details of a method that takes a DaddysCar instance and a decimal damageAmount, and will return a string. I've now added a new method to my DaddysCar class that can act as a sample target of a delegate:

public string ComputePunishment( DaddysCar car, decimal damageAmount)
 {
  if((int)damageAmount >1000.00 && car.WhichCar =="TBird")
   {
     return "Confiscate Car Keys Immediately; Call Insurance Company!";
   }
    else
   {
    return "Look Mad and Issue Stern Warning";
  }

Invoking Delegates

We invoke a delegate by supplying the name of a delegate instance, followed by parentheses containing a list of acceptable parameters. This has the same effect as calling the method pointed to by the delegate. So now let's put together a "Tester" class to flesh this all out:

using System;
using Daddy;
namespace DaddyTester
{
 class TestMeOutDelegate
 {
  delegate string NotifyDaddy(DaddysCar car, decimal damageAmount);
  [STAThread]
  static void Main(string[] args)
  {
   Daddy.DaddysCar d=new DaddysCar();
   d.WhichCar="TBird";
   NotifyDaddy BadNews = new NotifyDaddy(d.ComputePunishment);
   Console.WriteLine("Method: " + BadNews.Method);
   Console.WriteLine("Target: " + BadNews.Target);
   Console.WriteLine(BadNews(d,1500));
   Console.ReadLine();  
  }
 }
}

And here is the output from our delegate call:

The above comprises the very basic, "get to First Base" understanding of delegates. Hopefully it will encourage you to move forward on your own and study more on the use of delegates. Delegates can be used in ASP.NET, with SMTP, Web Services, even in FTP and on applications written for hand held devices such as the PocketPC.

Finally, delegates can be used to call methods asynchronously. For a complete example of how to implement this technique, please visit my previous articles "How to send emails asynchronously".

Now that we have a "basics" example of what a delegate is and how they are used, we can move on to be a "Delegate to the Event!", which covers events, in my next installment here.

 

 

 

An event, as one might guess, is a way that C# is used to signal that something has happened. The reason that events go hand in hand with delegates is because C# uses a delegate model for events. In the .NET Framework, the event model is composed of three parts:

  • The type that raises the event
  • A method that is called when the event is raised ("event handler"), and
  • A standardized way to connect the first two items.

In C#, the event handler need not be defined in the class that will generate the event. This provides us with both generality in the event model, and flexibility as well. In fact, event handlers are normally found in a different class than the one raising the event (For an example using MDI Windows Forms, see my recent article "MDI Inter-Form Communication with Events"). The "glue" that connects the event that is raised to the method that handles it is, of course, the delegate.

Delegates provide us with a generic reference to a method, matching it's specified return type and arguments. The delegate does not care what the method may do. A delegate is declared by using the C# keyword "delegate" and providing a method signature matching those methods to which the delegate can hold a reference:

public delegate void EventHandler ( object sender, EventArgs e);

The above example defines a delegate that can hold references to methods that do not have any return value, and that take two parameters: an object reference to the sender, and an instance of type EventArgs. If we wanted to create a method that would be an acceptable match to the above delegate, it might look like this:

public void Text_Changed(object sender, EventArgs e)
{
// your code here . . .
}

Our example above is also a sample of the predefined delegate, "EventHandler" in C#, which is designed to simplify commonly used programming needs. The object parameter means that any .NET type can be supplied, and the EventArgs parameter is an instance of the predefined EventArgs class, which is used whenever an event does not carry any event data. So, unless you have an event that will be providing specific event data, you would use this predefined delegate for your events. OK -- if we aren't sending in any event data, why do we need the second parameter? Well, the EventHandler delegate is used as a template for defining other delegates, and the EventArgs class is the base class for all custom event arguments classes. So, even though this "stock" event delegate doesn't accept any event data, we still need a "point of departure" for the OOP model to work effectively.

Define Your Event

In order to connect event handling methods to an event, we need to be able to define an event - so that interested objects can "subscribe" to be notified.:

public event EventHandler TextChanged;

The above defines an event named "TextChanged", using a delegate of type EventHandler. So now we have defined an event and the type of delegate which it uses, and all we need to do at this point is to raise the event. We do this by calling the event as if it were a method, passing in the arguments that match the delegate signature (object sender, EventArgs e):

Convention dictates that the raising of an event is done in a method named OnEventName. So here is an example of a method that would raise our TextChanged event:

protected void OnTextChanged()
  {
    if(TextChanged !=null)
      TextChanged(this, new EventArgs() );
 }

We need to first check that any observing methods have been attached to the delegate, hence the check to see if TextChanged is not null. Finally, we raise the event by calling it as we would a method, passing in "this" - a reference to the control or class to identify it as the sender of the event, and a new instance of the stock EventArgs class as the second parameter. At this point, our event, which has been defined as a delegate instance, calls all the methods that have been subscribed, with the arguments we supplied.

Eat Those Events

Now that we have our event and its delegate, we need to ensure that any object wanting to respond to events from the class adds an event handler for our event. This is done via the following syntax:

object.EventName+=new DelegateType(EventHandlerMethod);

We see this signature created by the Visual Studio.NET IDE and ASP.NET Designer all the time. For example,in any WebForm:

this.Load += new System.EventHandler(this.Page_Load);

For the TextChanged example, we could use the following code to add an event handler for the TextChanged event we've defined:

CustomTextBox1.TextChanged+=new EventHandler(CustomTextBox1_OnTextChanged);

public void CustomTextBox1_OnTextChanged(object sender, EventArgs e)
{
// your cool code here
}

Note that the method we have assigned to be the handler, "CustomTextBox1_OnTextChanged" has the same signature as the EventHandler delegate.

Convenntions

While I haven't been a 100% purist insofar as following accepted conventions, I think its important to do so, as it not only makes our code more readable to others; it also enables us to scan through our own code and more easily remember what it is doing after coming back to visit it after a period of time. The following table illustrates the accepted nomenclature for event - related patterns:

Event Context Convention Example
Delegate EventNameEventHandler TextChangedEventHandler
Event EventName TextChanged
Event raising method OnEventName OnTextChanged
Event Handler ObjectName_EventName CustomTextBox1_TextChanged

We could get a lot deeper into the event - handling process in C#.NET, but that's really a job for a chapter or even two or three chapters of a good book. My objective here in this two-part series has been to lay the groundwork for the very basics, and hopefully to make it that much easier for readers who need this information to be able to grasp it in an easily-digestible, simplified way.

 

posted @ 2010-07-28 22:31  简简单单幸福  阅读(362)  评论(0编辑  收藏  举报