From 9cb617b77f5d0b1b1faa68050cf0fb46da5da935 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sun, 11 Oct 2026 23:59:30 +1100 Subject: [PATCH] Use the binder in the Type.GetMethod polyfill --- src/Polyfill/Polyfill_Type.cs | 76 +++++++++--------- src/Split/net10.0/Polyfill_Type.cs | 1 + src/Split/net11.0/Polyfill_Type.cs | 1 + src/Split/net461/Polyfill_Type.cs | 65 ++++++++-------- src/Split/net462/Polyfill_Type.cs | 65 ++++++++-------- src/Split/net47/Polyfill_Type.cs | 65 ++++++++-------- src/Split/net471/Polyfill_Type.cs | 65 ++++++++-------- src/Split/net472/Polyfill_Type.cs | 65 ++++++++-------- src/Split/net48/Polyfill_Type.cs | 65 ++++++++-------- src/Split/net481/Polyfill_Type.cs | 65 ++++++++-------- src/Split/net5.0/Polyfill_Type.cs | 1 + src/Split/net6.0/Polyfill_Type.cs | 1 + src/Split/net7.0/Polyfill_Type.cs | 1 + src/Split/net8.0/Polyfill_Type.cs | 1 + src/Split/net9.0/Polyfill_Type.cs | 1 + src/Split/netcoreapp2.0/Polyfill_Type.cs | 65 ++++++++-------- src/Split/netcoreapp2.1/Polyfill_Type.cs | 1 + src/Split/netcoreapp2.2/Polyfill_Type.cs | 1 + src/Split/netcoreapp3.0/Polyfill_Type.cs | 1 + src/Split/netcoreapp3.1/Polyfill_Type.cs | 1 + src/Split/netstandard2.0/Polyfill_Type.cs | 65 ++++++++-------- src/Split/netstandard2.1/Polyfill_Type.cs | 1 + src/Split/uap10.0/Polyfill_Type.cs | 65 ++++++++-------- src/Tests/PolyfillTests_Type.cs | 95 +++++++++++++++++++++++ 24 files changed, 478 insertions(+), 355 deletions(-) diff --git a/src/Polyfill/Polyfill_Type.cs b/src/Polyfill/Polyfill_Type.cs index bbf27f5d..6202a279 100644 --- a/src/Polyfill/Polyfill_Type.cs +++ b/src/Polyfill/Polyfill_Type.cs @@ -1,6 +1,7 @@ namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -58,49 +59,54 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + + // Narrow to the candidates by name, generic arity and parameter count, + // then let the binder pick the best match for the argument types. + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - - if (IsMatch(method)) - { - return method; - } + continue; } + + if (method.GetParameters().Length != types.Length) + { + continue; + } + + candidates.Add(method); } - return null; + if (candidates.Count == 0) + { + return null; + } + + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; + } - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } #endif diff --git a/src/Split/net10.0/Polyfill_Type.cs b/src/Split/net10.0/Polyfill_Type.cs index 5fb3adaf..a0e78f38 100644 --- a/src/Split/net10.0/Polyfill_Type.cs +++ b/src/Split/net10.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/net11.0/Polyfill_Type.cs b/src/Split/net11.0/Polyfill_Type.cs index 5fb3adaf..a0e78f38 100644 --- a/src/Split/net11.0/Polyfill_Type.cs +++ b/src/Split/net11.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/net461/Polyfill_Type.cs b/src/Split/net461/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/net461/Polyfill_Type.cs +++ b/src/Split/net461/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Split/net462/Polyfill_Type.cs b/src/Split/net462/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/net462/Polyfill_Type.cs +++ b/src/Split/net462/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Split/net47/Polyfill_Type.cs b/src/Split/net47/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/net47/Polyfill_Type.cs +++ b/src/Split/net47/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Split/net471/Polyfill_Type.cs b/src/Split/net471/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/net471/Polyfill_Type.cs +++ b/src/Split/net471/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Split/net472/Polyfill_Type.cs b/src/Split/net472/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/net472/Polyfill_Type.cs +++ b/src/Split/net472/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Split/net48/Polyfill_Type.cs b/src/Split/net48/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/net48/Polyfill_Type.cs +++ b/src/Split/net48/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Split/net481/Polyfill_Type.cs b/src/Split/net481/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/net481/Polyfill_Type.cs +++ b/src/Split/net481/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Split/net5.0/Polyfill_Type.cs b/src/Split/net5.0/Polyfill_Type.cs index 12f8e3a6..fd050c3b 100644 --- a/src/Split/net5.0/Polyfill_Type.cs +++ b/src/Split/net5.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/net6.0/Polyfill_Type.cs b/src/Split/net6.0/Polyfill_Type.cs index b12f3076..94b92334 100644 --- a/src/Split/net6.0/Polyfill_Type.cs +++ b/src/Split/net6.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/net7.0/Polyfill_Type.cs b/src/Split/net7.0/Polyfill_Type.cs index b12f3076..94b92334 100644 --- a/src/Split/net7.0/Polyfill_Type.cs +++ b/src/Split/net7.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/net8.0/Polyfill_Type.cs b/src/Split/net8.0/Polyfill_Type.cs index b12f3076..94b92334 100644 --- a/src/Split/net8.0/Polyfill_Type.cs +++ b/src/Split/net8.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/net9.0/Polyfill_Type.cs b/src/Split/net9.0/Polyfill_Type.cs index 5fb3adaf..a0e78f38 100644 --- a/src/Split/net9.0/Polyfill_Type.cs +++ b/src/Split/net9.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/netcoreapp2.0/Polyfill_Type.cs b/src/Split/netcoreapp2.0/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/netcoreapp2.0/Polyfill_Type.cs +++ b/src/Split/netcoreapp2.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Split/netcoreapp2.1/Polyfill_Type.cs b/src/Split/netcoreapp2.1/Polyfill_Type.cs index 91339407..ed8cebd7 100644 --- a/src/Split/netcoreapp2.1/Polyfill_Type.cs +++ b/src/Split/netcoreapp2.1/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/netcoreapp2.2/Polyfill_Type.cs b/src/Split/netcoreapp2.2/Polyfill_Type.cs index 91339407..ed8cebd7 100644 --- a/src/Split/netcoreapp2.2/Polyfill_Type.cs +++ b/src/Split/netcoreapp2.2/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/netcoreapp3.0/Polyfill_Type.cs b/src/Split/netcoreapp3.0/Polyfill_Type.cs index 91339407..ed8cebd7 100644 --- a/src/Split/netcoreapp3.0/Polyfill_Type.cs +++ b/src/Split/netcoreapp3.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/netcoreapp3.1/Polyfill_Type.cs b/src/Split/netcoreapp3.1/Polyfill_Type.cs index 91339407..ed8cebd7 100644 --- a/src/Split/netcoreapp3.1/Polyfill_Type.cs +++ b/src/Split/netcoreapp3.1/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/netstandard2.0/Polyfill_Type.cs b/src/Split/netstandard2.0/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/netstandard2.0/Polyfill_Type.cs +++ b/src/Split/netstandard2.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Split/netstandard2.1/Polyfill_Type.cs b/src/Split/netstandard2.1/Polyfill_Type.cs index 91339407..ed8cebd7 100644 --- a/src/Split/netstandard2.1/Polyfill_Type.cs +++ b/src/Split/netstandard2.1/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; diff --git a/src/Split/uap10.0/Polyfill_Type.cs b/src/Split/uap10.0/Polyfill_Type.cs index b99bdfab..93c131ea 100644 --- a/src/Split/uap10.0/Polyfill_Type.cs +++ b/src/Split/uap10.0/Polyfill_Type.cs @@ -2,6 +2,7 @@ #pragma warning disable namespace Polyfills; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -47,44 +48,44 @@ public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInf throw new ArgumentNullException(nameof(types)); } } - var methods = target.GetMethods(bindingAttr); - if (genericParameterCount == 0) + var comparison = StringComparison.Ordinal; + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) { - foreach (var method in methods) - { - if (method.IsGenericMethod) - { - continue; - } - if (IsMatch(method)) - { - return method; - } - } + comparison = StringComparison.OrdinalIgnoreCase; } - else + var candidates = new List(); + foreach (var method in target.GetMethods(bindingAttr)) { - foreach (var method in methods) + if (!string.Equals(name, method.Name, comparison)) + { + continue; + } + var genericArgumentCount = 0; + if (method.IsGenericMethod) + { + genericArgumentCount = method.GetGenericArguments().Length; + } + if (genericParameterCount != genericArgumentCount) { - if (!method.IsGenericMethod) - { - continue; - } - var genericArguments = method.GetGenericArguments(); - if (genericParameterCount != genericArguments.Length) - { - continue; - } - if (IsMatch(method)) - { - return method; - } + continue; } + if (method.GetParameters().Length != types.Length) + { + continue; + } + candidates.Add(method); + } + if (candidates.Count == 0) + { + return null; + } + if (types.Length == 0 && + candidates.Count == 1) + { + return (MethodInfo) candidates[0]; } - return null; - bool IsMatch(MethodInfo method) => - name == method.Name && - method.GetParameters().Select(_ => _.ParameterType).SequenceEqual(types); + binder ??= Type.DefaultBinder; + return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as MethodInfo; } /// /// Searches for the specified method whose parameters match the specified generic parameter count, argument types and modifiers, using the specified binding constraints. diff --git a/src/Tests/PolyfillTests_Type.cs b/src/Tests/PolyfillTests_Type.cs index 995a1ac4..527938d1 100644 --- a/src/Tests/PolyfillTests_Type.cs +++ b/src/Tests/PolyfillTests_Type.cs @@ -25,6 +25,101 @@ public async Task Type_GetMethod_Validation() await Assert.That(() => type.GetMethod("Substring", 0, BindingFlags.Public, null, null!, null)).Throws(); } + [Test] + public async Task Type_GetMethod_UsesBinderForAssignableTypes() + { + const BindingFlags flags = BindingFlags.Public | BindingFlags.Static; + + // no WriteLine(Uri) exists, so the binder selects WriteLine(object) + var method = typeof(Console).GetMethod("WriteLine", 0, flags, null, [typeof(Uri)], null); + + await Assert.That(method).IsNotNull(); + await Assert.That(method!.GetParameters()[0].ParameterType).IsEqualTo(typeof(object)); + } + + [Test] + public async Task Type_GetMethod_ExactMatchPreferred() + { + const BindingFlags flags = BindingFlags.Public | BindingFlags.Static; + + var method = typeof(Console).GetMethod("WriteLine", 0, flags, null, [typeof(string)], null); + + await Assert.That(method).IsNotNull(); + await Assert.That(method!.GetParameters()[0].ParameterType).IsEqualTo(typeof(string)); + } + + [Test] + public async Task Type_GetMethod_NoApplicableMethod() + { + const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance; + + var method = typeof(string).GetMethod("Substring", 0, flags, null, [typeof(string)], null); + + await Assert.That(method).IsNull(); + } + + [Test] + public async Task Type_GetMethod_IgnoreCase() + { + const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance; + + var caseSensitive = typeof(string).GetMethod("substring", 0, flags, null, [typeof(int)], null); + var ignoreCase = typeof(string).GetMethod("substring", 0, flags | BindingFlags.IgnoreCase, null, [typeof(int)], null); + + await Assert.That(caseSensitive).IsNull(); + await Assert.That(ignoreCase).IsNotNull(); + await Assert.That(ignoreCase!.Name).IsEqualTo("Substring"); + } + + class LastCandidateBinder : + Binder + { + public int SelectMethodCalls; + + public override MethodBase? SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[]? modifiers) + { + SelectMethodCalls++; + foreach (var candidate in match) + { + if (candidate.GetParameters()[0].ParameterType == typeof(object)) + { + return candidate; + } + } + + return null; + } + + public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, CultureInfo? culture) => + throw new NotSupportedException(); + + public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object?[] args, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? names, out object? state) => + throw new NotSupportedException(); + + public override object ChangeType(object value, Type type, CultureInfo? culture) => + throw new NotSupportedException(); + + public override void ReorderArgumentArray(ref object?[] args, object state) => + throw new NotSupportedException(); + + public override PropertyInfo? SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type? returnType, Type[]? indexes, ParameterModifier[]? modifiers) => + throw new NotSupportedException(); + } + + [Test] + public async Task Type_GetMethod_UsesSuppliedBinder() + { + const BindingFlags flags = BindingFlags.Public | BindingFlags.Static; + var binder = new LastCandidateBinder(); + + // the default binder would select WriteLine(string) + var method = typeof(Console).GetMethod("WriteLine", 0, flags, binder, [typeof(string)], null); + + await Assert.That(binder.SelectMethodCalls).IsEqualTo(1); + await Assert.That(method).IsNotNull(); + await Assert.That(method!.GetParameters()[0].ParameterType).IsEqualTo(typeof(object)); + } + [Test] public async Task MemberInfo_HasSameMetadataDefinitionAs_Null_Throws() {