#region License // Copyright (c) 2007 James Newton-King // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. #endregion using System; using System.Collections.Generic; #if !HAVE_LINQ using LC.Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; #endif using System.Reflection; namespace LC.Newtonsoft.Json.Utilities { #if PORTABLE internal static class MethodBinder { /// /// List of primitive types which can be widened. /// private static readonly Type[] PrimitiveTypes = new Type[] { typeof(bool), typeof(char), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double) }; /// /// Widening masks for primitive types above. /// Index of the value in this array defines a type we're widening, /// while the bits in mask define types it can be widened to (including itself). /// /// For example, value at index 0 defines a bool type, and it only has bit 0 set, /// i.e. bool values can be assigned only to bool. /// private static readonly int[] WideningMasks = new int[] { 0x0001, 0x0FE2, 0x0D54, 0x0FFA, 0x0D50, 0x0FE2, 0x0D40, 0x0F80, 0x0D00, 0x0E00, 0x0C00, 0x0800 }; /// /// Checks if value of primitive type can be /// assigned to parameter of primitive type . /// /// Source primitive type. /// Target primitive type. /// true if source type can be widened to target type, false otherwise. private static bool CanConvertPrimitive(Type from, Type to) { if (from == to) { // same type return true; } int fromMask = 0; int toMask = 0; for (int i = 0; i < PrimitiveTypes.Length; i++) { if (PrimitiveTypes[i] == from) { fromMask = WideningMasks[i]; } else if (PrimitiveTypes[i] == to) { toMask = 1 << i; } if (fromMask != 0 && toMask != 0) { break; } } return (fromMask & toMask) != 0; } /// /// Checks if a set of values with given can be used /// to invoke a method with specified . /// /// Method parameters. /// Argument types. /// Try to pack extra arguments into the last parameter when it is marked up with . /// true if method can be called with given arguments, false otherwise. private static bool FilterParameters(ParameterInfo[] parameters, IList types, bool enableParamArray) { ValidationUtils.ArgumentNotNull(parameters, nameof(parameters)); ValidationUtils.ArgumentNotNull(types, nameof(types)); if (parameters.Length == 0) { // fast check for parameterless methods return types.Count == 0; } if (parameters.Length > types.Count) { // not all declared parameters were specified (optional parameters are not supported) return false; } // check if the last parameter is ParamArray Type? paramArrayType = null; if (enableParamArray) { ParameterInfo lastParam = parameters[parameters.Length - 1]; if (lastParam.ParameterType.IsArray && lastParam.IsDefined(typeof(ParamArrayAttribute))) { paramArrayType = lastParam.ParameterType.GetElementType(); } } if (paramArrayType == null && parameters.Length != types.Count) { // when there's no ParamArray, number of parameters should match return false; } for (int i = 0; i < types.Count; i++) { Type paramType = (paramArrayType != null && i >= parameters.Length - 1) ? paramArrayType : parameters[i].ParameterType; if (paramType == types[i]) { // exact match with provided type continue; } if (paramType == typeof(object)) { // parameter of type object matches anything continue; } if (paramType.IsPrimitive()) { if (!types[i].IsPrimitive() || !CanConvertPrimitive(types[i], paramType)) { // primitive parameter can only be assigned from compatible primitive type return false; } } else { if (!paramType.IsAssignableFrom(types[i])) { return false; } } } return true; } /// /// Compares two sets of parameters to determine /// which one suits better for given argument types. /// private class ParametersMatchComparer : IComparer { private readonly IList _types; private readonly bool _enableParamArray; public ParametersMatchComparer(IList types, bool enableParamArray) { ValidationUtils.ArgumentNotNull(types, nameof(types)); _types = types; _enableParamArray = enableParamArray; } public int Compare(ParameterInfo[] parameters1, ParameterInfo[] parameters2) { ValidationUtils.ArgumentNotNull(parameters1, nameof(parameters1)); ValidationUtils.ArgumentNotNull(parameters2, nameof(parameters2)); // parameterless method wins if (parameters1.Length == 0) { return -1; } if (parameters2.Length == 0) { return 1; } Type? paramArrayType1 = null, paramArrayType2 = null; if (_enableParamArray) { ParameterInfo lastParam1 = parameters1[parameters1.Length - 1]; if (lastParam1.ParameterType.IsArray && lastParam1.IsDefined(typeof(ParamArrayAttribute))) { paramArrayType1 = lastParam1.ParameterType.GetElementType(); } ParameterInfo lastParam2 = parameters2[parameters2.Length - 1]; if (lastParam2.ParameterType.IsArray && lastParam2.IsDefined(typeof(ParamArrayAttribute))) { paramArrayType2 = lastParam2.ParameterType.GetElementType(); } // A method using params always loses to one not using params if (paramArrayType1 != null && paramArrayType2 == null) { return 1; } if (paramArrayType2 != null && paramArrayType1 == null) { return -1; } } for (int i = 0; i < _types.Count; i++) { Type type1 = (paramArrayType1 != null && i >= parameters1.Length - 1) ? paramArrayType1 : parameters1[i].ParameterType; Type type2 = (paramArrayType2 != null && i >= parameters2.Length - 1) ? paramArrayType2 : parameters2[i].ParameterType; if (type1 == type2) { // exact match between parameter types doesn't change score continue; } // exact match with source type decides winner immediately if (type1 == _types[i]) { return -1; } if (type2 == _types[i]) { return 1; } int r = ChooseMorePreciseType(type1, type2); if (r != 0) { // winner decided return r; } } return 0; } private static int ChooseMorePreciseType(Type type1, Type type2) { if (type1.IsByRef || type2.IsByRef) { if (type1.IsByRef && type2.IsByRef) { type1 = type1.GetElementType(); type2 = type2.GetElementType(); } else if (type1.IsByRef) { type1 = type1.GetElementType(); if (type1 == type2) { return 1; } } else { type2 = type2.GetElementType(); if (type2 == type1) { return -1; } } } bool c1FromC2, c2FromC1; if (type1.IsPrimitive() && type2.IsPrimitive()) { c1FromC2 = CanConvertPrimitive(type2, type1); c2FromC1 = CanConvertPrimitive(type1, type2); } else { c1FromC2 = type1.IsAssignableFrom(type2); c2FromC1 = type2.IsAssignableFrom(type1); } if (c1FromC2 == c2FromC1) { return 0; } return c1FromC2 ? 1 : -1; } } /// /// Returns a best method overload for given argument . /// /// List of method candidates. /// Argument types. /// Best method overload, or null if none matched. public static TMethod SelectMethod(IEnumerable candidates, IList types) where TMethod : MethodBase { ValidationUtils.ArgumentNotNull(candidates, nameof(candidates)); ValidationUtils.ArgumentNotNull(types, nameof(types)); // ParamArrays are not supported by ReflectionDelegateFactory // They will be treated like ordinary array arguments const bool enableParamArray = false; return candidates .Where(m => FilterParameters(m.GetParameters(), types, enableParamArray)) .OrderBy(m => m.GetParameters(), new ParametersMatchComparer(types, enableParamArray)) .FirstOrDefault(); } } #endif }