Monday, August 10, 2009

Clearing event handlers implemented with lambda expressions

One of the niceties included with C# 3.0 is lambda expressions, a concise syntax for implementing anonymous methods. Lambda expressions can be over-used, but they're very handy for certain kinds of things, like defining how asynchronous callbacks should be handled, all within a single method.

Lambda expressions are also very handy for implementing event handlers. For instance, if you've got an AlarmClock class that looks like this:

class AlarmClock 
{
  public event EventHandler<EventArgs> Alarm;
  public void SoundAlarm()
  {
    if (Alarm != null)
    {
        Alarm(this, new EventArgs());
    }
  }
}

You would normally assign and implement an event-handler like this, with a separate defined method:

class Program
{
  public static void Main()
  {
    AlarmClock clock = new AlarmClock();
    clock.Alarm += new EventHandler<EventArgs>(clock_Alarm);
    clock.SoundAlarm();
  }

  static void clock_Alarm(object sender, EventArgs e)
  {
    Console.WriteLine("The alarm went off at {0}.", DateTime.Now);
   }
}

But with lambda expressions, you can do it like this, all in one method:

class Program
{
  public static void Main()
  {
    AlarmClock clock = new AlarmClock();
    clock.Alarm += (s, e) => 
    Console.WriteLine("The alarm went off at {0}.", DateTime.Now);
    clock.SoundAlarm();
  }
}

Most folks would agree that this looks cleaner, and is easier to follow. (At least, once you get the hang of lambda expressions, which admittedly have a rather odd syntax.)

The one time this doesn't work is when you need to be able to clear event handlers as well as assign them. The way that you normally do this is so:

public static void Main()
{
  AlarmClock clock = new AlarmClock();
  clock.Alarm += new EventHandler<EventArgs>(clock_Alarm);
  clock.SoundAlarm();
  clock.Alarm -= new EventHandler<EventArgs>(clock_Alarm);
}

But you can't do that with the lambda expression I used above, because you don't have any way to identify the lambda expression that you want to remove. I've seen a variety of folks asking questions about how to clear event handlers implemented as lambda expressions for this very reason.

It turns out that there are (at least) two ways to do it.

If you've got access to the source code for the class in question, you can write a public ClearEventHandlers() method that works like so:

class AlarmClock
{
    public event EventHandler<EventArgs> Alarm;
    public void SoundAlarm()
    {
        if (Alarm != null)
        {
            Alarm(this, new EventArgs());
        }
    }
    public void ClearEventHandlers()
    {
        Alarm = null;
    }
}

But if you need more granularity, or don't have access to the source, you can assign the lambda expression to a variable (e.g., "handleAlarm") before you assign it as an event-handler, like so:

public static void Main()
{
    AlarmClock clock = new AlarmClock();
    EventHandler<EventArgs> handleAlarm = null;
    handleAlarm = (s, e) =>
    {
        clock.Alarm -= handleAlarm;
        Console.WriteLine("The alarm went off at {0}.", DateTime.Now);
    };
    clock.Alarm += handleAlarm;
    clock.SoundAlarm();
}

Note that you need to assign the handleAlarm variable to null first, because otherwise the compiler complains about the first line of the lambda expression: it thinks that you're trying to muck about with an uninitialized variable. This is only sort of true, but to work around it, just assign the variable to null when you declare it.

The syntax for #2 is a bit unwieldy, but it can let you do things that would be difficult or complicated if you had to use a second method. For instance, within lambda expressions you can use local variables declared at the level of the containing function:

public static void Main()
{
    AlarmClock clock = new AlarmClock();
    EventHandler<EventArgs> handleAlarm = null;
    ManualResetEvent resetEvent = new ManualResetEvent(false);
    bool alarmSounded = false;
    handleAlarm = (s, e) =>
    {
        clock.Alarm -= handleAlarm;
        Console.WriteLine("The alarm went off at {0}.", DateTime.Now);
        alarmSounded = true;
        resetEvent.Set();
    };
    clock.Alarm += handleAlarm;
    clock.SoundAlarm();
    resetEvent.WaitOne(5000);
    if (!alarmSounded)
    {
        Console.WriteLine("The alarm didn't sound within the timeout period.");
    }
}

To duplicate this without lambda expressions, you'd have to declare the resetEvent and alarmSounded variables at the class level, which isn't nearly as clean, and could lead to some odd bugs if other methods were trying to use those same variables simultaneously.

Windows 7: Not Terribly Impressed

Windows Vista was never as bad as its reputation; and Windows 7 is not as good as everyone claims. It's not bad, but it still needs some work. And I dislike some of the design decisions.

Here are a few snippets from my own experience:

  • Something is wrong with the graphics drivers or graphics subsystem. Applications that worked fine under Vista keep "flickering" in Windows 7. And my Chrome browser keeps displaying a weird sort of static when it opens a new page, sometimes just for a quarter-second, sometimes permanently. Perhaps this is just a case of a bad video driver (I'm running an NVIDA GeForce 8800 GT), but I've installed the latest Windows 7-optimized driver, and it's still happening.
  • I've had two different blue-screens in my 48 hours of running Windows 7 RTM. Not a reassuring way to start a new relationship.

The computer has rebooted from a bugcheck. The bugcheck was: 0x0000001a (0x00001236, 0x878be008, 0x878be08c, 0x00070054). A dump was saved in: C:\Windows\MEMORY.DMP. Report Id: 081009-37970-01.

The computer has rebooted from a bugcheck. The bugcheck was: 0x0000008e (0xc0000005, 0x8c28b885, 0xb062b750, 0x00000000). A dump was saved in: C:\Windows\MEMORY.DMP. Report Id: 081009-30061-01.

  • If you turn off UAC (you pretty much have to, unless you're the sort of person who shops here), the UI gives you the impression that it's taken effect right away – and some parts of it have. But you have to restart your machine to actually be able to launch everything as an Administrator. This is no different from Vista, except that the UI is more honest in Vista, and tells you that you need to reboot.
  • I dislike the new metaphor for "pinning" applications to the taskbar. Among other things, it means that if I have multiple instances of an application open, I have to click twice to switch to the right one, rather than just once. It's also much more difficult and confusing to tell at a glance if something is open. I don't get why MS thought that clicking twice is better than clicking once. Luckily, you can turn this behavior off; but it's still an odd default.
  • I don't get the new file manager. It lists very prominently a whole bunch of shortcuts that I've never found useful, and hides the thing I actually do want to use, a direct tree view into my file system. On top of that, it doesn't synchronize the tree view with where I've navigated to in the file system. So the tree view can be sitting at C:\, long after I've navigated to, say, C:\source\slidelinc\branches\98a_SlideLincClient_ken. This last is a behavior that you can change if you know where to look, but it's a rather weird and unpleasant default.

Again, none of these things are killers, and maybe some people will like the new UI and its associated defaults. On the whole, Windows 7 is OK, though not terribly impressive. It feels to me like a not-very-ambitious bug-fix to Vista. I'll run it. But I continue to wish that MS could be more successful at their core operating system business.