November 1, 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 »

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.

# # #





Page 2 of 2



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel