public static TDelegate GetOrCreateMethodCallDelegate()

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