Blog Archive

Tuesday, November 14

Single Raised EventHandler

Earlier today, a friend described a scenario at his work where he needed to hook up an EventHandler that was only raised once.

A naïve solution could be to merely check whether the critical code had been executed within the handler; then set the state appropriately upon the first execution. Something like:

public class TestClass
{
   public event EventHandler SimpleEvent;

   public void RaiseAll()
   {
      if (SimpleEvent != null)
         SimpleEvent(null, EventArgs.Empty);
   }
}

// elsewhere:

TestClass test = new TestClass();

bool raised = false;
test.SimpleEvent += delegate
{
   if (!raised)
   {
      // critical code:
      raised = true;
      Console.WriteLine("Hello world!");
   }
};

test.RaiseAll();
test.RaiseAll();
test.RaiseAll();

// output:
// Hello world!

Okay, that's fine for an event on a simple, transient instance of an object. But what if, in the lifecycle of our application, we could potentially throw away hundered of EventHandlers? And what if, further complicating the problem, the event is static? It sure would be nice if we could actually remove the EventHandler, once it has been raised.

Well, we can. There are two ways to achieve this: one is simple but requires duplication for reuse; the other is complicated but easily reusable.

Let's look at the simple one first:

TestClass test = new TestClass();

EventHandler handler = null; // avoid: error CS0165: Use of unassigned local variable 'handler'
test.SimpleEvent += handler = delegate
{
   test.SimpleEvent -= handler; // remove self before executing the critical code
   Console.WriteLine("Hello world!");
};

test.RaiseAll();
test.RaiseAll();
test.RaiseAll();

// output:
// Hello world!

There are a couple of things to note here:

  1. Since we know the type of TestClass's SimpleEvent, we can declare a strongly typed EventHandler and assign an anonymous method to it. Not knowing this type at compile-time is the source of much of the complication of the reusable solution below.
  2. Also, we temporarily assign null to handler before referring to handler within the body of the anonymous method to avoid the noted compiler error. Then, we assign the anonymous method to handler before attaching handler to the event.

But, Jacob, this works fine. Why would we care about "improving" it?

Well, for starters, it's not very extensible. The critical code is embedded into the anonymous method. So anytime we want to bring different functionality to this event, we'll need to repeat this pattern. Also, not only is the critical code not pluggable, but we've constrained ourselves to only EventHandler events. There are other types of strongly typed event handling delegates with far more interesting EventArgs (and how does the name "EventArgs" not violate the Framework Design Guidelines, anyway?). And lastly, … well… because we can:

[At this point in the post, the author suddenly switches voices: the hand-holdy, instructive teacher is replaced with the programmer who has spent too much time with the material at hand and pastes in swaths of code assuming his audience will understand. Apologies for the lack of exposition to follow.]

public static class EventUtility
{
   public static void AttachRaisedOnce<TTarget>(TTarget target, string eventName, EventHandler raisedOnce)
      where TTarget : class
   {
      AttachRaisedOnce<TTarget, EventArgs>(target, eventName, CastDelegate<EventHandler<EventArgs>>(raisedOnce));
   }

   public static void AttachRaisedOnce<TTarget, TEventArgs>(TTarget target, string eventName, EventHandler<TEventArgs> raisedOnce)
      where TTarget : class
      where TEventArgs : EventArgs
   {
      EventInfo eventTarget = typeof(TTarget).GetEvent(eventName);
      if (eventTarget == null)
         throw new ArgumentException(String.Format("Couldn't find event with name '{0}'", eventName), "eventName");

      Delegate self = null; // avoid unassigned local
      EventHandler<TEventArgs> localMethod = delegate(object sender, TEventArgs e)
      {
         eventTarget.RemoveEventHandler(target, self);
         raisedOnce(sender, e);
      };

      self = Delegate.CreateDelegate(eventTarget.EventHandlerType, localMethod.Target, localMethod.Method);
      eventTarget.AddEventHandler(target, self);
   }

   // see earlier post
   // also: belongs elsewhere; maybe a static DelegateUtility class
   private static T CastDelegate<T>(Delegate source)
      where T : class // CS0702: Constraint cannot be special class 'System.Delegate'
   {
      if (source == null)
         return null;

      Delegate[] delegates = source.GetInvocationList();
      if (delegates.Length == 1)
         return Delegate.CreateDelegate(typeof(T), delegates[0].Target, delegates[0].Method) as T;

      for (int i = 0; i < delegates.Length; i++)
         delegates[i] = Delegate.CreateDelegate(typeof(T), delegates[i].Target, delegates[i].Method);

      return Delegate.Combine(delegates) as T;
   }
}

Calling code looks like:

TestClass test = new TestClass();

EventUtility.AttachRaisedOnce(test, "SimpleEvent", delegate { Console.WriteLine("Hello world!"); });

test.RaiseAll();
test.RaiseAll();
test.RaiseAll();

// output:
// Hello world!

For strongly typed EventHandlers, like the System.Web.UI.ImageClickEventHandler delegate, the calling code looks a little weird:

EventUtility.AttachRaisedOnce<ImageButton, ImageClickEventArgs>(button, "Click", delegate { Response.Write("Hello web!"); });

Note that the second type argument is the type of the EventArgs, not the type of the EventHandler delegate itself. This is due to the inablity to apply a Delegate constraint on a type argument, coupled with the need to assign an anonymous method to a local variable with the right type. We rely on the fact that we can convert from an EventHandler<T> to the strongly typed EventHandler delegate. This means that it is also possible to compile code with the wrong event/delegate combinations; but don't worry: the runtime will "inform you" of any conversion failures.

Enjoy.

Friday, November 3

Enum TryParse

I was somewhat surprised to discover that there's no TryParse method for the Enum class in the .NET Framework 2.0. (It's also odd that the docs/compiler keep referring to Enum as a class—and yet the apprpriate where constraint to apply is struct.) So here's a pair of generic methods to support TryParse for Enums:

public static class EnumUtility
{
   public static bool TryParse<T>(string value, out T result)
      where T : struct // error CS0702: Constraint cannot be special class 'System.Enum'
   {
      return TryParse<T>(value, out result, false);
   }

   public static bool TryParse<T>(string value, out T result, bool ignoreCase)
      where T : struct // error CS0702: Constraint cannot be special class 'System.Enum'
   {
      result = default(T);
      try
      {
         result = (T)Enum.Parse(typeof(T), value, ignoreCase);
         return true;
      }
      catch { }

      return false;
   }
}