September 16, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Working with Delegates Made Easier with C# 2.0

  • November 3, 2003
  • By Jeffrey Richter
  • Send Email »
  • More Articles »

Every .NET Framework developer must become quite familiar with delegates. Delegates are a technology that provides a type-safe method callback mechanism and is used heavily by many parts of the .NET Framework including Windows Forms, ASP.NET Web Forms, threading, and so on. Unfortunately, delegates are difficult for many developers to understand and use because delegates require special coordination between your programming language compiler and the CLR. Chapter 17 of my Applied Microsoft .NET Framework Programming book (Microsoft Press, 2002) goes into great depth about all the coordination that goes on allowing delegates to work in the .NET Framework.

Once you understand all of the coordination that goes into making delegates work, most programmers still find working with delegates to be cumbersome. The reason for this is that the syntax is so strange. For example, take this classic line of code:

button1.Click += new EventHandler(button1_Click);

where button1_Click is a method that looks something like this:

void button1_Click(Object sender, EventArgs e) {
   // Do something, the button was clicked...
}

The idea behind this line of code is to register the address of the button1_Click method with a button control so that, when the button is clicked, the method is called. To most programmers, it feels quite unnatural to construct an EventHandler object just to get the address of the button1_Click method. However, constructing the EventHandler object is required for the CLR because this object provides a wrapper that ensures that the method can only be called in a type-safe fashion. Unfortunately, this is a detail that most programmers don't care about. Programmers would prefer to write the code above as follows:

button1.Click += button1_Click;

Fortunately, one of the big, new features in the Microsoft's C# 2.0 compiler is its improved delegate syntax. To be clear, the compiler still offers the original delegate syntax so your existing code will continue to compile without forcing you to make any changes to your source code. However, C# 2.0 now offers a number of syntactical shortcuts which you can optionally avail yourself of. I'll explain all these shortcuts in this article. One last point before we begin: What I'm about to describe really boils down to C# syntactical sugar; the CLR has not changed how it supports or implements delegates in any way. In addition, what I'm about to describe is C# specific; none of Microsoft's other compilers (including Visual Basic .NET) have implemented any additional delegate syntax shortcuts that I'm aware of for their next release.

Syntactical Shortcut #1: No Need to Construct a Delegate Object

As demonstrated already, C# 2.0 allows you to specify the name of a callback method without having to construct a delegate object wrapper. Here is another example:

class AClass {
  static void CallbackWithoutNewingADelegateObject() {
         ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);
  }

  static void SomeAsyncTask(Object o) {
         Console.WriteLine(o);
  }
}

Here, the ThreadPool class's static QueueUserWorkItem method expects a reference to a WaitCallback delegate object that contains a reference to my SomeAsyncTask method. Because the C# 2.0 compiler is capable of inferring this on its own, it allows me to omit code that constructs the WaitCallback delegate object, making the code much more readable and understandable. Of course, when the code is compiled, the C# compiler does produce IL that does, in fact, new up the WaitCallback delegate object—we just got a syntactical shortcut.

Syntactical Shortcut #2: No Need to Define a Callback Method

In the code above, the name of the callback method, SomeAsyncTask, is passed to the ThreadPool's QueueUserWorkItem method. C# 2.0 allows you to write the code for the callback method in-line so that it doesn't have to be written inside its very own method. For example, the code above could be rewritten as follows:

class AClass {
  static void CallbackWithoutNewingADelegateObject() {
         ThreadPool.QueueUserWorkItem(
         delegate(Object obj) { Console.WriteLine(obj); },
         5);
  }
}

Notice that the first "parameter" to the QueueUserWorkItem method is a block of code! When the C# compiler sees the delegate keyword used wherever a reference to a delegate object is expected, the compiler automatically defines a new method in the class (AClass in this example). This method will be named something like __AnonymousMethod$00000002, which you can verify by running ILDasm.exe over the assembly produced by the compiler. The code in the "parameter" is then placed in this compiler-defined method. (There is no limit to the number of statements or kinds of statements you may have in the callback code.) In fact, it's as if the C# compiler re-wrote your code so that it looked like this:

class AClass {
  static void CallbackWithoutNewingADelegateObject() {
         ThreadPool.QueueUserWorkItem(
         new WaitCallback(__AnonymousMethod$00000002),
         5);
  }

  private static void __AnonymousMethod$00000002(Object obj) {
          Console.WriteLine(obj);
  }
}

The prototype of the anonymous method must match that of the WaitCallback delegate: It returns void and takes an Object parameter. However, I specified the name of the parameter by placing "(Object obj)" after the delegate keyword in my code.

It is also worth noting that the anonymous method is private; this forbids any code not defined within the type from accessing the method (although reflection will reveal that the method does exist). Also, note that the anonymous method is static; this is because the method that defined it, CallbackWithoutNewingADelegateObject, is also static. This means that it is possible to reference any static fields or static methods defined within the class. Here is an example:

class AClass {
  static String sm_name;    // A static field

  static void CallbackWithoutNewingADelegateObject() {
      ThreadPool.QueueUserWorkItem(
         // The callback code can reference static members.
         delegate(Object obj) { Console.WriteLine(sm_name+ ": " +
                                                  obj); }, 5);
  }
}

If the CallbackWithoutNewingADelegateObject method had not been static, the compiler would produce a non-static anonymous method and, of course, this method's code would be able to access any non-static fields and non-static methods:

class AClass {
   String m_name;    // An instance field

   // An instance method
   void CallbackWithoutNewingADelegateObject() {
      ThreadPool.QueueUserWorkItem(
         // The callback code can reference instance members.
         delegate(Object obj) { Console.WriteLine(m_name+ ": " +
                                                  obj); },
         5);
   }
}




Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel