Friday 29 August 2008

Method decoration using Lambda expressions

My latest foray into the world of .NET 3.5 got me wondering about how I might be able to 'decorate' methods from other assemblies, using a standard pattern, with the minimum of fuss.

To illustrate, in my particular example I wanted a 'RunAs' functionality for any existing method; so the workflow goes as follows:

1 - Cache any existing user details
2 - Logon a new user
3 - Execute some code
4 - Return the previous context

I didn't want other developers writing their own code to do this, because the potential for mistakes and misunderstanding is very high.

In the above example steps 1, 2 and 4 aren't important, I could have equally written

1 - Do something
2 - Execute some code
3 - Do something else

I found a neat way of doing this, involving both Lambda expressions and Extension methods (although I'm sure there's plenty of other ways to achieve the same thing).

Firstly, we'll create an extension method (mine is of System.Object so that ALL objects can do this, but you may want to restrict yours).

/// <summary>
/// Enables ANY Function to be executed as another user (prompts logon first)
/// </summary>
/// <typeparam name="R">Return type</typeparam>
/// <param name="source">Type to extend</param>
/// <param name="f">Function to be executed</param>
/// <returns>The response from the wrapped method</returns>
/// <remarks>Call this method like so:
/// <c>var result = this.RunAs(() => this.DoSomething());</c>
/// </remarks>
public static R RunAs<R>(this object source, Func<R> f)
{
DO SOMETHING PRIOR...
R result = f();
... DO SOMETHING POST
return result;
}

To call this method, we may write something like:

var result = this.RunAs(() => this.DoSomething());

Notice that the lambda expression is clever enough to figure out the return type for us. Pretty cool. Another remarkable fact is that the signature of the method you're calling doesn't even have to match the Func<R> delegate type in the extension method...

var result this.RunAs(() => this.DoSomethingElse( 1, 2, 3));

... would be equally valid! This is because the lambda expression actually evaluates to a Func<R> delegate, i.e. a function that takes no arguments (hence the () construct) and that returns something of type R (the return type of 'DoSomething').

An overload of the RunAs method is needed to execute an Action delegate (i.e. one with no return type)

public static void RunAs(this object source, Action a)
{
DO SOMETHING PRIOR...
a();
... DO SOMETHING POST
}


Does anybody else think this is totally cool?

Lambda expressions really are a very powerful tool for your programming locker.

Cheers!

Graeme

No comments: