in src/profiler/Elastic.Apm.Profiler.Managed/Reflection/DynamicMethodBuilder.cs [40:224]
public static TDelegate GetOrCreateMethodCallDelegate(
Type type,
string methodName,
OpCodeValue callOpCode,
Type returnType = null,
Type[] methodParameterTypes = null,
Type[] methodGenericArguments = null
) =>
Cache.GetOrAdd(
new Key(type, methodName, callOpCode, returnType, methodParameterTypes, methodGenericArguments),
key => CreateMethodCallDelegate(
key.Type,
key.MethodName,
key.CallOpCode,
key.MethodParameterTypes,
key.MethodGenericArguments));
/// <summary>
/// Creates a simple <see cref="DynamicMethod"/> using <see cref="System.Reflection.Emit"/> that
/// calls a method with the specified name and parameter types.
/// </summary>
/// <param name="type">The <see cref="Type"/> that contains the method to call when the returned delegate is executed..</param>
/// <param name="methodName">The name of the method to call when the returned delegate is executed.</param>
/// <param name="callOpCode">The OpCode to use in the method call.</param>
/// <param name="methodParameterTypes">If not null, use method overload that matches the specified parameters.</param>
/// <param name="methodGenericArguments">If not null, use method overload that has the same number of generic arguments.</param>
/// <returns>A <see cref="Delegate"/> that can be used to execute the dynamic method.</returns>
public static TDelegate CreateMethodCallDelegate(
Type type,
string methodName,
OpCodeValue callOpCode,
Type[] methodParameterTypes = null,
Type[] methodGenericArguments = null
)
{
var delegateType = typeof(TDelegate);
var genericTypeArguments = delegateType.GenericTypeArguments;
Type[] parameterTypes;
Type returnType;
if (delegateType.Name.StartsWith("Func`"))
{
// last generic type argument is the return type
var parameterCount = genericTypeArguments.Length - 1;
parameterTypes = new Type[parameterCount];
Array.Copy(genericTypeArguments, parameterTypes, parameterCount);
returnType = genericTypeArguments[parameterCount];
}
else if (delegateType.Name.StartsWith("Action`"))
{
parameterTypes = genericTypeArguments;
returnType = typeof(void);
}
else
throw new Exception($"Only Func<> or Action<> are supported in {nameof(CreateMethodCallDelegate)}.");
// find any method that matches by name and parameter types
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
.Where(m => m.Name == methodName);
// if methodParameterTypes was specified, check for a method that matches
if (methodParameterTypes != null)
{
methods = methods.Where(
m =>
{
var ps = m.GetParameters();
if (ps.Length != methodParameterTypes.Length)
return false;
for (var i = 0; i < ps.Length; i++)
{
var t1 = ps[i].ParameterType;
var t2 = methodParameterTypes[i];
// generics can be tricky to compare for type equality
// so we will just check the namespace and name
if (t1.Namespace != t2.Namespace || t1.Name != t2.Name)
return false;
}
return true;
});
}
if (methodGenericArguments != null)
{
methods = methods.Where(
m => m.IsGenericMethodDefinition &&
m.GetGenericArguments().Length == methodGenericArguments.Length);
}
var methodInfo = methods.FirstOrDefault();
if (methodInfo == null)
{
// method not found
// TODO: logging
return null;
}
if (methodGenericArguments != null)
methodInfo = methodInfo.MakeGenericMethod(methodGenericArguments);
Type[] effectiveParameterTypes;
var reflectedParameterTypes = methodInfo.GetParameters()
.Select(p => p.ParameterType);
if (methodInfo.IsStatic)
effectiveParameterTypes = reflectedParameterTypes.ToArray();
else
{
// for instance methods, insert object's type as first element in array
effectiveParameterTypes = new[] { type }
.Concat(reflectedParameterTypes)
.ToArray();
}
var dynamicMethod = new DynamicMethod(methodInfo.Name, returnType, parameterTypes, ObjectExtensions.Module, skipVisibility: true);
var il = dynamicMethod.GetILGenerator();
// load each argument and cast or unbox as necessary
for (ushort argumentIndex = 0; argumentIndex < parameterTypes.Length; argumentIndex++)
{
var delegateParameterType = parameterTypes[argumentIndex];
var underlyingParameterType = effectiveParameterTypes[argumentIndex];
switch (argumentIndex)
{
case 0:
il.Emit(OpCodes.Ldarg_0);
break;
case 1:
il.Emit(OpCodes.Ldarg_1);
break;
case 2:
il.Emit(OpCodes.Ldarg_2);
break;
case 3:
il.Emit(OpCodes.Ldarg_3);
break;
default:
il.Emit(OpCodes.Ldarg_S, argumentIndex);
break;
}
if (underlyingParameterType.IsValueType && delegateParameterType == typeof(object))
il.Emit(OpCodes.Unbox_Any, underlyingParameterType);
else if (underlyingParameterType != delegateParameterType)
il.Emit(OpCodes.Castclass, underlyingParameterType);
}
if (callOpCode == OpCodeValue.Call || methodInfo.IsStatic)
{
// non-virtual call (e.g. static method, or method override calling overriden implementation)
il.Emit(OpCodes.Call, methodInfo);
}
else if (callOpCode == OpCodeValue.Callvirt)
{
// Note: C# compiler uses CALLVIRT for non-virtual
// instance methods to get the cheap null check
il.Emit(OpCodes.Callvirt, methodInfo);
}
else
throw new NotSupportedException($"OpCode {callOpCode} not supported when calling a method.");
if (methodInfo.ReturnType.IsValueType && !returnType.IsValueType)
il.Emit(OpCodes.Box, methodInfo.ReturnType);
else if (methodInfo.ReturnType.IsValueType && returnType.IsValueType && methodInfo.ReturnType != returnType)
{
throw new ArgumentException(
$"Cannot convert the target method's return type {methodInfo.ReturnType.FullName} (valuetype) to the delegate method's return type {returnType.FullName} (valuetype)");
}
else if (!methodInfo.ReturnType.IsValueType && returnType.IsValueType)
{
throw new ArgumentException(
$"Cannot reliably convert the target method's return type {methodInfo.ReturnType.FullName} (reference type) to the delegate method's return type {returnType.FullName} (value type)");
}
else if (!methodInfo.ReturnType.IsValueType && !returnType.IsValueType && methodInfo.ReturnType != returnType)
il.Emit(OpCodes.Castclass, returnType);
il.Emit(OpCodes.Ret);
return (TDelegate)dynamicMethod.CreateDelegate(delegateType);
}