#if REFLECTION_EMIT_COMPILER using System.Reflection; using System; namespace FastReport.Code.Expressions { internal static class CustomMethodSearcher { public static MethodInfo Search(Type onType, string methodName, Type[] requiredArguments) { // TODO: remove this default logic to work only with new searcher MethodInfo methodInfo = onType.GetMethod(methodName, requiredArguments); if (methodInfo == null) { // New searcher // TODO: CACHE // ['{methodName}_{onType}_{requiredArguments}'] = methodInfo MethodInfoExt candidate = MethodInfoExt.Empty; // searching compatible methods foreach (var method in onType.GetMethods()) { if (method.Name == methodName) { if (ArgumentsAreEqual(requiredArguments, method, out MethodInfoExt methodInfoExt)) { // comparison with a more suitable method if (candidate.CompareTo(methodInfoExt) > 0) candidate = methodInfoExt; } } } methodInfo = candidate.Method; } return methodInfo; } private readonly struct MethodInfoExt : IComparable { public readonly MethodInfo Method; public readonly int ParamLength; public readonly bool HasParamArray; public static readonly MethodInfoExt Empty = new MethodInfoExt(null, int.MaxValue, true); public MethodInfoExt(MethodInfo method, int paramLength, bool hasParamArray) { Method = method; ParamLength = paramLength; HasParamArray = hasParamArray; } public int CompareTo(MethodInfoExt other) { const int ItsGreater = -1; const int ItsLess = 1; if (!HasParamArray && other.HasParamArray) return ItsGreater; if (HasParamArray && !other.HasParamArray) return ItsLess; var otherParamLength = other.ParamLength; if (!HasParamArray) { if (ParamLength > otherParamLength) return ItsLess; if (ParamLength < otherParamLength) return ItsGreater; } else { if (ParamLength < otherParamLength) return ItsLess; if (ParamLength > otherParamLength) return ItsGreater; } return 0; } } private static bool ArgumentsAreEqual(Type[] requiredArguments, MethodInfo foundMethod, out MethodInfoExt methodInfoExt) { ParameterInfo[] parameters = foundMethod.GetParameters(); methodInfoExt = MethodInfoExt.Empty; // if argumentTypes = Empty if (requiredArguments.Length == 0) { if (parameters.Length == 0) { methodInfoExt = new MethodInfoExt( method: foundMethod, paramLength: parameters.Length, hasParamArray: false); return true; } var parameter = parameters[0]; return parameter.IsOptional(); } // if parameters = Empty if (parameters.Length == 0) return false; bool hasParamArray = false; for (int argIndex = 0, paramIndex = 0; ;) { var argument = requiredArguments[argIndex]; var parameter = parameters[paramIndex]; if (!TypeCompatible(argument, parameter)) return false; argIndex++; // parameter isn't 'params []' if (!parameter.IsParamArray()) paramIndex++; else hasParamArray = true; if (argIndex < requiredArguments.Length) { // if params finished, but arguments aren't if (paramIndex >= parameters.Length) return false; } else { // arguments are over, check the rest of the parameters while (parameters.Length - paramIndex > 0) { parameter = parameters[paramIndex]; if (parameter.IsParamArray()) hasParamArray = true; if (!parameter.IsOptional()) return false; paramIndex++; } methodInfoExt = new MethodInfoExt( method: foundMethod, paramLength: parameters.Length, hasParamArray: hasParamArray); return true; } } } private static bool TypeCompatible(Type type1, ParameterInfo parameter) { Type paramType = parameter.ParameterType; if (TypeCompatible(type1, paramType)) return true; // Is it `param T[] args` ? if (parameter.IsParamArray()) { var elementType = paramType.GetElementType(); if (TypeCompatible(type1, elementType)) return true; } return false; } private static bool TypeCompatible(Type type1, Type type2) { if (type1 == type2) return true; if (type2.IsAssignableFrom(type1)) return true; return false; } } internal static class ParameterInfoHelpers { internal static bool IsParamArray(this ParameterInfo parameter) { return parameter.GetCustomAttribute(typeof(ParamArrayAttribute), false) != null; } internal static bool IsOptional(this ParameterInfo parameter) { // parameter is 'params []' if (parameter.IsParamArray()) return true; // parameter has default value if (parameter.HasDefaultValue) // TODO: IsOptional ? return true; return false; } } } #endif