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