From 95a4979ba4fe8b4b7ec259087ff948613a24c408 Mon Sep 17 00:00:00 2001 From: Alexey Skalozub Date: Wed, 31 Aug 2016 19:38:53 +0300 Subject: [PATCH 1/4] Advanced constructor parameter binding on .NET Core & portable --- Src/Newtonsoft.Json.Tests/JsonConvertTest.cs | 304 ++++++++++++++++++ .../DynamicReflectionDelegateFactory.cs | 14 + .../ExpressionReflectionDelegateFactory.cs | 7 + .../Utilities/TypeExtensions.cs | 272 +++++++++++++--- 4 files changed, 559 insertions(+), 38 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs index e56a551a..7e487e92 100644 --- a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs @@ -1330,6 +1330,310 @@ namespace Newtonsoft.Json.Tests public string One { get; set; } } + + public class OverloadsJsonConverterer : JsonConverter + { + private readonly string _type; + + // constructor with Type argument + + public OverloadsJsonConverterer(Type typeParam) + { + _type = "Type"; + } + + public OverloadsJsonConverterer(object objectParam) + { + _type = string.Format("object({0})", objectParam.GetType().FullName); + } + + // primitive type conversions + + public OverloadsJsonConverterer(byte byteParam) + { + _type = "byte"; + } + + public OverloadsJsonConverterer(short shortParam) + { + _type = "short"; + } + + public OverloadsJsonConverterer(int intParam) + { + _type = "int"; + } + + public OverloadsJsonConverterer(long longParam) + { + _type = "long"; + } + + public OverloadsJsonConverterer(double doubleParam) + { + _type = "double"; + } + + // params argument + + public OverloadsJsonConverterer(params int[] intParams) + { + _type = "int[]"; + } + + public OverloadsJsonConverterer(bool[] intParams) + { + _type = "bool[]"; + } + + // closest type resolution + + public OverloadsJsonConverterer(IEnumerable iEnumerableParam) + { + _type = "IEnumerable"; + } + + public OverloadsJsonConverterer(IList iListParam) + { + _type = "IList"; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(_type); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(int); + } + + } + + public class OverloadWithTypeParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), typeof(int))] + public int Overload { get; set; } + } + + [Test] + public void JsonConverterConstructor_OverloadWithTypeParam() + { + OverloadWithTypeParameter value = new OverloadWithTypeParameter(); + string json = JsonConvert.SerializeObject(value); + + Assert.AreEqual("{\"Overload\":\"Type\"}", json); + } + + public class OverloadWithUnhandledParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), "str")] + public int Overload { get; set; } + } + + [Test] + public void JsonConverterConstructor_OverloadWithUnhandledParam_FallbackToObject() + { + OverloadWithUnhandledParameter value = new OverloadWithUnhandledParameter(); + string json = JsonConvert.SerializeObject(value); + + Assert.AreEqual("{\"Overload\":\"object(System.String)\"}", json); + } + + public class OverloadWithIntParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), 1)] + public int Overload { get; set; } + } + + public class OverloadWithUIntParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), 1U)] + public int Overload { get; set; } + } + + public class OverloadWithLongParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), 1L)] + public int Overload { get; set; } + } + + public class OverloadWithULongParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), 1UL)] + public int Overload { get; set; } + } + + public class OverloadWithShortParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), (short)1)] + public int Overload { get; set; } + } + + public class OverloadWithUShortParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), (ushort)1)] + public int Overload { get; set; } + } + + public class OverloadWithSByteParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), (sbyte)1)] + public int Overload { get; set; } + } + + public class OverloadWithByteParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), (byte)1)] + public int Overload { get; set; } + } + + public class OverloadWithCharParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), 'a')] + public int Overload { get; set; } + } + + public class OverloadWithBoolParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), true)] + public int Overload { get; set; } + } + + public class OverloadWithFloatParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), 1.5f)] + public int Overload { get; set; } + } + + public class OverloadWithDoubleParameter + { + [JsonConverter(typeof(OverloadsJsonConverterer), 1.5)] + public int Overload { get; set; } + } + + [Test] + public void JsonConverterConstructor_OverloadsWithPrimitiveParams() + { + { + OverloadWithIntParameter value = new OverloadWithIntParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"int\"}", json); + } + + { + // uint -> long + OverloadWithUIntParameter value = new OverloadWithUIntParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"long\"}", json); + } + + { + OverloadWithLongParameter value = new OverloadWithLongParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"long\"}", json); + } + + { + // ulong -> double + OverloadWithULongParameter value = new OverloadWithULongParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"double\"}", json); + } + + { + OverloadWithShortParameter value = new OverloadWithShortParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"short\"}", json); + } + + { + // ushort -> int + OverloadWithUShortParameter value = new OverloadWithUShortParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"int\"}", json); + } + + { + // sbyte -> short + OverloadWithSByteParameter value = new OverloadWithSByteParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"short\"}", json); + } + + { + OverloadWithByteParameter value = new OverloadWithByteParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"byte\"}", json); + } + + { + // char -> int + OverloadWithCharParameter value = new OverloadWithCharParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"int\"}", json); + } + + { + // bool -> (object)bool + OverloadWithBoolParameter value = new OverloadWithBoolParameter(); + var json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"object(System.Boolean)\"}", json); + } + + { + // float -> double + OverloadWithFloatParameter value = new OverloadWithFloatParameter(); + string json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"double\"}", json); + } + + { + OverloadWithDoubleParameter value = new OverloadWithDoubleParameter(); + var json = JsonConvert.SerializeObject(value); + Assert.AreEqual("{\"Overload\":\"double\"}", json); + } + } + + public class OverloadWithArrayParameters + { + [JsonConverter(typeof(OverloadsJsonConverterer), new int[] { 1, 2, 3 })] + public int WithParams { get; set; } + + [JsonConverter(typeof(OverloadsJsonConverterer), new bool[] { true, false })] + public int WithoutParams { get; set; } + } + + [Test] + public void JsonConverterConstructor_OverloadsWithArrayParams() + { + OverloadWithArrayParameters value = new OverloadWithArrayParameters(); + string json = JsonConvert.SerializeObject(value); + + Assert.AreEqual("{\"WithParams\":\"int[]\",\"WithoutParams\":\"bool[]\"}", json); + } + + public class OverloadWithBaseType + { + [JsonConverter(typeof(OverloadsJsonConverterer), new object[] { new string[] { "a", "b", "c" } })] + public int Overload { get; set; } + } + + [Test] + public void JsonConverterConstructor_OverloadsWithBaseTypes() + { + OverloadWithBaseType value = new OverloadWithBaseType(); + string json = JsonConvert.SerializeObject(value); + + Assert.AreEqual("{\"Overload\":\"IList\"}", json); + } + + [Test] public void CustomDoubleRounding() { diff --git a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs index 43ee712f..f55c8252 100644 --- a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs @@ -163,6 +163,20 @@ namespace Newtonsoft.Json.Utilities // parameter has value, get value from array again and unbox generator.MarkLabel(skipSettingDefault); generator.PushArrayInstance(argsIndex, i); + + if (parameterType.IsPrimitive()) + { + // for primitive types we need to call Convert.ChangeType() to correctly + // convert value from other compatible primitive types (e.g. short -> int) + + generator.Emit(OpCodes.Ldtoken, parameterType); + generator.Emit(OpCodes.Call, typeof(Type).GetMethod( + "GetTypeFromHandle", new[] { typeof(RuntimeTypeHandle) })); + + generator.Emit(OpCodes.Call, typeof(Convert).GetMethod( + "ChangeType", new[] { typeof(object), typeof(Type) })); + } + generator.UnboxIfNeeded(parameterType); // parameter finished, we out! diff --git a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs index 02042626..b6235e1a 100644 --- a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs @@ -360,6 +360,13 @@ namespace Newtonsoft.Json.Utilities return expression; } + if (targetType.IsPrimitive()) + { + expression = Expression.Call( + typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) }), + expression, Expression.Constant(targetType)); + } + return Expression.Convert(expression, targetType); } } diff --git a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs index ee7e0924..79878c90 100644 --- a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs +++ b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs @@ -273,6 +273,226 @@ namespace Newtonsoft.Json.Utilities } #if !DOTNET + + 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) + }; + + private static readonly int[] WideningMasks = new int[] + { + 0x0001, 0x0FE2, 0x0D54, 0x0FFA, + 0x0D50, 0x0FE2, 0x0D40, 0x0F80, + 0x0D00, 0x0E00, 0x0C00, 0x0800 + }; + + internal static bool CanConvertPrimitive(Type from, Type to) + { + if (from == to) return true; + + int fromMask = 0, toMask = 0; + + for (int i = 0; i < PrimitiveTypes.Length && (fromMask == 0 || toMask == 0); i++) + { + if (PrimitiveTypes[i] == from) + { + fromMask = WideningMasks[i]; + } + else if (PrimitiveTypes[i] == to) + { + toMask = 1 << i; + } + } + + return (fromMask & toMask) != 0; + } + + private static bool FilterParameters(ParameterInfo[] parameters, IList types, bool enableParamArray) + { + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + if (types == null) + throw new ArgumentNullException(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 && + parameters[parameters.Length - 1].ParameterType.IsArray && + parameters[parameters.Length - 1].IsDefined(typeof(ParamArrayAttribute))) + { + paramArrayType = parameters[parameters.Length - 1].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++) + { + var paramType = (paramArrayType != null && i >= parameters.Length - 1) ? paramArrayType : parameters[i].ParameterType; + + // exact match with provided type + if (paramType == types[i]) continue; + + // parameter of type object matches anything + if (paramType == typeof(object)) continue; + + if (paramType.IsPrimitive()) + { + // primitive parameter can only be assigned from compatible primitive type + if (!types[i].IsPrimitive() || !CanConvertPrimitive(types[i], paramType)) + return false; + } + else + { + if (!paramType.IsAssignableFrom(types[i])) + return false; + } + } + + return true; + } + + private class ParametersMatchComparer : IComparer + { + private readonly IList _types; + private readonly bool _enableParamArray; + + public ParametersMatchComparer(IList types, bool enableParamArray) + { + if (types == null) + throw new ArgumentNullException(nameof(types)); + + _types = types; + _enableParamArray = enableParamArray; + } + + public int Compare(ParameterInfo[] parameters1, ParameterInfo[] parameters2) + { + if (parameters1 == null) + throw new ArgumentNullException(nameof(parameters1)); + if (parameters2 == null) + throw new ArgumentNullException(nameof(parameters2)); + + // parameterless method wins + if (parameters1.Length == 0) return -1; + if (parameters2.Length == 0) return 1; + + Type paramArrayType1 = null, paramArrayType2 = null; + + if (_enableParamArray) + { + if (parameters1[parameters1.Length - 1].ParameterType.IsArray && + parameters1[parameters1.Length - 1].IsDefined(typeof(ParamArrayAttribute))) + { + paramArrayType1 = parameters1[parameters1.Length - 1].ParameterType.GetElementType(); + } + + if (parameters2[parameters2.Length - 1].ParameterType.IsArray && + parameters2[parameters2.Length - 1].IsDefined(typeof(ParamArrayAttribute))) + { + paramArrayType2 = parameters2[parameters2.Length - 1].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++) + { + var type1 = (paramArrayType1 != null && i >= parameters1.Length - 1) ? paramArrayType1 : parameters1[i].ParameterType; + var type2 = (paramArrayType2 != null && i >= parameters2.Length - 1) ? paramArrayType2 : parameters2[i].ParameterType; + + // exact match between parameter types doesn't change score + if (type1 == type2) continue; + + // exact match with source type decides winner immediately + if (type1 == _types[i]) return -1; + if (type2 == _types[i]) return 1; + + var r = ChooseMorePreciseType(type1, type2); + if (r != 0) 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; + } + + } + + private static MethodBase SelectMethod(IEnumerable candidates, BindingFlags bindingFlags, IList types) + { + if (candidates == null) + throw new ArgumentNullException(nameof(candidates)); + if (types == null) + throw new ArgumentNullException(nameof(types)); + + // ParamArrays are not supported by ReflectionDelegateFactory + // They will be treated like ordinary array arguments + const bool enableParamArray = false; + + return candidates + .Where(m => TestAccessibility(m, bindingFlags) && FilterParameters(m.GetParameters(), types, enableParamArray)) + .OrderBy(m => m.GetParameters(), new ParametersMatchComparer(types, enableParamArray)) + .FirstOrDefault(); + } + public static MethodInfo GetMethod(this Type type, string name) { return type.GetMethod(name, DefaultFlags); @@ -295,21 +515,7 @@ namespace Newtonsoft.Json.Utilities public static MethodInfo GetMethod(this Type type, string name, BindingFlags bindingFlags, object placeHolder1, IList parameterTypes, object placeHolder2) { - return type.GetTypeInfo().DeclaredMethods.Where( - m => - { - if (name != null && m.Name != name) - { - return false; - } - - if (!TestAccessibility(m, bindingFlags)) - { - return false; - } - - return m.GetParameters().Select(p => p.ParameterType).SequenceEqual(parameterTypes); - }).SingleOrDefault(); + return (MethodInfo)SelectMethod(type.GetTypeInfo().DeclaredMethods.Where(m => name == null || m.Name == name), bindingFlags, parameterTypes); } public static IEnumerable GetConstructors(this Type type) @@ -319,28 +525,9 @@ namespace Newtonsoft.Json.Utilities public static IEnumerable GetConstructors(this Type type, BindingFlags bindingFlags) { - return type.GetConstructors(bindingFlags, null); + return type.GetTypeInfo().DeclaredConstructors.Where(c => TestAccessibility(c, bindingFlags)); } - - private static IEnumerable GetConstructors(this Type type, BindingFlags bindingFlags, IList parameterTypes) - { - return type.GetTypeInfo().DeclaredConstructors.Where( - c => - { - if (!TestAccessibility(c, bindingFlags)) - { - return false; - } - - if (parameterTypes != null && !c.GetParameters().Select(p => p.ParameterType).SequenceEqual(parameterTypes)) - { - return false; - } - - return true; - }); - } - + public static ConstructorInfo GetConstructor(this Type type, IList parameterTypes) { return type.GetConstructor(DefaultFlags, null, parameterTypes, null); @@ -348,9 +535,9 @@ namespace Newtonsoft.Json.Utilities public static ConstructorInfo GetConstructor(this Type type, BindingFlags bindingFlags, object placeholder1, IList parameterTypes, object placeholder2) { - return type.GetConstructors(bindingFlags, parameterTypes).SingleOrDefault(); + return (ConstructorInfo)SelectMethod(type.GetTypeInfo().DeclaredConstructors, bindingFlags, parameterTypes); } - + public static MemberInfo[] GetMember(this Type type, string member) { return type.GetMemberInternal(member, null, DefaultFlags); @@ -573,6 +760,15 @@ namespace Newtonsoft.Json.Utilities return type.GetTypeInfo().IsValueType; #endif } + + public static bool IsPrimitive(this Type type) + { +#if !(DOTNET || PORTABLE) + return type.IsPrimitive; +#else + return type.GetTypeInfo().IsPrimitive; +#endif + } public static bool AssignableToTypeName(this Type type, string fullTypeName, out Type match) { From deefe2e00632e216eae44777f5ce67ca8c6df53f Mon Sep 17 00:00:00 2001 From: Alexey Skalozub Date: Sat, 10 Sep 2016 20:19:22 +0300 Subject: [PATCH 2/4] Removed Convert.ChangeType calls because of poor performance (DynamicReflectionDelegateFactory now uses IConvertible.ToTypeName, while ExpressionReflectionDelegateFactory uses Convert.ToTypeName). Method binder moved to a separate file and documented, + code style fixes. --- .../Newtonsoft.Json.Portable.csproj | 1 + .../Newtonsoft.Json.Portable40.csproj | 1 + Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 1 + .../DynamicReflectionDelegateFactory.cs | 64 +++- .../ExpressionReflectionDelegateFactory.cs | 10 +- Src/Newtonsoft.Json/Utilities/MethodBinder.cs | 333 ++++++++++++++++++ .../Utilities/TypeExtensions.cs | 223 +----------- 7 files changed, 391 insertions(+), 242 deletions(-) create mode 100644 Src/Newtonsoft.Json/Utilities/MethodBinder.cs diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj index 56f6b0ce..d65fa6e2 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj @@ -233,6 +233,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj index 55c3494a..8baace34 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj @@ -238,6 +238,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 1055878b..3da48f52 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -227,6 +227,7 @@ + diff --git a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs index f55c8252..082bbaba 100644 --- a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs @@ -55,7 +55,7 @@ namespace Newtonsoft.Json.Utilities ILGenerator generator = dynamicMethod.GetILGenerator(); GenerateCreateMethodCallIL(method, generator, 0); - + return (ObjectConstructor)dynamicMethod.CreateDelegate(typeof(ObjectConstructor)); } @@ -90,7 +90,9 @@ namespace Newtonsoft.Json.Utilities generator.PushInstance(method.DeclaringType); } - int localVariableCount = 0; + LocalBuilder localConvertible = generator.DeclareLocal(typeof(IConvertible)); + LocalBuilder localObject = generator.DeclareLocal(typeof(object)); + int localVariableCount = 2; for (int i = 0; i < args.Length; i++) { @@ -125,7 +127,7 @@ namespace Newtonsoft.Json.Utilities generator.MarkLabel(skipSettingDefault); generator.PushArrayInstance(argsIndex, i); generator.UnboxIfNeeded(parameterType); - generator.Emit(OpCodes.Stloc, localVariableCount); + generator.Emit(OpCodes.Stloc_S, localVariable); // parameter finished, we out! generator.MarkLabel(finishedProcessingParameter); @@ -133,7 +135,7 @@ namespace Newtonsoft.Json.Utilities else { generator.UnboxIfNeeded(parameterType); - generator.Emit(OpCodes.Stloc, localVariableCount); + generator.Emit(OpCodes.Stloc_S, localVariable); } } @@ -142,8 +144,9 @@ namespace Newtonsoft.Json.Utilities localVariableCount++; } else if (parameterType.IsValueType()) - { + { generator.PushArrayInstance(argsIndex, i); + generator.Emit(OpCodes.Stloc_S, localObject); // have to check that value type parameters aren't null // otherwise they will error when unboxed @@ -151,34 +154,59 @@ namespace Newtonsoft.Json.Utilities Label finishedProcessingParameter = generator.DefineLabel(); // check if parameter is not null + generator.Emit(OpCodes.Ldloc_S, localObject); generator.Emit(OpCodes.Brtrue_S, skipSettingDefault); // parameter has no value, initialize to default LocalBuilder localVariable = generator.DeclareLocal(parameterType); generator.Emit(OpCodes.Ldloca_S, localVariable); generator.Emit(OpCodes.Initobj, parameterType); - generator.Emit(OpCodes.Ldloc, localVariableCount); + generator.Emit(OpCodes.Ldloc_S, localVariable); generator.Emit(OpCodes.Br_S, finishedProcessingParameter); - // parameter has value, get value from array again and unbox + // argument has value, try to convert it to parameter type generator.MarkLabel(skipSettingDefault); - generator.PushArrayInstance(argsIndex, i); - + if (parameterType.IsPrimitive()) { - // for primitive types we need to call Convert.ChangeType() to correctly - // convert value from other compatible primitive types (e.g. short -> int) + // for primitive types we need to handle type widening (e.g. short -> int) - generator.Emit(OpCodes.Ldtoken, parameterType); - generator.Emit(OpCodes.Call, typeof(Type).GetMethod( - "GetTypeFromHandle", new[] { typeof(RuntimeTypeHandle) })); - - generator.Emit(OpCodes.Call, typeof(Convert).GetMethod( - "ChangeType", new[] { typeof(object), typeof(Type) })); + MethodInfo toParameterTypeMethod = typeof(IConvertible) + .GetMethod("To" + parameterType.Name, new[] { typeof(IFormatProvider) }); + + if (toParameterTypeMethod != null) + { + Label skipConvertible = generator.DefineLabel(); + + // check if argument type is an exact match for parameter type + // in this case we may use cheap unboxing instead + generator.Emit(OpCodes.Ldloc_S, localObject); + generator.Emit(OpCodes.Isinst, parameterType); + generator.Emit(OpCodes.Brtrue_S, skipConvertible); + + // types don't match, check if argument implements IConvertible + generator.Emit(OpCodes.Ldloc_S, localObject); + generator.Emit(OpCodes.Isinst, typeof(IConvertible)); + generator.Emit(OpCodes.Stloc_S, localConvertible); + generator.Emit(OpCodes.Ldloc_S, localConvertible); + generator.Emit(OpCodes.Brfalse_S, skipConvertible); + + // convert argument to parameter type + generator.Emit(OpCodes.Ldloc_S, localConvertible); + generator.Emit(OpCodes.Ldnull); + generator.Emit(OpCodes.Callvirt, toParameterTypeMethod); + generator.Emit(OpCodes.Br_S, finishedProcessingParameter); + + generator.MarkLabel(skipConvertible); + } } - generator.UnboxIfNeeded(parameterType); + // we got here because either argument type matches parameter (conversion will succeed), + // or argument type doesn't match parameter, but we're out of options (conversion will fail) + generator.Emit(OpCodes.Ldloc_S, localObject); + generator.UnboxIfNeeded(parameterType); + // parameter finished, we out! generator.MarkLabel(finishedProcessingParameter); localVariableCount++; diff --git a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs index b6235e1a..f8fc8082 100644 --- a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs @@ -362,9 +362,13 @@ namespace Newtonsoft.Json.Utilities if (targetType.IsPrimitive()) { - expression = Expression.Call( - typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) }), - expression, Expression.Constant(targetType)); + MethodInfo toTargetTypeMethod = typeof(Convert) + .GetMethod("To" + targetType.Name, new[] { typeof(object) }); + + if (toTargetTypeMethod != null) + { + expression = Expression.Call(toTargetTypeMethod, expression); + } } return Expression.Convert(expression, targetType); diff --git a/Src/Newtonsoft.Json/Utilities/MethodBinder.cs b/Src/Newtonsoft.Json/Utilities/MethodBinder.cs new file mode 100644 index 00000000..202321da --- /dev/null +++ b/Src/Newtonsoft.Json/Utilities/MethodBinder.cs @@ -0,0 +1,333 @@ +namespace Newtonsoft.Json.Utilities +{ +#if PORTABLE + + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + 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 + internal static bool CanConvertPrimitive(Type from, Type to) + { + if (from == to) + { + // same type + return true; + } + + int fromMask = 0, toMask = 0; + + for (int i = 0; i < PrimitiveTypes.Length && (fromMask == 0 || toMask == 0); i++) + { + if (PrimitiveTypes[i] == from) + { + fromMask = WideningMasks[i]; + } + else if (PrimitiveTypes[i] == to) + { + toMask = 1 << i; + } + } + + 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 ParamArray + /// True if method can be called with given arguments, false otherwise + private static bool FilterParameters(ParameterInfo[] parameters, IList types, bool enableParamArray) + { + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + if (types == null) + { + throw new ArgumentNullException(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) + { + if (types == null) + { + throw new ArgumentNullException(nameof(types)); + } + + _types = types; + _enableParamArray = enableParamArray; + } + + public int Compare(ParameterInfo[] parameters1, ParameterInfo[] parameters2) + { + if (parameters1 == null) + { + throw new ArgumentNullException(nameof(parameters1)); + } + if (parameters2 == null) + { + throw new ArgumentNullException(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; + } + + } + + /// + /// Retunrs 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 + { + if (candidates == null) + { + throw new ArgumentNullException(nameof(candidates)); + } + if (types == null) + { + throw new ArgumentNullException(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 +} diff --git a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs index 79878c90..f4554dd0 100644 --- a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs +++ b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs @@ -274,225 +274,6 @@ namespace Newtonsoft.Json.Utilities #if !DOTNET - 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) - }; - - private static readonly int[] WideningMasks = new int[] - { - 0x0001, 0x0FE2, 0x0D54, 0x0FFA, - 0x0D50, 0x0FE2, 0x0D40, 0x0F80, - 0x0D00, 0x0E00, 0x0C00, 0x0800 - }; - - internal static bool CanConvertPrimitive(Type from, Type to) - { - if (from == to) return true; - - int fromMask = 0, toMask = 0; - - for (int i = 0; i < PrimitiveTypes.Length && (fromMask == 0 || toMask == 0); i++) - { - if (PrimitiveTypes[i] == from) - { - fromMask = WideningMasks[i]; - } - else if (PrimitiveTypes[i] == to) - { - toMask = 1 << i; - } - } - - return (fromMask & toMask) != 0; - } - - private static bool FilterParameters(ParameterInfo[] parameters, IList types, bool enableParamArray) - { - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - if (types == null) - throw new ArgumentNullException(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 && - parameters[parameters.Length - 1].ParameterType.IsArray && - parameters[parameters.Length - 1].IsDefined(typeof(ParamArrayAttribute))) - { - paramArrayType = parameters[parameters.Length - 1].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++) - { - var paramType = (paramArrayType != null && i >= parameters.Length - 1) ? paramArrayType : parameters[i].ParameterType; - - // exact match with provided type - if (paramType == types[i]) continue; - - // parameter of type object matches anything - if (paramType == typeof(object)) continue; - - if (paramType.IsPrimitive()) - { - // primitive parameter can only be assigned from compatible primitive type - if (!types[i].IsPrimitive() || !CanConvertPrimitive(types[i], paramType)) - return false; - } - else - { - if (!paramType.IsAssignableFrom(types[i])) - return false; - } - } - - return true; - } - - private class ParametersMatchComparer : IComparer - { - private readonly IList _types; - private readonly bool _enableParamArray; - - public ParametersMatchComparer(IList types, bool enableParamArray) - { - if (types == null) - throw new ArgumentNullException(nameof(types)); - - _types = types; - _enableParamArray = enableParamArray; - } - - public int Compare(ParameterInfo[] parameters1, ParameterInfo[] parameters2) - { - if (parameters1 == null) - throw new ArgumentNullException(nameof(parameters1)); - if (parameters2 == null) - throw new ArgumentNullException(nameof(parameters2)); - - // parameterless method wins - if (parameters1.Length == 0) return -1; - if (parameters2.Length == 0) return 1; - - Type paramArrayType1 = null, paramArrayType2 = null; - - if (_enableParamArray) - { - if (parameters1[parameters1.Length - 1].ParameterType.IsArray && - parameters1[parameters1.Length - 1].IsDefined(typeof(ParamArrayAttribute))) - { - paramArrayType1 = parameters1[parameters1.Length - 1].ParameterType.GetElementType(); - } - - if (parameters2[parameters2.Length - 1].ParameterType.IsArray && - parameters2[parameters2.Length - 1].IsDefined(typeof(ParamArrayAttribute))) - { - paramArrayType2 = parameters2[parameters2.Length - 1].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++) - { - var type1 = (paramArrayType1 != null && i >= parameters1.Length - 1) ? paramArrayType1 : parameters1[i].ParameterType; - var type2 = (paramArrayType2 != null && i >= parameters2.Length - 1) ? paramArrayType2 : parameters2[i].ParameterType; - - // exact match between parameter types doesn't change score - if (type1 == type2) continue; - - // exact match with source type decides winner immediately - if (type1 == _types[i]) return -1; - if (type2 == _types[i]) return 1; - - var r = ChooseMorePreciseType(type1, type2); - if (r != 0) 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; - } - - } - - private static MethodBase SelectMethod(IEnumerable candidates, BindingFlags bindingFlags, IList types) - { - if (candidates == null) - throw new ArgumentNullException(nameof(candidates)); - if (types == null) - throw new ArgumentNullException(nameof(types)); - - // ParamArrays are not supported by ReflectionDelegateFactory - // They will be treated like ordinary array arguments - const bool enableParamArray = false; - - return candidates - .Where(m => TestAccessibility(m, bindingFlags) && FilterParameters(m.GetParameters(), types, enableParamArray)) - .OrderBy(m => m.GetParameters(), new ParametersMatchComparer(types, enableParamArray)) - .FirstOrDefault(); - } - public static MethodInfo GetMethod(this Type type, string name) { return type.GetMethod(name, DefaultFlags); @@ -515,7 +296,7 @@ namespace Newtonsoft.Json.Utilities public static MethodInfo GetMethod(this Type type, string name, BindingFlags bindingFlags, object placeHolder1, IList parameterTypes, object placeHolder2) { - return (MethodInfo)SelectMethod(type.GetTypeInfo().DeclaredMethods.Where(m => name == null || m.Name == name), bindingFlags, parameterTypes); + return MethodBinder.SelectMethod(type.GetTypeInfo().DeclaredMethods.Where(m => (name == null || m.Name == name) && TestAccessibility(m, bindingFlags)), parameterTypes); } public static IEnumerable GetConstructors(this Type type) @@ -535,7 +316,7 @@ namespace Newtonsoft.Json.Utilities public static ConstructorInfo GetConstructor(this Type type, BindingFlags bindingFlags, object placeholder1, IList parameterTypes, object placeholder2) { - return (ConstructorInfo)SelectMethod(type.GetTypeInfo().DeclaredConstructors, bindingFlags, parameterTypes); + return MethodBinder.SelectMethod(type.GetConstructors(bindingFlags), parameterTypes); } public static MemberInfo[] GetMember(this Type type, string member) From 7b0331725c0cd35d81558afa1b085fcbd717ac6b Mon Sep 17 00:00:00 2001 From: Alexey Skalozub Date: Sun, 11 Sep 2016 03:35:36 +0300 Subject: [PATCH 3/4] Speed up expression delegates when widening types is not needed --- .../ExpressionReflectionDelegateFactory.cs | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs index f8fc8082..99b93846 100644 --- a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs @@ -105,18 +105,7 @@ namespace Newtonsoft.Json.Utilities Expression paramAccessorExpression = Expression.ArrayIndex(argsParameterExpression, indexExpression); - Expression argExpression; - - if (parameterType.IsValueType()) - { - BinaryExpression ensureValueTypeNotNull = Expression.Coalesce(paramAccessorExpression, Expression.New(parameterType)); - - argExpression = EnsureCastExpression(ensureValueTypeNotNull, parameterType); - } - else - { - argExpression = EnsureCastExpression(paramAccessorExpression, parameterType); - } + Expression argExpression = EnsureCastExpression(paramAccessorExpression, parameterType, !isByRef); if (isByRef) { @@ -349,26 +338,39 @@ namespace Newtonsoft.Json.Utilities Action compiled = (Action)lambdaExpression.Compile(); return compiled; } - - private Expression EnsureCastExpression(Expression expression, Type targetType) + + private Expression EnsureCastExpression(Expression expression, Type targetType, bool allowWidening = false) { Type expressionType = expression.Type; - + // check if a cast or conversion is required if (expressionType == targetType || (!expressionType.IsValueType() && targetType.IsAssignableFrom(expressionType))) { return expression; } - if (targetType.IsPrimitive()) + if (targetType.IsValueType()) { - MethodInfo toTargetTypeMethod = typeof(Convert) - .GetMethod("To" + targetType.Name, new[] { typeof(object) }); + Expression convert = Expression.Unbox(expression, targetType); - if (toTargetTypeMethod != null) + if (allowWidening && targetType.IsPrimitive()) { - expression = Expression.Call(toTargetTypeMethod, expression); + MethodInfo toTargetTypeMethod = typeof(Convert) + .GetMethod("To" + targetType.Name, new[] { typeof(object) }); + + if (toTargetTypeMethod != null) + { + convert = Expression.Condition( + Expression.TypeIs(expression, targetType), + convert, + Expression.Call(toTargetTypeMethod, expression)); + } } + + return Expression.Condition( + Expression.Equal(expression, Expression.Constant(null, typeof(object))), + Expression.Default(targetType), + convert); } return Expression.Convert(expression, targetType); From 04c429a15d51b6e46f2a65a227605671890bb9f6 Mon Sep 17 00:00:00 2001 From: Alexey Skalozub Date: Sun, 11 Sep 2016 19:14:32 +0300 Subject: [PATCH 4/4] Use ValidationUtils.ArgumentNotNull, code cleanup --- Src/Newtonsoft.Json/Utilities/MethodBinder.cs | 48 +++++++------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/Src/Newtonsoft.Json/Utilities/MethodBinder.cs b/Src/Newtonsoft.Json/Utilities/MethodBinder.cs index 202321da..ec9d2281 100644 --- a/Src/Newtonsoft.Json/Utilities/MethodBinder.cs +++ b/Src/Newtonsoft.Json/Utilities/MethodBinder.cs @@ -42,7 +42,7 @@ /// Source primitive type /// Target primitive type /// True if source type can be widened to target type, false otherwise - internal static bool CanConvertPrimitive(Type from, Type to) + private static bool CanConvertPrimitive(Type from, Type to) { if (from == to) { @@ -50,9 +50,10 @@ return true; } - int fromMask = 0, toMask = 0; + int fromMask = 0, + toMask = 0; - for (int i = 0; i < PrimitiveTypes.Length && (fromMask == 0 || toMask == 0); i++) + for (int i = 0; i < PrimitiveTypes.Length; i++) { if (PrimitiveTypes[i] == from) { @@ -62,6 +63,11 @@ { toMask = 1 << i; } + + if (fromMask != 0 && toMask != 0) + { + break; + } } return (fromMask & toMask) != 0; @@ -77,14 +83,9 @@ /// True if method can be called with given arguments, false otherwise private static bool FilterParameters(ParameterInfo[] parameters, IList types, bool enableParamArray) { - if (parameters == null) - { - throw new ArgumentNullException(nameof(parameters)); - } - if (types == null) - { - throw new ArgumentNullException(nameof(types)); - } + ValidationUtils.ArgumentNotNull(parameters, nameof(parameters)); + ValidationUtils.ArgumentNotNull(types, nameof(types)); + if (parameters.Length == 0) { // fast check for parameterless methods @@ -161,10 +162,7 @@ public ParametersMatchComparer(IList types, bool enableParamArray) { - if (types == null) - { - throw new ArgumentNullException(nameof(types)); - } + ValidationUtils.ArgumentNotNull(types, nameof(types)); _types = types; _enableParamArray = enableParamArray; @@ -172,14 +170,8 @@ public int Compare(ParameterInfo[] parameters1, ParameterInfo[] parameters2) { - if (parameters1 == null) - { - throw new ArgumentNullException(nameof(parameters1)); - } - if (parameters2 == null) - { - throw new ArgumentNullException(nameof(parameters2)); - } + ValidationUtils.ArgumentNotNull(parameters1, nameof(parameters1)); + ValidationUtils.ArgumentNotNull(parameters2, nameof(parameters2)); // parameterless method wins if (parameters1.Length == 0) @@ -308,14 +300,8 @@ /// Best method overload, or null if none matched public static TMethod SelectMethod(IEnumerable candidates, IList types) where TMethod : MethodBase { - if (candidates == null) - { - throw new ArgumentNullException(nameof(candidates)); - } - if (types == null) - { - throw new ArgumentNullException(nameof(types)); - } + ValidationUtils.ArgumentNotNull(candidates, nameof(candidates)); + ValidationUtils.ArgumentNotNull(types, nameof(types)); // ParamArrays are not supported by ReflectionDelegateFactory // They will be treated like ordinary array arguments