C#反射如何在方法内部获取局部函数
C# Reflection how to get local function inside a method
您好,我想列出一个方法内的所有局部函数,但我找不到任何方法来获取这些方法。
示例:
我想获取 MyFunction MethodInfo,使用 Assembly.EntryPoint.GetLocalMethods()
谢谢你的时间,如果我不够清楚,请告诉我。
经过sharplab的一些实验,我发现所有本地方法都被编译为内部静态方法,名称如下:
<Name1>g__Name2|x_y
Name1
是周围方法的名称。 Name2
是本地方法的名称。 x
和 y
是我还不是很清楚它们的含义的数字。他们还有一个 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);
}
请注意,这在很大程度上依赖于实现细节。这很可能在未来发生变化,您的代码将会中断。
虽然 在大多数情况下是正确的,但也有例外。
基本上我发现至少在以下条件下:
- 方法中有lambda capturing/referencing 任何局部变量或参数,并且
- 本地方法正在捕获 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)
.
您好,我想列出一个方法内的所有局部函数,但我找不到任何方法来获取这些方法。
示例:
我想获取 MyFunction MethodInfo,使用 Assembly.EntryPoint.GetLocalMethods()
谢谢你的时间,如果我不够清楚,请告诉我。
经过sharplab的一些实验,我发现所有本地方法都被编译为内部静态方法,名称如下:
<Name1>g__Name2|x_y
Name1
是周围方法的名称。 Name2
是本地方法的名称。 x
和 y
是我还不是很清楚它们的含义的数字。他们还有一个 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);
}
请注意,这在很大程度上依赖于实现细节。这很可能在未来发生变化,您的代码将会中断。
虽然
基本上我发现至少在以下条件下:
- 方法中有lambda capturing/referencing 任何局部变量或参数,并且
- 本地方法正在捕获 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)
.