Reputation: 387
Hello i am trying to list all local functions inside of a method, but i dont find any way to get those methods.
Example:
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
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:
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
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