From 712240f7fe9a4591ddac4eae8aa5c75bb0e9194c Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 6 Jul 2022 09:45:24 -0700 Subject: [PATCH] DefaultBinder named parameter support skipping default parameters (#71013) --- .../src/System/DefaultBinder.cs | 110 +++++---- .../tests/DefaultBinderTests.cs | 213 ++++++++++++++++++ .../tests/System.Reflection.Tests.csproj | 1 + 3 files changed, 283 insertions(+), 41 deletions(-) create mode 100644 src/libraries/System.Reflection/tests/DefaultBinderTests.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/DefaultBinder.cs b/src/libraries/System.Private.CoreLib/src/System/DefaultBinder.cs index 8c918899d878f..18cb496117691 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DefaultBinder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DefaultBinder.cs @@ -60,7 +60,7 @@ public sealed override MethodBase BindToMethod( if (names == null) { // Default mapping - for (j = 0; j < args.Length; j++) + for (j = 0; j < par.Length; j++) paramOrder[i][j] = j; } else @@ -196,43 +196,54 @@ public sealed override MethodBase BindToMethod( if (pCls.IsByRef) pCls = pCls.GetElementType()!; - // the type is the same - if (pCls == argTypes[paramOrder[i][j]]) - continue; + int index = paramOrder[i][j]; + if (index < args.Length) + { + // the type is the same + if (pCls == argTypes[index]) + continue; - // a default value is available - if (defaultValueBinding && args[paramOrder[i][j]] == Type.Missing) - continue; + // a default value is available + if (defaultValueBinding && args[index] == Type.Missing) + continue; - // the argument was null, so it matches with everything - if (args[paramOrder[i][j]] == null) - continue; + // the argument was null, so it matches with everything + if (args[index] == null) + continue; - // the type is Object, so it will match everything - if (pCls == typeof(object)) - continue; + // the type is Object, so it will match everything + if (pCls == typeof(object)) + continue; - // now do a "classic" type check - if (pCls.IsPrimitive) - { - if (argTypes[paramOrder[i][j]] == null || !CanChangePrimitive(args[paramOrder[i][j]]?.GetType(), pCls)) + // now do a "classic" type check + if (pCls.IsPrimitive) { - break; + if (argTypes[index] == null || !CanChangePrimitive(args[index]!.GetType(), pCls)) + { + break; + } + } + else + { + if (argTypes[index] == null) + continue; + + if (!pCls.IsAssignableFrom(argTypes[index])) + { + if (Marshal.IsBuiltInComSupported && argTypes[index].IsCOMObject) + { + if (pCls.IsInstanceOfType(args[index])) + continue; + } + break; + } } } else { - if (argTypes[paramOrder[i][j]] == null) - continue; - - if (!pCls.IsAssignableFrom(argTypes[paramOrder[i][j]])) + if (defaultValueBinding && par[j].HasDefaultValue) { - if (Marshal.IsBuiltInComSupported && argTypes[paramOrder[i][j]].IsCOMObject) - { - if (pCls.IsInstanceOfType(args[paramOrder[i][j]])) - continue; - } - break; + continue; } } #endregion @@ -313,17 +324,25 @@ public sealed override MethodBase BindToMethod( { object?[] objs = new object[parms.Length]; - for (i = 0; i < args.Length; i++) - objs[i] = args[i]; - - for (; i < parms.Length - 1; i++) - objs[i] = parms[i].DefaultValue; - - if (paramArrayTypes[0] != null) - objs[i] = Array.CreateInstance(paramArrayTypes[0], 0); // create an empty array for the - - else - objs[i] = parms[i].DefaultValue; + for (i = 0; i < parms.Length; i++) + { + int k = paramOrder[0][i]; + if (k < args.Length) + { + objs[i] = args[k]; + } + else + { + if (i == parms.Length - 1 && paramArrayTypes[0] != null) + { + objs[i] = Array.CreateInstance(paramArrayTypes[0], 0); // create an empty array for the + } + else + { + objs[i] = parms[i].DefaultValue; + } + } + } args = objs; } @@ -1138,10 +1157,19 @@ private static void ReorderParams(int[] paramOrder, object?[] vars) { object?[] varsCopy = new object[vars.Length]; for (int i = 0; i < vars.Length; i++) + { varsCopy[i] = vars[i]; + } - for (int i = 0; i < vars.Length; i++) - vars[i] = varsCopy[paramOrder[i]]; + for (int i = 0, j = 0; i < vars.Length; j++) + { + if (paramOrder[j] < vars.Length) + { + vars[i] = varsCopy[paramOrder[j]]; + paramOrder[j] = i; + i++; + } + } } // This method will create the mapping between the Parameters and the underlying diff --git a/src/libraries/System.Reflection/tests/DefaultBinderTests.cs b/src/libraries/System.Reflection/tests/DefaultBinderTests.cs new file mode 100644 index 0000000000000..99018fed4b6a1 --- /dev/null +++ b/src/libraries/System.Reflection/tests/DefaultBinderTests.cs @@ -0,0 +1,213 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Reflection.Tests +{ + public class DefaultBinderTests + { + private const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; + private const BindingFlags invokeFlags = flags | BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding; + private static Binder binder = Type.DefaultBinder; + + [Fact] + public static void DefaultBinderAllUnspecifiedParametersHasDefaultsTest() + { + MethodInfo[] methods = typeof(Sample).GetMethods(); + object[] methodArgs = new object[] { "value" }; + MethodBase method = binder.BindToMethod(flags, methods, ref methodArgs, null, null, new[] { "par1" }, out var _); + Assert.NotNull(method); + Assert.Equal("MethodMoreParameters", method.Name); + } + + [Fact] + public static void DefaultBinderNamedParametersInOrderTest() + { + MethodInfo[] methods = typeof(Sample).GetMethods(); + object[] methodArgs = new object[] { "value", 1 }; + MethodBase method = binder.BindToMethod(flags, methods, ref methodArgs, null, null, new[] { "param1", "param2" }, out var _); + Assert.NotNull(method); + Assert.Equal("SampleMethod", method.Name); + } + + [Fact] + public static void DefaultBinderNamedParametersOutOrderTest() + { + MethodInfo[] methods = typeof(Sample).GetMethods(); + object[] methodArgs = new object[] { 1, "value" }; + MethodBase method = binder.BindToMethod(flags, methods, ref methodArgs, null, null, new[] { "param2", "param1" }, out var _); + Assert.NotNull(method); + Assert.Equal("SampleMethod", method.Name); + } + + [Fact] + public static void DefaultBinderNamedParametersSkippedOrderTest() + { + MethodInfo[] methods = typeof(Sample).GetMethods(); + object[] methodArgs = new object[] { "value", true }; + MethodBase method = binder.BindToMethod(flags, methods, ref methodArgs, null, null, new[] { "param1", "param3" }, out var _); + Assert.NotNull(method); + Assert.Equal("SampleMethod", method.Name); + } + + [Fact] + public static void DefaultBinderNamedParametersMissingRequiredParameterThrowsMissingMethodException() + { + MethodInfo[] methods = new MethodInfo[] { typeof(Sample).GetMethod("NoDefaultParameterMethod") }; + object[] methodArgs = new object[] { "value", 1 }; + Assert.Throws(() => binder.BindToMethod(flags, methods, ref methodArgs, null, null, new[] { "param1", "param2" }, out var _)); + } + + [Fact] + public static void DefaultBinderNamedParametersMixedOrderNoDefaults () + { + MethodInfo[] methods = typeof(Sample).GetMethods(); + object[] methodArgs = new object[] { true, "value", 3.14, 1 }; + MethodBase method = binder.BindToMethod(flags, methods, ref methodArgs, null, null, new[] { "param3", "param1", "param4", "param2" }, out var _); + Assert.NotNull(method); + Assert.Equal("NoDefaultParameterMethod", method.Name); + } + + [Fact] + public static void DefaultBinderNoNamedParametersInOrderNoDefaults() + { + MethodInfo[] methods = typeof(Sample).GetMethods(); + object[] methodArgs = new object[] { "value", 1, true, 3.14 }; + MethodBase method = binder.BindToMethod(flags, methods, ref methodArgs, null, null, null, out var _); + Assert.NotNull(method); + Assert.Equal("NoDefaultParameterMethod", method.Name); + } + + [Fact] + public static void DefaultBinderNoNamedParametersNoArgumentsAllDefaults() + { + MethodInfo[] methods = typeof(Test).GetMethods(); + object[] methodArgs = new object[] { null, null }; + MethodBase method = binder.BindToMethod(flags, methods, ref methodArgs, null, null, null, out var _); + Assert.NotNull(method); + Assert.Equal("SampleMethod", method.Name); + } + + [Fact] + public static void DefaultBinderNoNamedParametersOutOrderThrows() + { + MethodInfo[] methods = typeof(Sample).GetMethods(); + object[] methodArgs = new object[] { true, "value", 3.14, 1 }; + Assert.Throws(() => binder.BindToMethod(flags, methods, ref methodArgs, null, null, null, out var _)); + } + + [Fact] + public static void DefaultBinderNamedParametersSkippedAndOutOfOrderTest() + { + MethodInfo[] methods = typeof(Sample).GetMethods(); + object[] methodArgs = new object[] { 8, "value" }; + MethodBase method = binder.BindToMethod(flags, methods, ref methodArgs, null, null, new[] { "par5", "par1" }, out var _); + Assert.NotNull(method); + Assert.Equal("MethodMoreParameters", method.Name); + } + + [Fact] + public static void InvokeWithNamedParameters1st2ndTest() + { + Type t = typeof(Sample); + object instance = Activator.CreateInstance(t); + object[] methodArgs = new object[] { "value", 3 }; + string[] paramNames = new string[] { "param1", "param2" }; + + int result = (int)t.InvokeMember("SampleMethod", invokeFlags, null, instance, methodArgs, null, null, paramNames); + Assert.Equal(3, result); + } + + [Fact] + public static void InvokeWithNamedParameters1st3rd() + { + Type t = typeof(Sample); + object instance = Activator.CreateInstance(t); + object[] methodArgs = new object[] { "value", true }; + string[] paramNames = new string[] { "param1", "param3" }; + + int result = (int)t.InvokeMember("SampleMethod", invokeFlags, null, instance, methodArgs, null, null, paramNames); + Assert.Equal(7, result); + } + + [Fact] + public static void InvokeWithNamedParameters1st4th() + { + Type t = typeof(Sample); + object instance = Activator.CreateInstance(t); + object[] methodArgs = new object[] { "value", true }; + string[] paramNames = new string[] { "param1", "param4" }; + + int result = (int)t.InvokeMember("AnotherMethod", invokeFlags, null, instance, methodArgs, null, null, paramNames); + Assert.Equal(7, result); + } + + [Fact] + public static void InvokeWithNamedParameters4th1st() + { + Type t = typeof(Sample); + object instance = Activator.CreateInstance(t); + object[] methodArgs = new object[] { true, "value" }; + string[] paramNames = new string[] { "param4", "param1" }; + + int result = (int)t.InvokeMember("AnotherMethod", invokeFlags, null, instance, methodArgs, null, null, paramNames); + Assert.Equal(7, result); + } + + [Fact] + public static void InvokeWithNamedParametersOutOfOrder() + { + Type t = typeof(Sample); + object instance = Activator.CreateInstance(t); + object[] methodArgs = new object[] { 3, "value2", true, "value" }; + string[] paramNames = new string[] { "param3", "param2", "param4", "param1" }; + + int result = (int)t.InvokeMember("AnotherMethod", invokeFlags, null, instance, methodArgs, null, null, paramNames); + Assert.Equal(8, result); + } + + public class Test + { + public void TestMethod(int param1) { } + public void SampleMethod(int param2 = 2, bool param3 = false) { } + } + + public class Sample + { + public static int SampleMethod(int param1 = 2) + { + return 1; + } + + public static int SampleMethod(int param2 = 2, bool param3 = false) + { + return 1; + } + + public void NoDefaultParameterMethod(string param1, int param2, bool param3, double param4) { } + + public static int SampleMethod(string param1, int param2 = 2, bool param3 = false) + { + if (param3) + { + return param2 + param1.Length; + } + + return param2; + } + + public int AnotherMethod(string param1, string param2 = "", int param3 = 2, bool param4 = false) + { + if (param4) + { + return param3 + param1.Length; + } + + return param3; + } + + public void MethodMoreParameters(string par1, string par2 = "", int par3 = 2, bool par4 = false, short par5 = 1) { } + } + } +} diff --git a/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj b/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj index d423ed1b8d26f..62f1aa5edd99a 100644 --- a/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj +++ b/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj @@ -26,6 +26,7 @@ +