On CBS MoneyWatch: Dumbest Things You Do With Money
BNET Business Network:
BNET
TechRepublic
ZDNet

May 9th, 2007

Cool use of anonymous delegates

Posted by John Carroll @ 11:18 am

Categories: General, Programming

Tags: Method, Web, Delegate, Web Service, John Carroll

I try to keep up to date with the latest changes to the programming tools I use on a regular basis. This means that I have a tendency to use less generally well-known language features. I use them, however, if I feel I have good reason to use them. Until recently, I didn't have a good reason to use anonymous methods.

Normally, when you hook an event generated by an object in .NET, you register a delegate with an event that points to a named method (static or member). The C# syntax for that would be:

obj.SomeEvent += new EventHandler(this.MyHandler);

Since the EventHandler delegate is defined as something that takes an object and an EventArgs argument, as follows:

public delegate void EventHandler(object obj, EventArgs args);

…the method used with the delegate would look like this:

private void MyHandler(
   object obj, EventArgs args)
{
   // Do something
}

I never found this syntax to be particularly onerous, and so I never had much reason to do anything but use named methods with delegates. Besides, most people understand named methods, and anonymous delegates are a rather exotic C# version 2.0 addition to the language the existence of which many are unaware. Better to use what is known, as others will end up maintaining my code someday.

Last week, however, I ran across a situation that resisted conventional solutions. I have a program that makes a number of web service calls. I wanted each web service call to repeat a configurable number of times in error cases, retrying after waiting a configurable interval, and logging each error (if any) encountered during each pass.

This was a refactoring project, where existing code that had existed for several years was being adapted for use in a library that could be used more widely than the old, standalone Windows Service in which it previously resided. In that code, some of the web service calls did what they were supposed to do, looping as indicated in the previous paragraph. Others did so incompletely (skipped the wait interval, or used a hard-coded retry count) and others skipped the retry step completely.

Worse, some of the logging steps misidentified themselves to the log, mostly because the boilerplate code (which was clearly copied from elsewhere in the program) had been incompletely updated to account for its new location.

Clearly, this logic needed to be turned into something that could be shared throughout the program. But how?

The problem was that, though the retry loop, wait interval and logging steps are the same across web service calls, the arguments passed to the web service call and its return type are not. That makes things tricky. The service method can have zero or 100 arguments. The method can return nothing, or a value that is required for additional processing later in the calling method.

Generics (a bit like C++ templates, and also called parameterized types) aren't any help, as even generics need to know the calling details of a particular method. I contemplated a byzantine reflection-based framework that would serve the same purpose, but I hate methods with untyped object arrays as arguments, and besides, it seemed like an awful lot of work just to be able to reuse boilerplate web service calling logic.

This is where anonymous methods proved useful. Anonymous methods are odd beasts, as they have access to the local variables present in the method in which they are declared. This is why the following code, when compiled, causes 100 to be printed at the end.

class Program
{
   public static void RunDelegate(
      int count,
      EventHandler handler)
   {
      for (int i = 0; i < count; i++)
      {
         handler(null, null);
      }
   }

   static void Main(string[] args)
   {
      int counter = 0;
      
      Program.RunDelegate(
         100,
         delegate(object obj, EventArgs a)
         {
            counter++;
         });

      Console.Out.WriteLine(
         "Counter: " + counter);
   }
}

This peculiarity of anonymous methods enabled me to solve my boilerplate web service method problem using common code that could be shared throughout my project. The net result was a solution that ensured that retry logic was performed in exactly the same way for every web service call in the system. More generally, the lesson learned gave me one more tool to use in my programming toolkit.

A brief explanation of the code: The repeater code is located inside the WebServiceCallRepeater class. This is a simplified version of code I'm using in my project. WebServiceCall is an example of the use of the WebServiceCallRepeater class, and can be used in place of any direct call to the Web Service method.

public class WebServiceCallRepeater
{
   public delegate void OperationToRepeat(
      WebServiceCallRepeater repeater);

   private ICallContext _context;

   public WebServiceCallRepeater(
      ICallContext context)
   {
      if (context == null)
      {
         throw new ArgumentNullException(
            "context");
      }

      this._context = context;
   }
   
   public ICallContext Context
   {
      get
      {
         return this._context;
      }
   }
   
   public static void RepeatOperation(
      ICallContext context,
      string operationName,
      OperationToRepeat operationToRepeat)
   {
      WebServiceCallRepeater repeater
         = new WebServiceCallRepeater(context);
      repeater.RepeatOperation(
         operationName,
         operationToRepeat);
   }
   
   public void RepeatOperation(
      string operationName,
      OperationToRepeat operationToRepeat)
   {
      int retries = 1;
      
      DateTime begin = DateTime.Now;
      while (true)
      {
         try
         {
            this._context.Log.LogInfo(
               String.Format(
                  "Calling {0}, Attempt #: {1}",
                  operationName,
                  retries));
            
            operationToRepeat(this);
            break;
         }
         catch (Exception e)
         {
            this._context.Log.LogError(
               String.Format(
                  "Call to {0} FAILED! Time elapsed: {1}.",
                  operationName,
                  DateTime.Now - begin),
               e);
            
            retries++;
            if (retries <= this._context.RetryCount)
            {
               this._context.Log.LogInfo(
                  String.Format(
                     "Retrying {0}, attempt #{1}",
                     operationName,
                     retries));
               System.Threading.Thread.Sleep(
                  this._context.RetryInterval);
            }
            else
            {
               this._context.Log.LogError(
                  String.Format(
                     "Retry count exceeded on " +
                     "web service call {0}, limit: {1}",
                     operationName,
                     this._context.RetryCount),
                  e);
               throw;
            }
         }
      }
      
      this._context.Log.LogInfo(
         String.Format(
            "Call to {0} Succeeded! Time elapsed: {1}.",
            operationName,
            DateTime.Now - begin));
   }
}

public static class WebServiceCall
{
   public static string CallServiceMethod(
      ICallContext context,
      string arg1,
      double arg2)
   {
      Proxies.MyProxy proxy = new Proxies.MyProxy();
      
      WebServiceCallRepeater repeater =
         new WebServiceCallRepeater(
            context);
      string retVal = null;
      repeater.RepeatOperation(
         "MyProxy.MyServiceCall",
         delegate(WebServiceCallRepeater arg)
         {
            retVal = proxy.MyServiceCall("arg1", 2);
         });
      
      return retVal;
   }
}

John CarrollJohn Carroll has delivered his opinion on ZDNet since the last millennium. Since May 2008, he is no longer a Microsoft employee. He is currently working at a unified messaging-related startup. See his full profile and disclosure of his industry affiliations.

Email John Carroll

Subscribe to A Developer's View via Email alerts or RSS.

  • Talkback
  • Most Recent of 22 Talkback(s)
Hmmm
I can't think of any particular book, though I've never gone wrong with O'Reilly. They try to create short, content-rich books. I seem to remember a previous book by Jesse Liberty which was quite go... (Read the rest)
Posted by: John Carroll Posted on: 05/12/07 You are currently: a Guest | | Terms of Use
A wheel suitable to its purpose.  Anton Philidor | 05/09/07
Copy and paste works  John CarrollZDNet Moderator | 05/09/07
Does anything you've written apply better to code...  Anton Philidor | 05/09/07
I think  John CarrollZDNet Moderator | 05/09/07
There you go again with 'Jive Tallk'  D. T. Schmitz | 05/09/07
Hey is this...  D. T. Schmitz | 05/09/07
Yes...  John CarrollZDNet Moderator | 05/09/07
More like  dragosani | 05/09/07
Oh Sure  D. T. Schmitz | 05/09/07
You can do it in Ruby, too  John CarrollZDNet Moderator | 05/09/07
Oh and IronRuby...  D. T. Schmitz | 05/09/07
You responded to the wrong blog  NonZealot | 05/09/07
It is worth noting that...  D. T. Schmitz | 05/09/07
You know better than that John C!  John Le'Brecage | 05/09/07
I must be dreaming.  John Le'Brecage | 05/09/07
Re:  John CarrollZDNet Moderator | 05/10/07
If I hadn't been so sleepy...  John Le'Brecage | 05/10/07
is it really central to your design?  pphant | 05/11/07
That was what I contemplated originally  John CarrollZDNet Moderator | 05/11/07
Ack  John CarrollZDNet Moderator | 05/11/07
re: Ack  pphant | 05/11/07
Hmmm  John CarrollZDNet Moderator | 05/12/07

What do you think?

SponsoredWhite Papers, Webcasts, and Downloads

advertisement

Recent Entries

Top Rated

Archives

ZDNet Blogs

White Papers, Webcasts, and Downloads

Enterprise Applications

  • Check out some of the easiest and most powerful ways to boost productivity while saving money on your application infrastructure. See ZDNet's comprehensive Enterprise Application resource center, now!
  • New Online Dashboard
  • Read about top issues IT decision-makers face every day, plus get cost effective solutions to real life IT problems. Oracle Topline