« .NET Regular Expressions and Unicode | Main | Fix for error C2373 after upgrading to VS2008 SP1 »

July 28, 2008

Casting delegates

One of the annoying things about delegates in .NET is that delegates with exactly the same parameters and return type are not compatible. Specifically, you cannot cast a delegate to a delegate of another type even if they have the same parameters and return type.

Predicate<int> isPositive = n => n > 0;

Func<int, bool> isPositive2 = (Predicate<int>) isPositive; // COMPILER ERROR

This problem is mitigated somewhat in C# 3.5, which defines generic delegates that take arbitrary parameters and return types and encourages their use: Action, Action<T>, Action<T1, T2>, ..., Func<TR>, Func<T, TR>, Func<T1, T2, TR>, ...

However, all of the "old" delegates still exist and are in use: AsyncCallback, Comparison<T>, and Predicate<T>, to name a few.

The biggest source of delegate types is event handlers. There's plain old EventHandler and the newer EventHandler<T>, but there are still lots of non-generic event handlers like CancelEventHandler. Neither WPF nor Windows Forms use EventHandler<T>, so they are chock full of unique delegate types that take an object and some EventArgs-derived class.

Usually this doesn't present a problem, but occasionally you'd like to convert between compatible delegates. If both types are known at compile-time, you can just use a lambda:

Predicate<int> isPositive = n => n > 0;

Func<int, bool> isPositive2 = n => isPositive(n);

But sometimes, the types aren't known at compile-time. We primarily find this to be the case when trying to write generic utility code that can work with arbitrary event handlers. Fortunately, it is possible to cast between arbitrary delegate types, though it isn't as efficient as you might like – DelegateUtility.Cast:

public static class DelegateUtility

{

    public static T Cast<T>(Delegate source) where T : class

    {

        return Cast(source, typeof(T)) as T;

    }

 

    public static Delegate Cast(Delegate source, Type type)

    {

        if (source == null)

            return null;

 

        Delegate[] delegates = source.GetInvocationList();

        if (delegates.Length == 1)

            return Delegate.CreateDelegate(type,

                delegates[0].Target, delegates[0].Method);

 

        Delegate[] delegatesDest = new Delegate[delegates.Length];

        for (int nDelegate = 0; nDelegate < delegates.Length; nDelegate++)

            delegatesDest[nDelegate] = Delegate.CreateDelegate(type,

                delegates[nDelegate].Target, delegates[nDelegate].Method);

        return Delegate.Combine(delegatesDest);

    }

}

There is a generic version and a non-generic version. Note that the null case is handled first, followed by the single-invocation case, followed by the rare multiple-invocation case.

It is quite straightforward to use. (We'd have made it an extension method, but converting delegates isn't really a common enough need to justify it.)

CancelEventHandler handler = (source, e) => e.Cancel = OnCancel();

EventHandler<CancelEventArgs> handler2 =

    DelegateUtility.Cast<EventHandler<CancelEventArgs>>(handler);

The types used by the two delegate types must be exactly the same for DelegateUtility.Cast to work. Supporting compatible types is left as an exercise for the reader; we certainly haven't needed it.

Posted by Ed Ball at July 28, 2008 4:00 PM

Trackback Pings

TrackBack URL for this entry:
http://ancientblogs.logos.com/mt-cgi/mt-tb.cgi/218