Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to identify a lambda closure with reflection

I am writing a component that involves Actions and came upon a requirement to find a way to identify using reflection cases when the Action.Target object is a closure that the compiler have generated. I am doing an experiment to try and find a way, the purpose of this little experiment is to develop a predicate that takes an Action and returns a bool that tells if the action target is an instance of such closure class.

In my test case, I have the following methods that create 4 different types of actions:

    private void _createClosure(int i)
    {
        ClosureAction = new Action(() =>
        {
            var j = i;
            var k = somenum;
        });
    }

    private void _createLambda()
    {
        LambdaAction = new Action(() =>
        {
            this._instanceAction();

        });
    }

    private void _createInstance()
    {
        InstanceAction = new Action(_instanceAction);
    }

    private void _createStatic()
    {
        StaticAction = new Action(_staticAction);
    }

    private int somenum;

    private void _instanceAction()
    {
        somenum++;
    }

    private static void _staticAction()
    {

    }

The following table shows the properties of each action: Action properties

As you see, LambaAction and ClosureAction are quite similar in terms of defnition, they both use lambda, but the closure one has a local function variable that is being used inside the lambda, and therefore the compiler is forced into generating a closure class. Its clear that the second row, the one that presents ClosureAction, is the only one that has a target that is a closure type. The static one does not have a target at all, and the other two use the calling class (Called ActionReferences) as target. The next table presents a comparison of the target reflection type properties: Target Types

So we can see that what's unique about the closure case is that the target type is not a type info but rather a nested type. It's also the only one that is private nested, sealed and has a name that contains the string +<>c__DisplayClass. Now while I think that these characteristics are conclusive for any normal usage case, I would prefer to define a predicate that I can rely on. I don't like to base this mechanism on compilers naming conventions or properties that are not unique because technically, the user may create a private nested sealed class with the same naming convention... it's not likely, but it's not 100% clean solution.

So finally - the question is this: Is there a clean cut way to write a predicate the identifies actions that are actually compiler generated closures?

Thanks

like image 571
Kobi Hari Avatar asked Sep 08 '14 10:09

Kobi Hari


People also ask

What is Lambda closure?

A lambda expression is an anonymous function and can be defined as a parameter. The Closures are like code fragments or code blocks that can be used without being a method or a class. It means that Closures can access variables not defined in its parameter list and also assign it to a variable.

Why a lambda expression forms a closure?

a function that can be treated as an object is just a delegate. What makes a lambda a closure is that it captures its outer variables. lambda expressions converted to expression trees also have closure semantics, interestingly enough.


1 Answers

This isn't 100% accurate, but it generally works:

bool isClosure = action.Target != null && Attribute.IsDefined(
    action.Target.GetType(), typeof(CompilerGeneratedAttribute));
Console.WriteLine(isClosure);

You can of course force false positives just by manually adding [CompilerGenerated] to any type you choose.

You could also use action.Method.DeclaringType, but since all captures involve a target instance, it is useful to retain the Target check:

bool isClosure = action.Target != null && Attribute.IsDefined(
    action.Method.DeclaringType, typeof(CompilerGeneratedAttribute));
like image 192
Marc Gravell Avatar answered Oct 28 '22 06:10

Marc Gravell