AJ's blog

July 24, 2010

Replacing Events by Callbacks

Filed under: .NET, .NET Framework, C#, Silverlight, Software Development — ajdotnet @ 1:57 pm

My last post laid out how the employment of events has changed recently. Most importantly the broadcasting scenario – which was the major pattern so far – is no longer the only relevant pattern. Rather the “event-based asynchronous pattern”, MSDN, has emerged. Reasons include the inherently asynchronous nature of Silverlight as well as parallel patterns.

Now for the practical implications of this new pattern. Let’s look at an example to get the idea, and a better understanding of the consequences in code…

Let’s assume a component that is instantiated, does some work (allegedly asynchronous, and notifying about the progress) and provides the result at the end via an event. This is akin to making a server call, showing a confirmation message box, or the way the BackgroundWorker component works.

Example 1: Using events

First, implementing the a component the classical way would look somewhat like this:

The result event needs a respective EventArgs class, the declaration and the trigger method:

public class WorkResultEventArgs : EventArgs
{
    public object ResultData { get; set; }
}

public class MyWorkingComponent1
{
    public event EventHandler<WorkResultEventArgs> WorkResult;
    
    protected virtual void OnWorkResult(object resultData)
    {
        if (WorkResult != null)
            WorkResult(this, new WorkResultEventArgs() { ResultData = resultData });
    } 
    …
}

A work progress event should go a little further and provide cancelation support:

public class WorkProgressEventArgs : CancelEventArgs
{
    public int Progress { get; set; }
    public object SomeData { get; set; }
}

public class MyWorkingComponent1

    public event EventHandler<WorkProgressEventArgs> WorkProgress;
    
    protected virtual bool OnWorkProgress(int progress, object someData)
    {
        if (WorkProgress == null)
            return true;
        
        var ea = new WorkProgressEventArgs() { Progress = progress, SomeData = someData, Cancel = false };
        WorkProgress(this, ea);
        return ea.Cancel ? false : true;
    }
    …
}

Now we only need the actual worker method:

public class MyWorkingComponent1

    …
     
    public void StartWork()
    {
        int sum = 0;
        for (int i = 0; i < 10; ++i)
        {
            sum += i;
            if (!OnWorkProgress(i, sum))
                return;
        }
        OnWorkResult(sum);
    }
}

Again, we may assume that there is some asynchronicity involved, e.g. the loop could contain a web request or something. But this example should do for the sake of the argument.

The usage (as by support form Visual Studio to create the event handlers) would look like this:

public void Test1()
{
    var worker = new MyWorkingComponent1();
    worker.WorkProgress += new EventHandler<WorkProgressEventArgs>(Worker_WorkProgress);
    worker.WorkResult += new EventHandler<WorkResultEventArgs>(Worker_WorkResult);
    
    worker.StartWork();
}

void Worker_WorkProgress(object sender, WorkProgressEventArgs e)
{
    Console.WriteLine(e.Progress + ":" + e.SomeData);
}

void Worker_WorkResult(object sender, WorkResultEventArgs e)
{
    Console.WriteLine("Result:" + e.ResultData);
}

Creating the component, registering the event handlers, running the task, throw the component away. The fact that events are multi cast capable is never used at all (and never will, as the component is rather short-lived).

I guess we can agree that this is all very boilerplate. And all in all, that’s quite some overhead, from the component perspective as well as from the client code.

Example 2: Using callbacks

Now let’s try the new approach. Rather than defining an event, I pass in two callbacks. The information that was carried in the EventArgs is moved to the parameter lists, thus no need for these classes. The Cancel property is replaced by the return value of the callback. And since the client code always follows the same idiom, I expect the callbacks as constructor parameters, eliminating a source of errors along the way — something that is not possible with event handlers:

public class MyWorkingComponent2

    public Action<MyWorkingComponent2, object> WorkResult {get;set;} 
    public Func<MyWorkingComponent2, int, object, bool> WorkProgress { get; set; } 
         
    public MyWorkingComponent2( 
        Action<MyWorkingComponent2, object> workResult, 
        Func<MyWorkingComponent2, int, object, bool> workProgress) 
    { 
        WorkResult = workResult; 
        WorkProgress= workProgress; 
    } 
    …
}

The worker method changes only slightly:

public class MyWorkingComponent2

    …
         
    public void StartWork() 
    { 
        int sum = 0; 
        for (int i = 0; i < 10; ++i) 
        { 
            sum += i; 
            if (!WorkProgress(this, i, sum)) 
                return
        } 
        WorkResult(this, sum); 
    } 
}

That’s it. No EventArgs classes, no events, no respective OnEventHappened methods. Granted, the callback declarations are a little more complex, and their parameters also lack intellisense providing information about the semantics of each parameter. But otherwise? Way shorter, way more concise, way less overhead. The actual worker method hasn’t changed at all, but all the event related overhead is gone, which amounted to only 40% LOC.

Now the client code, first only slightly adapted:

public void Test1()
{
    var worker = new MyWorkingComponent2(
        (sender, resultData) => Worker_WorkResult(sender, resultData),
        (sender, progress, someData) => Worker_WorkProgress(sender, progress, someData)
    );
    
    worker.StartWork();
}

bool Worker_WorkProgress(object sender, int progress, object someData)
{
    Console.WriteLine(progress + ":" + someData);
    return true;
}

void Worker_WorkResult(object sender, object resultData)
{
    Console.WriteLine("Result:" + resultData);
}

As you can see, it didn’t change that much. But passing in the lambdas via the constructor fits the use case far better than events, and it is even more robust, as I cannot forget to pass in a callback via the constructor, the way I can forget to register an event handler.

Speaking of lambdas, and since the implementation is that simple, we can even simplify the client code further by omitting those two handler methods:

public void Test1()
{
    var worker = new MyWorkingComponent2(
        (sender, resultData) => { Console.WriteLine("Result:" + resultData); },
        (sender, progress, someData) => { Console.WriteLine(progress + ":" + someData); return true; }
    );
    
    worker.StartWork();
}

Alright, this would have been possible with events as well if you used anonymous methods. But Visual Studio guides you otherwise and early examples of anonymous methods (before we had lambdas) where rather ugly, so I doubt that can be seen as valid counterargument. Here however lambdas can be seen as typical means of choice.

Verdict

Neat? Net result:

  • I’m writing less code on the event source side, including no longer declaring EventArgs classes.
  • I’m writing less code on the event sink side.
  • The handler methods can use clean parameter lists (rather than EventArgs).
  • I’m eliminating the risk of forgetting to register event handlers by making the callbacks explicit parameters.
  • I’m elimination the danger of leaks due to failing to consistently deregistering event handlers.
    • (That was not addressed in the example, but still.)
  • When chaining together several of these steps I can make the logic – especially conditional processing – more explicit and concise.
    • Events would either require setting up beforehand (partly unnecessary overhead), or setup on demand, cluttering the handler with registration and deregistration code.

All in all, this is way more readable, way more robust, and way more efficient than using events.

I for one have begun to adopt this scheme quite liberally. My Silverlight bookshelf application has wrappers for service calls that translate the event to callbacks (several actually, including error handling and other demands). My dialogs always take callbacks for OK and Cancel. I so far have two ICommand implementations, both take callbacks (one with parameters, the other without). I even have a PropertyObserver class that translates a PropertyChanged event into a callback.
Actual event handlers? Apart from the wrappers enabling what I just presented, only a few reacting to control events.

In other words: This is not just an interesting technical detail. It really changes the way I’m addressing certain demands.

That’s all for now folks,
AJ.NET

kick it on DotNetKicks.com

Blog at WordPress.com.