CodeGuru
Earthweb Search
Forums Wireless Jars Gamelan Developer.com
CodeGuru Navigation
Member Sign In
User ID:
Password:
Remember Me:
Forgot Password?
Not a member?
Click here for more information and to register.

Become a Marketplace Partner

jobs.internet.com

internet.commerce
Partners & Affiliates
















RSS Feeds

RSSAll

RSSVC++/C++

RSS.NET/C#

RSSVB

See more EarthWeb Network feeds

Home >> .NET / C# >> C# >> Delegates


Working with Delegates Made Easier with C# 2.0
Rating:

Jeffrey Richter (view profile)
November 3, 2003

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.


(continued)



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(__AnonymousMethodCODE_REPLACEMENT 5000002),
         5);
  }

  private static void __AnonymousMethodCODE_REPLACEMENT 5000002(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 = new __LocalDisplayClass();

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

         // New up delegate object bound to the helper object and
         // its anonymous instance method
         ThreadPool.QueueUserWorkItem(
            new WaitCallback(s.__anonymousMethodCODE_REPLACEMENT 11000004), 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 __LocalsDisplayClassCODE_REPLACEMENT 11000006 : 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 __LocalsDisplayClassCODE_REPLACEMENT 11000006 { }

      // Public instance method containing the callback code
      public void __anonymousMethodCODE_REPLACEMENT 11000004(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.

# # #

Tools:
Add www.codeguru.com to your favorites
Add www.codeguru.com to your browser search box
IE 7 | Firefox 2.0 | Firefox 1.5.x
Receive news via our XML/RSS feed







RATE THIS ARTICLE:   Excellent  Very Good  Average  Below Average  Poor  

(You must be signed in to rank an article. Not a member? Click here to register)

Latest Comments:
No Comments Posted.
Add a Comment:
Title:
Comment:
Pre-Formatted: Check this if you want the text to display with the formatting as typed (good for source code)



(You must be signed in to comment on an article. Not a member? Click here to register)


JupiterOnlineMedia

internet.comearthweb.comDevx.commediabistro.comGraphics.com

Search:

Jupitermedia Corporation has two divisions: Jupiterimages and JupiterOnlineMedia

Jupitermedia Corporate Info


Legal Notices, Licensing, Reprints, & Permissions, Privacy Policy.

Advertise | Newsletters | Tech Jobs | Shopping | E-mail Offers

Solutions
Whitepapers and eBooks
IBM Whitepaper: Innovative Collaboration to Advance Your Business
Internet.com eBook: Real Life Rails
Avaya Article: Call Control XML - Powerful, Standards-Based Call Control
Tripwire Whitepaper: Seven Practical Steps to Mitigate Virtualization Security Risks
Internet.com eBook: The Pros and Cons of Outsourcing
Go Parallel Article: Scalable Parallelism with Intel(R) Threading Building Blocks
Internet.com eBook: Best Practices for Developing a Web Site
IBM CXO Whitepaper: The 2008 Global CEO Study "The Enterprise of the Future"
Avaya Article: Call Control XML in Action - A CCXML Auto Attendant
Go Parallel Article: James Reinders on the Intel Parallel Studio Beta Program
IBM CXO Whitepaper: Unlocking the DNA of the Adaptable Workforce--The Global Human Capital Study 2008
Adobe Acrobat Connect Pro: Web Conferencing and eLearning Whitepapers
Go Parallel Article: Getting Started with TBB on Windows
HP eBook: Storage Networking , Part 1
MORE WHITEPAPERS, EBOOKS, AND ARTICLES
Webcasts
Go Parallel Video: Intel(R) Threading Building Blocks: A New Method for Threading in C++
HP Video: Is Your Data Center Ready for a Real World Disaster?
Microsoft Partner Portal Video: Microsoft Gold Certified Partners Build Successful Practices
HP On Demand Webcast: Virtualization in Action
Go Parallel Video: Performance and Threading Tools for Game Developers
Rackspace Hosting Center: Customer Videos
Intel vPro Developer Virtual Bootcamp
HP Disaster-Proof Solutions eSeminar
HP On Demand Webcast: Discover the Benefits of Virtualization
MORE WEBCASTS, PODCASTS, AND VIDEOS
Downloads and eKits
Microsoft Download: Silverlight 2 Software Development Kit Beta 2
30-Day Trial: SPAMfighter Exchange Module
Red Gate Download: SQL Toolbelt
Iron Speed Designer Application Generator
Microsoft Download: Silverlight 2 Beta 2 Runtime
MORE DOWNLOADS, EKITS, AND FREE TRIALS
Tutorials and Demos
IBM IT Innovation Article: Green Servers Provide a Competitive Advantage
Microsoft Article: Expression Web 2 for PHP Developers--Simplify Your PHP Applications
Featured Algorithm: Intel Threading Building Blocks - parallel_reduce
MORE TUTORIALS, DEMOS AND STEP-BY-STEP GUIDES