C#反射如何在方法内部获取局部函数

C# Reflection how to get local function inside a method

您好,我想列出一个方法内的所有局部函数,但我找不到任何方法来获取这些方法。

示例:

我想获取 MyFunction MethodInfo,使用 Assembly.EntryPoint.GetLocalMethods()

谢谢你的时间,如果我不够清楚,请告诉我。

经过sharplab的一些实验,我发现所有本地方法都被编译为内部静态方法,名称如下:

<Name1>g__Name2|x_y

Name1是周围方法的名称。 Name2 是本地方法的名称。 xy 是我还不是很清楚它们的含义的数字。他们还有一个 CompilerGeneratedAttribute.

反正有了这些信息,你就可以找到一个方法中所有的局部方法了!

首先你可以使用正则表达式来判断一个MethodInfo是否是一个周围方法的局部方法:

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;
}

然后你可以做一个简单的Where过滤器:

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

请注意,这在很大程度上依赖于实现细节。这很可能在未来发生变化,您的代码将会中断。

虽然 在大多数情况下是正确的,但也有例外。

基本上我发现至少在以下条件下:

  1. 方法中有lambda capturing/referencing 任何局部变量或参数,并且
  2. 本地方法正在捕获 capturing/referencing 任何局部变量或参数(不一定与 lambda 中捕获的相同)

在这种特殊情况下,本地方法将不再是 class 中的静态方法,而是编译器中生成的嵌套 class 中的实例方法,其中 class 名称位于<>c__DisplayClassx_y 的格式,即 <>c__DisplayClass10_0 和自定义属性将在 class 中,而该方法将没有任何自定义属性,本地方法名称将采用以下格式之一:

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

查找所有本地方法

因此,我建议使用以下代码来涵盖所有情况:

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);
}

寻找个别方法

虽然只是获取一个单独的方法,但下面的代码可能更清晰并且性能更好:

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;
}

正在调用

在调用该方法时,您应该注意,在以静态版本结束的一般非捕获情况下,您只需将 null 作为第一个参数传递给 .Invoke(),因为它是 static,对于捕获变量的例外情况,您需要传递一个实际对象(它是嵌套 class 的一个实例)。

嵌套 class 的结构似乎是每个捕获变量的一个 public 字段(而不是 属性),与捕获变量同名,除非它是一个受限制的名称,例如 this,在这种情况下,它使用格式为 <>x__this 的名称,例如 <>4__this.

因此,要调用该方法,您只需实例化 class 的一个实例,很可能是使用 Activator.CreateInstance(nestedType),然后设置所有需要的字段,很可能是 nestedType.GetField(nameof(myCapturedVariable), BindingFlags.Instance) .