-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
There are diagnostic scenarios for which people currently use reflection:
- Obtaining information about a method on a stack frame (
StackFrame.GetMethod
) - Obtaining information about a target of a delegate (
Delegate.Method
)
These APIs return a fully functional MethodBase
instance.
This poses a problem for trimming because the MethodBase
returned from these APIs needs to be fully functional (up to and including providing the ability to invoke the method). This means the MethodBase
needs to have intact parameter names, custom attributes including all the nullable annotation cruft, etc.
Trimming currently approaches this two ways:
StackFrame.GetMethod
is marked asRequiresUnreferencedCode
. Accessing it in a trimmed or AOT'd app produces a build time warning. The property may or may not return a null or an incomplete/damagedMethodInfo
. It will mostly return a damaged/incompleteMethodInfo
in a trimmed app (e.g. missing parameter names), and mostly return null in an AOT'd app.Delegate.Method
is trim safe. Trimming and AOT ensure all delegate targets are considered reflection visible.
Solution 1 currently blocks some customer scenarios (#92869) that people are not able to do well in trimmed apps.
Solution 2 leaves size savings on the table.
Fundamentally, many of the use cases of StackFrame.GetMethod
/Delegate.Method
only need names of things, they don't actually need the whole MethodBase
. For example, the use cases in #92869, or our own use cases within the framework like
runtime/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
Line 1687 in 396edb2
TplEventSource.Log.TraceOperationBegin(this.Id, "Task: " + m_action.Method.Name, 0); |
The proposal is to introduce a new API that would be trim safe and produce just the name part, using a more limited portion of the metadata.
API Proposal
namespace System.Diagnostics;
public sealed class DiagnosticMethodInfo
{
// Returns name of the method, same as MethodInfo.Name
public string Name { get; }
// Returns FullName of the declaring type. Same as MethodInfo.DeclaringType.FullName
public string DeclaringTypeName { get; }
// Returns full name of the declaring assembly, same as MethodInfo.Module.Assembly.FullName
public string DeclaringAssemblyName { get; }
}
public static void DiagnosticExtensions
{
// These return a nullable result because if the app is aggressively trimmed (e.g. `StackTraceSupport` https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options?pivots=dotnet-6-0#trimming-framework-library-features feature switch is disabled), we might return null
public static DiagnosticMethodInfo? GetDiagnosticMethodInfo(this Delegate del);
public static DiagnosticMethodInfo? GetDiagnosticMethodInfo(this StackFrame frame);
}
API Usage
Action a = Method;
Console.WriteLine($"Delegate points to {a.GetDiagnosticMethodInfo()?.Name ?? "Unknown method"});
Alternative Designs
Don't trim any metadata from methods that are statically reachable so that StackFrame.Method always works. This is an instant 20% size regression for AOT (where we don't just trim names of parameters, but also other things), probably a little less for IL level trimming (where we currently only trim parameter names).
Risks
No response