November 23, 2005 spout

A C# 2.0 anon delegate gotcha”

I’m a huge fan of anonymous delegates, but I ran into a gotcha this morning when using anon delegates inside a for-loop:

class Worker {

  public event WorkCompleted Completed;

 

  public void DoWork() {

    Console.WriteLine(Worker: work completed”);

    if( this.Completed != null ) {

      foreach( WorkCompleted wc in this.Completed.GetInvocationList() )

        wc.BeginInvoke(delegate(IAsyncResult result) {

          // Use wc from surrounding context

          int grade = wc.EndInvoke(result);

          Console.WriteLine(Worker grade= {0}”, grade);

        },

        null);

      }

    }

  }

}

When I run this code, I get the follow exception:

System.InvalidOperationException:
  The IAsyncResult object provided does not match
  this delegate.

What’s happening, of course, is that the wc variable continues to change after the dynamic invocation happens instead of the value being stored away, one for each call to BeginInvoke, as I’d intended. One fix for this problem is to pass the value I wish I could take off the stack as the async state argument:

class Worker {

  public event WorkCompleted Completed;

 

  public void DoWork() {

    Console.WriteLine(Worker: work completed”);

    if( this.Completed != null ) {

      foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {

        wc.BeginInvoke(delegate(IAsyncResult result) {

          // Pull the value out of async state

          WorkCompleted wc2 = (WorkCompleted)result.AsyncState;

          int grade = wc2.EndInvoke(result);

          Console.WriteLine(Worker grade= {0}”, grade);

        },

        wc);

      }

    }

  }

}

This isn’t quite the elegant code I’d like to write with anon delegates, however. I can do a little better by creating a variable on the stack specifically for use in the delegate:

class Worker {

  public event WorkCompleted Completed;

 

  public void DoWork() {

    Console.WriteLine(Worker: work completed”);

    if( this.Completed != null ) {

      foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {

        WorkCompleted wc2 = wc; // Copy wc for use in delegate

        wc.BeginInvoke(delegate(IAsyncResult result) {

          int grade = wc2.EndInvoke(result);

          Console.WriteLine(Worker grade= {0}”, grade);

        },

        null);

      }

    }

  }

}

In this case, each time through the loop, a new variable is created on the stack specifically for that invocation of the loop and is therefore unchanged by the time the anon delegate is executed. This still isn’t quite ideal, but it’s not too bad.