private static MethodInfo SelectTargetMethod()

in src/profiler/Elastic.Apm.Profiler.Managed/DuckTyping/DuckType.Methods.cs [492:719]


		private static MethodInfo SelectTargetMethod(Type targetType, MethodInfo proxyMethod, ParameterInfo[] proxyMethodParameters,
			Type[] proxyMethodParametersTypes
		)
		{
			var proxyMethodDuckAttribute = proxyMethod.GetCustomAttribute<DuckAttribute>(true) ?? new DuckAttribute();
			proxyMethodDuckAttribute.Name ??= proxyMethod.Name;

			MethodInfo targetMethod = null;

			// Check if the duck attribute has the parameter type names to use for selecting the target method, in case of not found an exception is thrown.
			if (proxyMethodDuckAttribute.ParameterTypeNames != null)
			{
				var parameterTypes = proxyMethodDuckAttribute.ParameterTypeNames.Select(pName => Type.GetType(pName, true)).ToArray();
				targetMethod = targetType.GetMethod(proxyMethodDuckAttribute.Name, proxyMethodDuckAttribute.BindingFlags, null, parameterTypes, null);
				if (targetMethod is null)
					DuckTypeTargetMethodNotFoundException.Throw(proxyMethod);

				return targetMethod;
			}

			// If the duck attribute doesn't specify the parameters to use, we do the best effor to find a target method without any ambiguity.

			// First we try with the current proxy parameter types
			targetMethod = targetType.GetMethod(proxyMethodDuckAttribute.Name, proxyMethodDuckAttribute.BindingFlags, null,
				proxyMethodParametersTypes, null);
			if (targetMethod != null)
				return targetMethod;

			// If the method wasn't found could be because a DuckType interface is being use in the parameters or in the return value.
			// Also this can happen if the proxy parameters type uses a base object (ex: System.Object) instead the type.
			// In this case we try to find a method that we can match, in case of ambiguity (> 1 method found) we throw an exception.

			var allTargetMethods = targetType.GetMethods(DuckAttribute.DefaultFlags);
			foreach (var candidateMethod in allTargetMethods)
			{
				var name = proxyMethodDuckAttribute.Name;
				var useRelaxedNameComparison = false;

				// If there is an explicit interface type name we add it to the name
				if (!string.IsNullOrEmpty(proxyMethodDuckAttribute.ExplicitInterfaceTypeName))
				{
					var interfaceTypeName = proxyMethodDuckAttribute.ExplicitInterfaceTypeName;

					if (interfaceTypeName == "*")
					{
						// If a wildcard is use, then we relax the name comparison so it can be an implicit or explicity implementation
						useRelaxedNameComparison = true;
					}
					else
					{
						// Nested types are separated with a "." on explicit implementation.
						interfaceTypeName = interfaceTypeName.Replace("+", ".");

						name = interfaceTypeName + "." + name;
					}
				}

				// We omit target methods with different names.
				if (candidateMethod.Name != name)
				{
					if (!useRelaxedNameComparison || !candidateMethod.Name.EndsWith("." + name))
						continue;
				}

				// Check if the candidate method is a reverse mapped method
				var reverseMethodAttribute = candidateMethod.GetCustomAttribute<DuckReverseMethodAttribute>(true);
				if (reverseMethodAttribute?.Arguments is not null)
				{
					var arguments = reverseMethodAttribute.Arguments;
					if (arguments.Length != proxyMethodParametersTypes.Length)
						continue;

					var match = true;
					for (var i = 0; i < arguments.Length; i++)
					{
						if (arguments[i] != proxyMethodParametersTypes[i].FullName && arguments[i] != proxyMethodParametersTypes[i].Name)
						{
							match = false;
							break;
						}
					}

					if (match)
						return candidateMethod;
				}

				var candidateParameters = candidateMethod.GetParameters();

				// The proxy must have the same or less parameters than the candidate ( less is due to possible optional parameters in the candidate ).
				if (proxyMethodParameters.Length > candidateParameters.Length)
					continue;

				// We compare the target method candidate parameter by parameter.
				var skip = false;
				for (var i = 0; i < proxyMethodParametersTypes.Length; i++)
				{
					var proxyParam = proxyMethodParameters[i];
					var candidateParam = candidateParameters[i];

					var proxyParamType = proxyParam.ParameterType;
					var candidateParamType = candidateParam.ParameterType;

					// both needs to have the same parameter direction
					if (proxyParam.IsOut != candidateParam.IsOut)
					{
						skip = true;
						break;
					}

					// Both need to have the same element type or byref type signature.
					if (proxyParamType.IsByRef != candidateParamType.IsByRef)
					{
						skip = true;
						break;
					}

					// If the parameters are by ref we unwrap them to have the actual type
					proxyParamType = proxyParamType.IsByRef ? proxyParamType.GetElementType() : proxyParamType;
					candidateParamType = candidateParamType.IsByRef ? candidateParamType.GetElementType() : candidateParamType;

					// We can't compare generic parameters
					if (candidateParamType.IsGenericParameter)
						continue;

					// If the proxy parameter type is a value type (no ducktyping neither a base class) both types must match
					if (proxyParamType.IsValueType && !proxyParamType.IsEnum && proxyParamType != candidateParamType)
					{
						skip = true;
						break;
					}

					// If the proxy parameter is a class and not is an abstract class (only interface and abstract class can be used as ducktype base type)
					if (proxyParamType.IsClass && !proxyParamType.IsAbstract && proxyParamType != typeof(object))
					{
						if (!candidateParamType.IsAssignableFrom(proxyParamType))
						{
							// Check if the parameter type contains generic types before skipping
							if (!candidateParamType.IsGenericType || !proxyParamType.IsGenericType)
							{
								skip = true;
								break;
							}

							// if the string representation of the generic parameter types is not the same we need to analyze the
							// GenericTypeArguments array before skipping it
							if (candidateParamType.ToString() != proxyParamType.ToString())
							{
								if (candidateParamType.GenericTypeArguments.Length != proxyParamType.GenericTypeArguments.Length)
								{
									skip = true;
									break;
								}

								for (var paramIndex = 0; paramIndex < candidateParamType.GenericTypeArguments.Length; paramIndex++)
								{
									var candidateParamTypeGenericType = candidateParamType.GenericTypeArguments[paramIndex];
									var proxyParamTypeGenericType = proxyParamType.GenericTypeArguments[paramIndex];

									// Both need to have the same element type or byref type signature.
									if (proxyParamTypeGenericType.IsByRef != candidateParamTypeGenericType.IsByRef)
									{
										skip = true;
										break;
									}

									// If the parameters are by ref we unwrap them to have the actual type
									proxyParamTypeGenericType = proxyParamTypeGenericType.IsByRef
										? proxyParamTypeGenericType.GetElementType()
										: proxyParamTypeGenericType;
									candidateParamTypeGenericType = candidateParamTypeGenericType.IsByRef
										? candidateParamTypeGenericType.GetElementType()
										: candidateParamTypeGenericType;

									// We can't compare generic parameters
									if (candidateParamTypeGenericType.IsGenericParameter)
										continue;

									// If the proxy parameter type is a value type (no ducktyping neither a base class) both types must match
									if (proxyParamTypeGenericType.IsValueType && !proxyParamTypeGenericType.IsEnum
										&& proxyParamTypeGenericType != candidateParamTypeGenericType)
									{
										skip = true;
										break;
									}

									// If the proxy parameter is a class and not is an abstract class (only interface and abstract class can be used as ducktype base type)
									if (proxyParamTypeGenericType.IsClass && !proxyParamTypeGenericType.IsAbstract
										&& proxyParamTypeGenericType != typeof(object))
									{
										if (!candidateParamTypeGenericType.IsAssignableFrom(proxyParamTypeGenericType))
										{
											skip = true;
											break;
										}
									}
								}

								if (skip)
									break;
							}
						}
					}
				}

				if (skip)
					continue;

				// The target method may have optional parameters with default values so we have to skip those
				for (var i = proxyMethodParametersTypes.Length; i < candidateParameters.Length; i++)
				{
					if (!candidateParameters[i].IsOptional)
					{
						skip = true;
						break;
					}
				}

				if (skip)
					continue;

				if (targetMethod is null)
					targetMethod = candidateMethod;
				else
					DuckTypeTargetMethodAmbiguousMatchException.Throw(proxyMethod, targetMethod, candidateMethod);
			}

			return targetMethod;
		}