Monday, May 30, 2011

CodeDOM and poor man's lambdas -- Part 1

The CodeDOM expression methods are stuck at the level of .net 2.0, and look unlikely to grow further support for further syntax outside of feeding it in as literal strings using the System.CodeDom.CodeSnippet* classes -- which takes away the cross-language spirit of the whole library.

So, what to do when we want to generate code containing lambdas?

Well, the obvious way is to strip away the syntactic sugar involved in the lambda/closure notation and generate explicitly what the compiler is doing for you under the covers anyway.

So, to take a case which is relevant to my interests, if we have a method (of static type Wrapper, say) --

public static T LogCall<T>(string name, Func<T> call)

that will perform some pre- and post- call logging around invoking the delegate; and we have a method

public int DoIt(int argument, out string result)

which we want to invoke through the LogCall method (considered as a decorator to the action). Writing this by hand, we would want a decorator type containing a matching method something like this:

which exposes the same interface, and delegates to the real logic in the contained object wrapped.

If we examine the code that is actually generated by compiling this, we see that the whole closure is replaced by a synthetic private inner class with a public field for each object closed over -- in this case all the arguments to the DoIt method, plus a this reference for the decorator -- and a method that stands for the lambda, which, generated names aside, looks like:

while the wrapping method looks like

And now, while the analogous F# code would probably return an int * string tuple, rather than an out parameter for multiple returns, the equivalent code snippet will do the job, just changing the types of the Func object; and the literal transposition of the C# code, out parameters and all, should still work, cross language.

In the more general case, of lambdas with arguments, then the method in the proxy type has the same arguments as the lambda needs; and in the case where no variables are being closed over e.g.

Func<int, int> square = x => x*x;

there is an optimization that the supporting method can be made static on the enclosing type, rather than requiring an object to hold the closure references.

In part 2, having simplified the problem, we can move on to using the CodeDOM to generate the LogCall decorating method and closure, given a MethodInfo.

No comments :