C# Corner

Use Lambda Expressions for Abstract Delegates

Lambda expressions are nothing more than convenient syntax for delegates. So why can't you use them where the Framework expects a delegate? And more importantly, what can you do about it?

TECHNOLOGY TOOLBOX: C#

I've written before about how much I like lambda expressions and the way type inferences work in C#. I think lambda expressions have more clarity than the combination of delegate definitions and out-of-band method definitions and the related extra work you have to do only to satisfy the language definitions. But, there are some weaknesses. If a method's parameters contain the System.Delegate abstract type, using lambda expressions introduces special problems: The C# compiler can't convert a lambda to some not-yet-defined concrete, derived delegate type.

Without some extra thought on your part, you'll end up with code that looks like it came from .NET 1.0. I'll walk you through the exercise of understanding why lambda expressions aren't immediately convertible to abstract delegate types, and how you can convince the compiler to convert the specific delegate type you defined for your use. The solution I'll describe relies on Windows Presentation

Foundation (WPF) and the System.Windows.Threading.Dispatcher component, but this is not strictly a WPF issue. The issue described crops up in several locations in the .NET Framework, including the Windows Forms, Office APIs, and Reflection APIs. You'll be able to follow the approach described in this article when you see it crop up in those other technologies as well.

Whenever I use an API in the .NET Framework that has a parameter that's some form of delegate, I prefer to use a lambda expression instead of a more verbose representation. For example, this line of code creates a System.Windows.Threading.Timer that calls the method TickHandler when the timer fires:

tick = new System.Threading.Timer((unused) => 
    TickHandler());

If the body of the method were small enough, I would replace the TickHandler() method call with the body of the method. That approach works fine most of the time, but the same technique doesn't work when the API uses System.Delegate as a parameter. For example, you use the System.Windows.Controls.Dispatcher.Invoke() method to marshal calls across threads in WPF:

public object Invoke(
    delegate method,
    params object[] args)

Now consider what happens when you try to use a lambda expression for this call:

MyTime.Dispatcher.Invoke(() => DoSomething());

You get the cryptic error:

error CS1660: Cannot convert lambda expression to 
type 'System.Delegate' because it is not a delegate type

You'll probably wonder what's going on the first time you see this error. Of course, it's a delegate type, right? Well, the compiler isn't quite as smart as you are. The System.Delegate type is an abstract type, and the type inference engine can't infer the number and types of the arguments or the return value for some unknown delegate type derived from System.Delegate. To fix this problem, you must create a concrete delegate type and assign your lambda expression to that type. Before you delve too far into the distant past and begin writing C# 1.0 code, remember delegate types let you treat methods as data.

I built a WPF timer application to show you how this works, illustrating how a little C# 3.0 can make it easy to work with these older APIs (see Figure 1). The timer is quite simple: You give it a length of time and press start. The background changes from light green, to yellow, to red as time runs out.

Updating the display based on time requires responding to events from a timer. Timers run on background threads, so you'll run headlong into the problem I described at the outset.

Updating the Application
The UI handling code is straightforward. The work happens when the timer fires, and the code updates the timer display. The update must change the text, and possibly the background for the control. That's a couple quick lines of code:

MyTime.Background = newBrush;
MyTime.Content = label;

The timer runs on a background thread, so you need to marshal the call across the thread boundary using the Dispatcher.Invoke() method. These two lines of code are what you'd want to put in a lambda expression, not the heavy-duty business logic that justifies a method definition. But I said earlier that a lambda doesn't work with Dispatcher.Invoke. It can, but you need to use a concrete delegate definition. Several are defined in .NET Framework 3.5. The fact that you can use the built-in delegate definitions and assign them makes this solution less work than might initially appear to be the case. These two lines also require a pair of parameters: a string for the content and a brush for the background color. That means you need to use a delegate definition that takes two parameters and returns void:

Action<string, Brush> updateTimer;

After declaring the variable, you can assign the delegate variable to the body of the code you want to execute. You can use a lambda expression here because Action<T1, T2> is a concrete delegate definition:

updateTimer = (label, newBrush) =>
    {
        MyTime.Background = newBrush;
        MyTime.Content = label;
    };

Now you've got a member variable that points to the block of code that you want to execute when the timer raises its event. All that's left is to use the delegate definition with Dispatcher.Invoke():

if (!MyTime.Dispatcher.CheckAccess())
{
    MyTime.Dispatcher.Invoke(updateTimer, 
        newLabel, next);
}
else
    updateTimer(newLabel, next);

This process is straightforward, but it's something that you'll do repeatedly, so let's go a few steps further and make this easier to reuse.

There's a simple pattern at work here. Event handlers can be called from a background thread. You'll see this behavior when you use timers, when you call Web services asynchronously, and in other common tasks. Whenever you're not sure what thread you're on, you can call Dispatcher.CheckAccess() to determine whether you can access any UI controls. If you need to marshal a call across thread boundaries, you must use Dispatcher.Invoke(). The Dispatcher.Invoke() method avoids several different overloads by using a params array for any parameters to the method; it uses an abstract delegate type for the code that you want to execute.

You want a single method that checks whether marshaling is needed. If marshaling is needed, the method marshals the call; otherwise, it calls the method pointed to by the delegate. You want that method to appear as if it were a member of the System.Windows.Controls.Control class. This enables you to use the code as if it were part of any control. C# 3.0 gives you a way to do this: extension methods. You need to write a few different overloads of the methods, which enables you to use them with different numbers of parameters:

public static class WPFExtensions:

{
    public static voidInvokeIfNeeded(
       this Control widget, 
       Action whatToDo)
    {
        if (!widget.Dispatcher.
            CheckAccess())
            widget.Dispatcher.Invoke(whatToDo);
        else
            whatToDo();
    }

    public static void    
       InvokeIfNeeded<T>(
       this Controlwidget, Action<T> 
       whatToDo, T parm)
    {
        if (!widget.Dispatcher.CheckAccess())
            widget.Dispatcher.Invoke(whatToDo, parm);
        else
            whatToDo(parm);
    }

    public static void   
       InvokeIfNeeded<T1, T2>(this 
       Controlwidget, Action<T1, T2> 
       whatToDo, 
        T1 parm1, T2 parm2)
    {
        if (!widget.Dispatcher.
            CheckAccess())
            widget.Dispatcher.
            Invoke(whatToDo, 
            parm1, parm2);
        else
            whatToDo(parm1, parm2);
    }
}

Of course, you can extend this class by adding more overloads with more parameters. It's a simple extension.

There was a method to the WPF designers' madness: They wanted to make it as easy as possible to use the Dispatcher object by minimizing the surface area of the API. By using abstract delegates, and params on the parameter list, this object is usable in the widest possible number of scenarios. Any method with any number of parameters can be used. However, that comes with a downside. This more abstract API removes all type safety, and this hurts the compiler's ability to use type inference to make you more productive. The work around is to add your own layer of type-safe extension methods that provide a layer between your type safe calls and the more abstract .NET library API.

About the Author

Bill Wagner, author of Effective C#, has been a commercial software developer for the past 20 years. He is a Microsoft Regional Director and a Visual C# MVP. His interests include the C# language, the .NET Framework and software design. Reach Bill at [email protected].

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube