2

How does one determine if a MethodInfo represents the metadata for a lambda expression?

user1546077
  • 157
  • 8
  • What do you mean? A MethodInfo would refer to a method, no? – Lasse V. Karlsen Apr 22 '14 at 19:04
  • Updated question to be slightly more specific, i.e. metadata for a lambda expression. – user1546077 Apr 22 '14 at 19:07
  • A MethodInfo will never represent a lambda expression. However, a `Delegate` or `Expression` may represent a lambda, and that delegate or expression may just represent a method call. Are you trying to get the `MethodInfo` from a delegate? – Michael Gunter Apr 22 '14 at 19:08
  • I guess I need to write out a class that passes an asynchronous lambda and show that when reflected, a MethodInfo is returned for the compiler generated lambda expression. This was not obvious to me and I should have expected it not to be obvious to others. I'll add the code but it will take a bit to create a self contained snippet. I'm getting all the methods of a type and trying to filter out the lambda expression methods the compiler has generated. – user1546077 Apr 22 '14 at 19:10
  • @MichaelGunter: "delegate or expression represent a method call"? I don't like the word "represent" here. – Wiktor Zychla Apr 22 '14 at 19:11
  • possible duplicate of [How to identify anonymous methods in System.Reflection](http://stackoverflow.com/questions/2503336/how-to-identify-anonymous-methods-in-system-reflection) – Martin Costello Apr 22 '14 at 19:16
  • unfortunately the methodInfos do not have the CompilerGeneratedAttribute, they have a DebuggerStepThroughAttribute and a AsyncStateMachineAttribute – user1546077 Apr 22 '14 at 19:21

2 Answers2

6

I think you are talking about anonymous methods.So, you can write an extension method for that and check whether the name of the method contains any invalid chars.Because the compiler generated methods contain invalid chars, you can use that feature to determine whether the method is anonymous or not:

public static bool IsAnonymous(this MethodInfo method)
{
     var invalidChars = new[] {'<', '>'};
     return method.Name.Any(invalidChars.Contains);
}

Test:

Func<int> f = () => 23;

Console.Write(f.Method.IsAnonymous());  // true

More elegant way would be validating the method name using IsValidLanguageIndependentIdentifier method, like this (method from this answer):

public static bool IsAnonymous(this MethodInfo method)
{
    return !CodeGenerator.IsValidLanguageIndependentIdentifier(method.Name);
}

Remember in order to access IsValidLanguageIndependentIdentifier method you need to include the System.CodeDom.Compiler namespace.

Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • fantastic, CodeGenerator.IsValidLanguageIndependentIdentifier it is - marking this as the answer – user1546077 Apr 22 '14 at 19:24
  • 2
    This doesnt detect Lambdamethods which are compiled into a delegate though. – CSharpie Apr 22 '14 at 19:24
  • 1
    @CSharpie is right. Using `method.DeclaringType == null || !CodeGenerator.IsValidLanguageIndependentIdentifier(method.Name)` fixes this, because DeclaringType is null for a compiled expression. – EM0 Nov 16 '16 at 16:21
  • This code does not make a proper distinction between lambdas and internal methods (it catches both as anonymous). See my answer below... – Frederic Jun 07 '19 at 13:59
1

The following code can do the trick. It is a bit long compared to the accepted answer, but alas the accepted answer does not make a proper distinction between lambdas and inner methods, which both get name mangled by the compiler. Hence the following provide two methods: IsAnonymous and IsInner.

By the way, the code should work under Mono as well (names seem to be mangled the same way but with a different magic tag under the hood).

public static class MethodInfoUtil
{
    static readonly Regex MagicTagPattern = new Regex(">([a-zA-Z]+)__");
    static readonly string AnonymousMagicTag;
    static readonly string InnerMagicTag;

    public static bool IsAnonymous(this MethodInfo mi)
    {
        return mi.Name.Contains(AnonymousMagicTag);
    }

    public static bool IsInner(this MethodInfo mi)
    {
        return mi.Name.Contains(InnerMagicTag);
    }

    public static string GetNameMagicTag(this MethodInfo mi, bool noThrow = false)
    {
        var match = MagicTagPattern.Match(mi.Name);
        if (match.Success && match.Value is string value && !match.NextMatch().Success)
            return value;
        else if (noThrow)
            return null;
        else
            throw new ArgumentException($"Cant find magic tag of {mi}");
    }

    // static constructor: initialize the magic tags
    static MethodInfoUtil()
    {
        void Inner() { };
        Action inner = Inner;
        Action anonymous = () => { };
        InnerMagicTag = GetNameMagicTag(inner.Method);
        AnonymousMagicTag = GetNameMagicTag(anonymous.Method);

        CheckThatItWorks();
    }

    [Conditional("DEBUG")]
    static void CheckThatItWorks()
    { 
        // Static mathods are neither anonymous nor inner
        Debug.Assert(!((Func<int, int>)Math.Abs).Method.IsAnonymous());
        Debug.Assert(!((Func<int, int>)Math.Abs).Method.IsInner());

        // Instance methods are neither anonymous nor inner
        Debug.Assert(!((Func<string, bool>)"".StartsWith).Method.IsAnonymous());
        Debug.Assert(!((Func<string, bool>)"".StartsWith).Method.IsInner());

        // Lambda 
        Action anonymous1 = () => { };
        Debug.Assert(anonymous1.Method.IsAnonymous());
        Debug.Assert(!anonymous1.Method.IsInner());

        // Anonymous delegates 
        Action anonymous2 = delegate(){ };
        Debug.Assert(anonymous2.Method.IsAnonymous());

        // Sublambdas 
        Action anonymous3 = new Func<Func<Action>>(() => () => () => { })()();
        Debug.Assert(anonymous3.Method.IsAnonymous());

        void Inner() { }
        Action inner1 = Inner;
        Debug.Assert(inner1.Method.IsInner());
        Debug.Assert(!inner1.Method.IsAnonymous());

        // Deep inner methods have same tag as inner
        Action Imbricated()
        {
            void Inside() { };
            return Inside;
        }
        Action inner2 = Imbricated();
        Debug.Assert(inner2.Method.IsInner());
    }
}
Frederic
  • 1,580
  • 15
  • 15