Working with Delegates Made Easier with C# 2.0

by Jeffrey Richter of Wintellect

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

Syntactical Shortcut #3: No Need to Specify Callback Method Parameters

A common way to use the previous syntactical shortcut is when you want to have some code execute when a button is clicked:

button1.Click += delegate(Object sender, EventArgs e)
                 { MessageBox.Show("The Button was clicked!"); };

It's nice to be able to specify the callback code right inline without having to manually go and define another method. But, in this example, the callback code doesn't refer to the callback method's arguments, sender and e, at all. If your callback code doesn't care about the arguments, C# allows the code above to be shortened to this:

button1.Click += delegate 
                 { MessageBox.Show("The Button was clicked!"); };

Notice that I just deleted the "(Object sender, EventArgs e)" part from the original code. When the compiler emits the anonymous method, it still emits a method whose prototype matches the delegate exactly—the CLR absolutely requires this for type safety. In this case, the compiler would still emit an anonymous method that matches an EventHandler delegate (the delegate type expected by Button's Click event). It's just that the arguments won't be referenced by the anonymous method's code.

If the callback code references any of the parameters, then after the delegate keyword, you must include parentheses, the parameter types, and variable names. The return type is still inferred from the delegate's type and, if the return type is not void, you must have a return statement inside the callback code.

Syntactical Shortcut #4: No Need to Manually Wrap Local Variables in a Class to Pass Them to a Callback Method

I've already shown how the callback code can reference other members defined in the class. However, sometimes you might like the callback code to reference local parameters or variables that exist in the defining method. Here's an interesting example:

class AClass {
   static void UsingLocalVariablesInTheCallbackCode(Int32 numToDo) {

      // Some local variables
      Int32[] squares = new Int32[numToDo];
      AutoResetEvent done = new AutoResetEvent(false);

      // Do a bunch of tasks on other threads
      for (Int32 n = 0; n < squares.Length; n++) {
         ThreadPool.QueueUserWorkItem(
            delegate(Object obj) {
               Int32 num = (Int32) obj; 

               // This task would normally more time consuming
               squares[num] = num * num;

               // If last task, let main thread continue running
               if (Interlocked.Decrement(ref numToDo) == 0) 
                  done.Set(); 
            }, n);
      }

      // Wait for all the other threads to finish
      done.WaitOne();

      // Show the results
      for (Int32 n = 0; n < squares.Length; n++) 
         Console.WriteLine("Index {0}, Square={1}", n, squares[n]);
   }
}

This example really shows off how easy C# 2.0 makes implementing what used to be a pretty complex task. The method above defines one parameter, numToDo, and two local variables, squares, and done. And, the delegate callback code refers to these variables.

Now, imagine that the callback code is placed in a separate method (as would have to be the case before C# 2.0). How would the values of the variables be passed to the callback method? The only way to do this is to define a new helper class that also defines a field for each value you want passed to the callback code. In addition, the callback code would have to be defined as an instance method in this helper class. Then, the UsingLocalVariablesInTheCallbackCode method would have to construct an instance of the helper class, initialize the fields from the values in its local variables, and then construct the delegate object bound to the helper object/instance method.

This is a lot of very tedious and error-prone work and, of course, C# 2.0 does all this for you automatically. When you write the code shown above, it's as if the C# compiler re-writes your code so that it looks like this:

class AClass {
   static void UsingLocalVariablesInTheCallbackCode(Int32 numToDo) {

      // Some local variables
      Int32[] squares = new Int32[numToDo];
      AutoResetEvent done = new AutoResetEvent(false);

      // Do a bunch of tasks on other threads
      for (Int32 n = 0; n < squares.Length; n++) {
         // Construct an instance of the helper class
         __LocalsDisplayClass s$1 = new __LocalDisplayClass();

         // Initialize fields from the local variable values
         s$1.squares = squares;
         s$1.numToDo = numToDo;
         s$1.done = done;

         // New up delegate object bound to the helper object and
         // its anonymous instance method
         ThreadPool.QueueUserWorkItem(
            new WaitCallback(s$1.__anonymousMethod$00000004), n);
      }

      // Wait for all the other threads to finish
      done.WaitOne();

      // Show the results
      for (Int32 n = 0; n < squares.Length; n++) 
         Console.WriteLine("Index {0}, Square={1}", n, squares[n]);
   }

   // The helper class is given a strange name to avoid potential
   // conflicts and is private to forbid access from outside AClass
   private sealed class __LocalsDisplayClass$00000006 : Object {

      // One public field per local variable used in the callback
      // code
      public Int32[] squares;
      public Int32 numToDo;
      public AutoResetEvent done;

      // public parameterless constructor
      public __LocalsDisplayClass$00000006 { }

      // Public instance method containing the callback code
      public void __anonymousMethod$00000004(Object obj) {
         Int32 num = (Int32) obj;
         squares[num] = num * num;
         if (Interlocked.Decrement(ref numToDo) == 0)
            done.Set();
      }
   }
}

Conclusion

C# 2.0 boasts many new features that are extremely useful and greatly increase programmer productivity. These new delegate syntax conveniences take a lot of the tedium and drudgery out of working with delegates. Little conveniences such as these add up and continue to keep programming fun and enjoyable.

About the Author

Jeffrey Richter is a co-founder of Wintellect (www.Wintellect.com); a training, debugging, and consulting firm dedicated to helping companies build better software, faster. He is the author of several best-selling .NET and Win32 programming books, including Applied Microsoft .NET Framework Programming (Microsoft Press). Jeffrey is also a contributing editor to MSDN Magazine, where he authors the .NET column. Jeff has been consulting with Microsoft's .NET Framework team since October 1999 and has also been consulting on Microsoft's XML Web Services and Messaging Team ("Indigo") since January 2003.

# # #



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • "Security" is the number one issue holding business leaders back from the cloud. But does the reality match the perception? Keeping data close to home, on premises, makes business and IT leaders feel inherently more secure. But the truth is, cloud solutions can offer companies real, tangible security advantages. Before you assume that on-site is the only way to keep data safe, it's worth taking a comprehensive approach to evaluating risks. Doing so can lead to big benefits.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds