Wednesday, May 28, 2008

ASP.NET AJAX Client Library: Combining createDelegate and createCallback

When working with the ASP.NET AJAX client library, I find that I occasionally need to use both Function.createDelegate() and Function.createCallback() simultaneously.

Each time you use one or the other of these methods, the library places a "call wrapper" around your function.  Then, when the new delegate (or callback) is called, there are actually two calls being made: the first to the wrapper, and the second when the wrapper calls your function.

By combining the use of both methods, you're now placing two call wrappers around your function, which is fairly inefficient, not to mention that it's not exactly elegant or readable.

Why would you want to combine the two methods?

You'd want to combine them when you want to ensure that this refers to a specific object, and you also want to pass specific arguments ("context") to the function.

For example, let's start with two objects, each containing an array of messages (strings).   We also have a function that expects that this will refer to one of the objects, and it will expect to receive the index of the message to display as an argument.

(This is a very contrived example, but it shows the requirement and solution in simple terms.  Real-world situations that require the use of both createDelegate and createCallback are more complex.)

var myObject1 = {
    messages: ["Red", "Blue", "Black"]

var myObject2 = {
    messages: ["Audi", "Chevy", "Mitsubishi"]

function showAlert(index) {

Now, I'll create two delegates/callbacks: one will call showAlert() and display a color, and the other will call showAlert() and display a car type.  I'll do this by combining the use of createDelegate and createCallback.

var showColor = Function.createDelegate(myObject1, Function.createCallback(showAlert, 2));

var showCar = Function.createDelegate(myObject2, Function.createCallback(showAlert, 1));

If we were to call showColor(), the user would see the word "Black" appear in a alert window.  Likewise, if we were to call showCar(), the user would see the word "Chevy" appear in a alert window. 

The significance of what took place here is that both showColor and showCar can be passed as simple function references to an event or method, and they retain not only the calling object context that we desired (this), but also the argument(s) that we needed to pass (the "context").  It allows us to use one single function (showAlert) to satisfy the display requirements of both objects (myObject1 and myObject2).

The problem with the showColor and showCar methods, as written above, is that they are inefficient because they make a total of three calls each time one is called (two wrapper calls plus the actual function call), and looking at the code, it can be difficult to understand what it is doing (i.e., the code lacks readability).

To solve both issues, I have created a new method called Function.createDelegateCallback().

In one step, we can specify a this reference, the function to wrap/call, and arguments ("context") to pass to the function.

The simplest way of creating the new method would have been to create a method that calls Function.createDelegate(this, Function.createCallback(func, args)), which would solve the readability issue, but would do nothing to solve the inefficiency issue.

Instead, I started with the source code for createCallback, and modified it to include the createDelegate functionality, making sure that only one wrapper call would be placed around the function.

Note: the new createDelegateCallback method is added directly to the JavaScript Function object, just like createDelegate and createCallback are today, so it is utilized in exactly the same way.

Update: After some additional testing, I have slightly modified the code below.  I am not sure as to the reasons for Microsoft coding the for loop they way they did in createCallback, but I accepted at face value that it was the best way.  I now believe it is better below, and my testing bears that out.  There is certainly a reason they coded it the way they did; I just can't figure it out.

Additional note: If you want to pass more than one argument to the target function, simply include them after the context argument.

Function.createDelegateCallback = function (instance, method, context) {
    /// <param name="instance" mayBeNull="true"></param>
    /// <param name="method" type="Function"></param>
    /// <param name="context" mayBeNull="true"></param>
    /// <returns type="Function"></returns>

    return function() {
        var l = arguments.length;

        if (l > 3) {
            var args = [];

            for (var i=2; i<l; i++) {
                args[i-2] = arguments[i];

            return method.apply(instance, args);

        return, context);

Now that we have the new createDelegateCallback method, we can re-write the methods above to make them more efficient and readable:

var showColor = Function.createDelegateCallback(myObject1, showAlert, 2);

var showCar = Function.createDelegateCallback(myObject2, showAlert, 1);

createDelegateCallback is not something you'll use every day, but if you do a lot of client-side coding with the ASP.NET AJAX client library, you will be glad someday that you have it.