Mário Gabriel
Mário Gabriel

Reputation: 387

C# Reflection how to get local function inside a method

Hello i am trying to list all local functions inside of a method, but i dont find any way to get those methods.

Example:

enter image description here

I want to get the MyFunction MethodInfo, using something like Assembly.EntryPoint.GetLocalMethods()

Thanks for your time and please tell me if i was not clear enough.

Upvotes: 2

Views: 1436

Answers (2)

yoel halb
yoel halb

Reputation: 12711

While the the accepted answer is right in most cases, there are exceptions to the rule.

Basically I found that at least in the following condition:

  1. There is a lambda in the method capturing/referencing any local variable or parameter, and
  2. The local method is capturing capturing/referencing any local variable or parameter (not necessarily the same as the one captured in the lambda)

In this particular case the local method will no longer be a static method in the class but an instance method in a compiler generated nested class with the class name being in the format of <>c__DisplayClassx_y i.e. <>c__DisplayClass10_0 and the custom attribute will be in the class while the method will NOT have any custom attributes and the local method name will be in one of the following formats:

  • <Name1>g__Name2|x i.e. <MyClass>g__MyMethod|1
  • <Name1>g__Name2|x_y i.e. <MyClass>g__MyMethod|1_2

Finding all local methods

As such I would recommend the following code to cover all cases:

private static bool IsLocal(MethodInfo localMethod, string surroundingMethodName)
{
    var match = Regex.Match(localMethod.Name, @"^<(\w+)>g__(\w+)\|\d+(_\d+)?");
    return match != null && match.Groups[1].Value == surroundingMethodName;
}

foreach (var method in typeof(SomeClass)
     .GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
     .Where(x => IsLocal(x, nameof(SomeMethod)) && x.GetCustomAttribute<CompilerGeneratedAttribute>() != null))
     .Union(typeof(SomeClass)
            .GetNestedTypes(BindingFlags.NonPublic)
            .Where(t => t.GetCustomAttribute<CompilerGeneratedAttribute>() != null)
            .SelectMany(t => t.GetMethods(BindingFlags.NonPublic| BindingFlags.Instance)
                             .Where(x => IsLocal(x, nameof(SomeMethod))))
{
      Console.WriteLine(method.Name);
}

Finding an individual method

While to just get an individual method the following code might be more clear and have better performance:

public static bool IsMatchLocal(this MethodInfo localMethod, 
                     string surroundingMethodName, string localMethodName)
   => Regex.IsMatch(localMethod.Name, 
                $@"^<{surroundingMethodName}>g__{localMethodName}\|\d+(_\d+)?");

public static MethodInfo? GetLocalMethod(this Type type, 
                  string surroundingMethodName, string localMethodName)
{
     var method = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
             .FirstOrDefault(m => m.GetCustomAttribute<CompilerGeneratedAttribute>() != null 
                  && m.IsMatchLocal(surroundingMethodName, localMethodName));

     if(method is not null) return method;

     var nestedTypes = type.GetNestedTypes(BindingFlags.NonPublic)
                        .Where(t => t.GetCustomAttribute<CompilerGeneratedAttribute>() != null);
     foreach(var nested in nestedTypes)
     {
           method = nested.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
              .FirstOrDefault(m => m.IsMatchLocal(surroundingMethodName, localMethodName));

           if(method is not null) return method;
     }

     return null;
}

Invoking

When it comes to invoking the method, you should note that while in the general non captured case which ends up with a static version you just pass null as the first argument to .Invoke() since it is static, for the exceptional case with captured variables you need to pass an actual object (which is an instance of the nested class).

The structure of the nested class appears to be one public field (not property) per each captured variable, with the same name as the captured variable, unless it is a restricted name such as this in which case it uses a name in the the format of <>x__this, for example <>4__this.

So to invoke the method you just need to instantiate an instance of the class, most probably by using Activator.CreateInstance(nestedType) and then set all fields needed, most probably by doing nestedType.GetField(nameof(myCapturedVariable), BindingFlags.Instance).

Upvotes: 3

Sweeper
Sweeper

Reputation: 273565

After some experimenting in sharplab, I found that all local methods are compiled to internal static methods with a name of the following form:

<Name1>g__Name2|x_y

Name1 is the name of the surrounding method. Name2 is the name of the local method. x and y are numbers that I don't particular know what they mean yet. They also have a CompilerGeneratedAttribute.

Anyway, with this information, you can find all the local methods in a method!

First you can use a regex to determine whether a MethodInfo is a local method of a surrounding method:

private static bool IsLocal(MethodInfo localMethod, string surroundingMethodName)
{
    var match = Regex.Match(localMethod.Name, "^<(\\w+)>g__(\\w+)\\|\\d+_\\d+");
    return match != null && match.Groups[1].Value == surroundingMethodName && localMethod.GetCustomAttribute<CompilerGeneratedAttribute>() != null;
}

And then you can just do a simple Where filter:

foreach (var method in typeof(SomeClass)
    .GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
    .Where(x => IsLocal(x, nameof(SomeMethod))))
{
    Console.WriteLine(method.Name);
}

Note that this relies heavily on implementation detail. This could very well change in the future and your code will break.

Upvotes: 6

Related Questions