From 4d0d8b14370880ddfef09b9c00081d15c77c153d Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 11 Mar 2023 18:27:02 +0100 Subject: [PATCH 1/8] Add invocation helpers for (generic) methods (on generic types) --- NeosModLoader/NeosModLoader.csproj | 4 +- NeosModLoader/Utility/GenericMethodInvoker.cs | 143 +++++++++++++++ .../Utility/GenericTypeMethodsInvoker.cs | 166 ++++++++++++++++++ NeosModLoader/Utility/TypeDefinition.cs | 46 +++++ 4 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 NeosModLoader/Utility/GenericMethodInvoker.cs create mode 100644 NeosModLoader/Utility/GenericTypeMethodsInvoker.cs create mode 100644 NeosModLoader/Utility/TypeDefinition.cs diff --git a/NeosModLoader/NeosModLoader.csproj b/NeosModLoader/NeosModLoader.csproj index b480df5..fe483d0 100644 --- a/NeosModLoader/NeosModLoader.csproj +++ b/NeosModLoader/NeosModLoader.csproj @@ -1,4 +1,4 @@ - + {D4627C7F-8091-477A-ABDC-F1465D94D8D9} @@ -13,7 +13,7 @@ false net462 512 - 9.0 + latestMajor enable true true diff --git a/NeosModLoader/Utility/GenericMethodInvoker.cs b/NeosModLoader/Utility/GenericMethodInvoker.cs new file mode 100644 index 0000000..1b263c1 --- /dev/null +++ b/NeosModLoader/Utility/GenericMethodInvoker.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace NeosModLoader.Utility +{ + internal sealed class GenericMethodInvoker + { + private readonly Dictionary concreteMethods = new(); + + public MethodInfo GenericMethod { get; } + + public GenericMethodInvoker(MethodInfo method) + { + if (!method.ContainsGenericParameters) + throw new InvalidOperationException("Target method must have remaining open type parameters."); + + GenericMethod = method; + } + + public GenericMethodInvoker(Delegate @delegate) : this(@delegate.Method) + { } + + internal GenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters) + { + //Intentionally left out generic parameter check to simplify the GenericTypeMethodInvoker + GenericMethod = method; + } + + public TReturn Invoke(TInstance instance, Type[] types, params object[] parameters) + { + return InvokeInternal(instance, types, parameters); + } + + public TReturn Invoke(TInstance instance, Type type, params object[] parameters) + { + return InvokeInternal(instance, type, parameters); + } + + internal TReturn InvokeInternal(TInstance? instance, TypeDefinition definition, object[]? parameters) + { + if (!concreteMethods.TryGetValue(definition, out var method)) + { + if (GenericMethod.ContainsGenericParameters) + method = GenericMethod.MakeGenericMethod(definition.Types); + else + method = GenericMethod; + + concreteMethods.Add(definition, method); + } + + return (TReturn)method.Invoke(instance, parameters); + } + } + + internal sealed class GenericMethodInvoker + { + private readonly Dictionary concreteMethods = new(); + + public MethodInfo GenericMethod { get; } + + public GenericMethodInvoker(MethodInfo method) + { + if (!method.ContainsGenericParameters) + throw new InvalidOperationException("Target method must have remaining open type parameters."); + + GenericMethod = method; + } + + public GenericMethodInvoker(Delegate @delegate) : this(@delegate.Method) + { } + + public TReturn Invoke(Type[] types, params object[] parameters) + { + return InvokeInternal(types, parameters); + } + + public TReturn Invoke(Type type, params object[] parameters) + { + return InvokeInternal(type, parameters); + } + + private TReturn InvokeInternal(TypeDefinition definition, object[] parameters) + { + if (!concreteMethods.TryGetValue(definition, out var method)) + { + if (GenericMethod.ContainsGenericParameters) + method = GenericMethod.MakeGenericMethod(definition.Types); + else + method = GenericMethod; + + concreteMethods.Add(definition, method); + } + + return (TReturn)method.Invoke(null, parameters); + } + } + + internal sealed class GenericMethodInvoker + { + private readonly Dictionary concreteMethods = new(); + + public MethodInfo GenericMethod { get; } + + public GenericMethodInvoker(MethodInfo method) + { + if (!method.ContainsGenericParameters) + throw new InvalidOperationException("Target method must have remaining open type parameters."); + + GenericMethod = method; + } + + public GenericMethodInvoker(Delegate @delegate) : this(@delegate.Method) + { } + + public void Invoke(Type[] types, params object[] parameters) + { + InvokeInternal(types, parameters); + } + + public void Invoke(Type type, params object[] parameters) + { + InvokeInternal(type, parameters); + } + + private void InvokeInternal(TypeDefinition definition, object[] parameters) + { + if (!concreteMethods.TryGetValue(definition, out var method)) + { + if (GenericMethod.ContainsGenericParameters) + method = GenericMethod.MakeGenericMethod(definition.Types); + else + method = GenericMethod; + + concreteMethods.Add(definition, method); + } + + method.Invoke(null, parameters); + } + } +} diff --git a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs new file mode 100644 index 0000000..3a6951f --- /dev/null +++ b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs @@ -0,0 +1,166 @@ +using HarmonyLib; +using NeosModLoader; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace NeosModLoader.Utility +{ + internal sealed class GenericTypeMethodsInvoker + { + private readonly Dictionary concreteTypes = new(); + + public Type GenericType { get; } + + public Func GetGenericMethodOfConcreteType { get; } + + public GenericTypeMethodsInvoker(Type genericType) + : this(genericType, GetGenericMethodOfConcreteTypeDefault) + { } + + public GenericTypeMethodsInvoker(Type genericType, Func getGenericMethodOfConcreteType) + { + GenericType = genericType; + GetGenericMethodOfConcreteType = getGenericMethodOfConcreteType; + } + + public TReturn Invoke(MethodInfo method, Type[] instanceTypes, object instance, Type[] methodTypes, params object[] parameters) + { + return (TReturn)InvokeInternal(method, instanceTypes, instance, methodTypes, parameters); + } + + public TReturn Invoke(MethodInfo method, Type[] instanceTypes, object instance, Type methodType, params object[] parameters) + { + return (TReturn)InvokeInternal(method, instanceTypes, instance, methodType, parameters); + } + + public object Invoke(MethodInfo method, Type[] instanceTypes, object instance, Type[] methodTypes, params object[] parameters) + { + return InvokeInternal(method, instanceTypes, instance, methodTypes, parameters); + } + + public object Invoke(MethodInfo method, Type instanceType, object instance, Type[] methodTypes, params object[] parameters) + { + return InvokeInternal(method, instanceType, instance, methodTypes, parameters); + } + + public object Invoke(MethodInfo method, Type[] instanceTypes, object instance, Type methodType, params object[] parameters) + { + return InvokeInternal(method, instanceTypes, instance, methodType, parameters); + } + + public object Invoke(MethodInfo method, Type instanceType, object instance, Type methodType, params object[] parameters) + { + return InvokeInternal(method, instanceType, instance, methodType, parameters); + } + + public TReturn Invoke(MethodInfo method, Type instanceType, object instance, Type[] methodTypes, params object[] parameters) + { + return (TReturn)InvokeInternal(method, instanceType, instance, methodTypes, parameters); + } + + public TReturn Invoke(MethodInfo method, Type instanceType, object instance, Type methodType, params object[] parameters) + { + return (TReturn)InvokeInternal(method, instanceType, instance, methodType, parameters); + } + + public TReturn Invoke(MethodInfo method, Type instanceType, object instance, params object[] parameters) + { + return (TReturn)InvokeInternal(method, instanceType, instance, new TypeDefinition(), parameters); + } + + public TReturn Invoke(MethodInfo method, Type instanceType, params object[] parameters) + { + return (TReturn)InvokeInternal(method, instanceType, null, new TypeDefinition(), parameters); + } + + public object Invoke(MethodInfo method, Type[] instanceTypes, object instance, params object[] parameters) + { + return InvokeInternal(method, instanceTypes, instance, new TypeDefinition(), parameters); + } + + public object Invoke(MethodInfo method, Type[] instanceTypes, params object[] parameters) + { + return InvokeInternal(method, instanceTypes, null, new TypeDefinition(), parameters); + } + + private static MethodInfo GetGenericMethodOfConcreteTypeDefault(MethodInfo needleMethod, Type concreteType) + { + NeosMod.Debug($"Looking for: {needleMethod.ReturnType.Name} {needleMethod.Name}({string.Join(", ", needleMethod.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"))})"); + + return concreteType.GetMethods(AccessTools.all) + .Single(hayMethod => + { + NeosMod.Debug($"Testing: {hayMethod.ReturnType.Name} {hayMethod.Name}({string.Join(", ", hayMethod.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"))})"); + + if (hayMethod.Name != needleMethod.Name) + return false; + + var needleParameters = needleMethod.GetParameters(); + var hayParameters = hayMethod.GetParameters(); + + if (hayParameters.Length != needleParameters.Length) + return false; + + for (var i = 0; i < needleParameters.Length; ++i) + { + //var needleParameter = needleParameters[i]; + //var hayParameter = hayParameters[i]; + //var checkType = (hayParameter.ParameterType.IsGenericParameter && needleParameter.ParameterType.IsGenericParameter) + // || (!hayParameter.ParameterType.IsGenericParameter && !needleParameter.ParameterType.IsGenericParameter); + + //NeosMod.Msg($"Comparing: {hayParameter.ParameterType} to {needleParameter.ParameterType} => {hayParameter.ParameterType.FullName == needleParameter.ParameterType.FullName}"); + + //if (checkType && hayParameter.ParameterType.FullName != needleParameter.ParameterType.FullName) + // return false; + + // TODO: Do a proper type check? lol + if (hayParameters[i].Name != needleParameters[i].Name) + return false; + } + + return true; + }); + } + + private object InvokeInternal(MethodInfo method, TypeDefinition instanceTypes, object? instance, TypeDefinition methodTypes, object[]? parameters) + { + if (!concreteTypes.TryGetValue(instanceTypes, out var concreteType)) + { + concreteType = GenericType.MakeGenericType(instanceTypes.Types); + concreteTypes.Add(instanceTypes, concreteType); + } + + var methodInvoker = concreteType.GetMethodInvoker(method, GetGenericMethodOfConcreteType); + + return methodInvoker.InvokeInternal(instance, methodTypes, parameters); + } + + private readonly struct ConcreteType + { + public readonly Dictionary> MethodInvokers = new(); + public readonly Type Type; + + public ConcreteType(Type type) + { + Type = type; + } + + public static implicit operator ConcreteType(Type type) => new(type); + + public GenericMethodInvoker GetMethodInvoker(MethodInfo genericMethod, Func getMethod) + { + if (!MethodInvokers.TryGetValue(genericMethod, out var methodInvoker)) + { + methodInvoker = new GenericMethodInvoker(getMethod(genericMethod, Type), true); + MethodInvokers.Add(genericMethod, methodInvoker); + } + + return methodInvoker; + } + } + } +} diff --git a/NeosModLoader/Utility/TypeDefinition.cs b/NeosModLoader/Utility/TypeDefinition.cs new file mode 100644 index 0000000..ae2aa48 --- /dev/null +++ b/NeosModLoader/Utility/TypeDefinition.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; + +namespace NeosModLoader.Utility +{ + internal readonly struct TypeDefinition : IEquatable + { + public readonly Type[] Types; + + public int Length => Types.Length; + + public TypeDefinition(params Type[] types) + { + Types = types ?? Array.Empty(); + } + + public static implicit operator TypeDefinition(Type[] types) => new(types); + + public static implicit operator TypeDefinition(Type type) + { + if (type == null) + return new(Array.Empty()); + + return new(type); + } + + public static bool operator !=(TypeDefinition left, TypeDefinition right) => !(left == right); + + public static bool operator ==(TypeDefinition left, TypeDefinition right) => left.Equals(right); + + public override bool Equals(object obj) + { + return obj is TypeDefinition definition && Equals(definition); + } + + public bool Equals(TypeDefinition other) + { + return Types.SequenceEqual(other.Types); + } + + public override int GetHashCode() + { + return unchecked(Types.Aggregate(0, (acc, type) => (-136316459 * acc) + type.GetHashCode())); + } + } +} From b1e5effb2c049675ec3d65995c447438bb320c61 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 11 Mar 2023 22:23:54 +0100 Subject: [PATCH 2/8] access oopsie --- NeosModLoader/Utility/GenericMethodInvoker.cs | 15 +++------------ .../Utility/GenericTypeMethodsInvoker.cs | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/NeosModLoader/Utility/GenericMethodInvoker.cs b/NeosModLoader/Utility/GenericMethodInvoker.cs index 1b263c1..7cdc195 100644 --- a/NeosModLoader/Utility/GenericMethodInvoker.cs +++ b/NeosModLoader/Utility/GenericMethodInvoker.cs @@ -6,7 +6,7 @@ namespace NeosModLoader.Utility { - internal sealed class GenericMethodInvoker + public sealed class GenericMethodInvoker { private readonly Dictionary concreteMethods = new(); @@ -20,9 +20,6 @@ public GenericMethodInvoker(MethodInfo method) GenericMethod = method; } - public GenericMethodInvoker(Delegate @delegate) : this(@delegate.Method) - { } - internal GenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters) { //Intentionally left out generic parameter check to simplify the GenericTypeMethodInvoker @@ -55,7 +52,7 @@ internal TReturn InvokeInternal(TInstance? instance, TypeDefinition definition, } } - internal sealed class GenericMethodInvoker + public sealed class GenericMethodInvoker { private readonly Dictionary concreteMethods = new(); @@ -69,9 +66,6 @@ public GenericMethodInvoker(MethodInfo method) GenericMethod = method; } - public GenericMethodInvoker(Delegate @delegate) : this(@delegate.Method) - { } - public TReturn Invoke(Type[] types, params object[] parameters) { return InvokeInternal(types, parameters); @@ -98,7 +92,7 @@ private TReturn InvokeInternal(TypeDefinition definition, object[] parameters) } } - internal sealed class GenericMethodInvoker + public sealed class GenericMethodInvoker { private readonly Dictionary concreteMethods = new(); @@ -112,9 +106,6 @@ public GenericMethodInvoker(MethodInfo method) GenericMethod = method; } - public GenericMethodInvoker(Delegate @delegate) : this(@delegate.Method) - { } - public void Invoke(Type[] types, params object[] parameters) { InvokeInternal(types, parameters); diff --git a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs index 3a6951f..2e4436f 100644 --- a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs +++ b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs @@ -9,7 +9,7 @@ namespace NeosModLoader.Utility { - internal sealed class GenericTypeMethodsInvoker + public sealed class GenericTypeMethodsInvoker { private readonly Dictionary concreteTypes = new(); From 7c73e42c657286b2e26dd746f045c3e6c7eda654 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 12 Mar 2023 16:55:03 +0100 Subject: [PATCH 3/8] Concrete Version number --- NeosModLoader/NeosModLoader.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NeosModLoader/NeosModLoader.csproj b/NeosModLoader/NeosModLoader.csproj index fe483d0..b5b2a09 100644 --- a/NeosModLoader/NeosModLoader.csproj +++ b/NeosModLoader/NeosModLoader.csproj @@ -13,7 +13,7 @@ false net462 512 - latestMajor + 10.0 enable true true From e69cef66964455d30537cb6a606616bf6437af4f Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 12 Mar 2023 17:13:34 +0100 Subject: [PATCH 4/8] Improve logging in GetGenericMethodOfConcreteType --- NeosModLoader/Utility/GenericTypeMethodsInvoker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs index 2e4436f..f0840a9 100644 --- a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs +++ b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs @@ -89,16 +89,16 @@ public object Invoke(MethodInfo method, Type[] instanceTypes, params object[] pa private static MethodInfo GetGenericMethodOfConcreteTypeDefault(MethodInfo needleMethod, Type concreteType) { - NeosMod.Debug($"Looking for: {needleMethod.ReturnType.Name} {needleMethod.Name}({string.Join(", ", needleMethod.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"))})"); + Logger.DebugFuncInternal(() => $"Looking for: {needleMethod.FullDescription()}"); return concreteType.GetMethods(AccessTools.all) .Single(hayMethod => { - NeosMod.Debug($"Testing: {hayMethod.ReturnType.Name} {hayMethod.Name}({string.Join(", ", hayMethod.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"))})"); - if (hayMethod.Name != needleMethod.Name) return false; + Logger.DebugFuncInternal(() => $"Testing potential candidate: {hayMethod.FullDescription()}"); + var needleParameters = needleMethod.GetParameters(); var hayParameters = hayMethod.GetParameters(); From 8b38b7ac7dc16c052db86c5d816e48527c5c4940 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 12 Mar 2023 19:34:02 +0100 Subject: [PATCH 5/8] InvalidOperation -> Argument Exceptions --- NeosModLoader/Utility/GenericMethodInvoker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NeosModLoader/Utility/GenericMethodInvoker.cs b/NeosModLoader/Utility/GenericMethodInvoker.cs index 7cdc195..573f912 100644 --- a/NeosModLoader/Utility/GenericMethodInvoker.cs +++ b/NeosModLoader/Utility/GenericMethodInvoker.cs @@ -15,7 +15,7 @@ public sealed class GenericMethodInvoker public GenericMethodInvoker(MethodInfo method) { if (!method.ContainsGenericParameters) - throw new InvalidOperationException("Target method must have remaining open type parameters."); + throw new ArgumentException("Target method must have remaining open type parameters.", nameof(method)); GenericMethod = method; } @@ -61,7 +61,7 @@ public sealed class GenericMethodInvoker public GenericMethodInvoker(MethodInfo method) { if (!method.ContainsGenericParameters) - throw new InvalidOperationException("Target method must have remaining open type parameters."); + throw new ArgumentException("Target method must have remaining open type parameters.", nameof(method)); GenericMethod = method; } @@ -101,7 +101,7 @@ public sealed class GenericMethodInvoker public GenericMethodInvoker(MethodInfo method) { if (!method.ContainsGenericParameters) - throw new InvalidOperationException("Target method must have remaining open type parameters."); + throw new ArgumentException("Target method must have remaining open type parameters.", nameof(method)); GenericMethod = method; } From cc45c1d56dc2dc1ad4904d84c8c5bf79e0ea5f0d Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 12 Mar 2023 19:54:11 +0100 Subject: [PATCH 6/8] Fix GenericParameter checks --- NeosModLoader/Utility/GenericMethodInvoker.cs | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/NeosModLoader/Utility/GenericMethodInvoker.cs b/NeosModLoader/Utility/GenericMethodInvoker.cs index 573f912..0d58745 100644 --- a/NeosModLoader/Utility/GenericMethodInvoker.cs +++ b/NeosModLoader/Utility/GenericMethodInvoker.cs @@ -12,17 +12,14 @@ public sealed class GenericMethodInvoker public MethodInfo GenericMethod { get; } - public GenericMethodInvoker(MethodInfo method) - { - if (!method.ContainsGenericParameters) - throw new ArgumentException("Target method must have remaining open type parameters.", nameof(method)); - - GenericMethod = method; - } + public GenericMethodInvoker(MethodInfo method) : this(method, false) + { } internal GenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters) { - //Intentionally left out generic parameter check to simplify the GenericTypeMethodInvoker + if (!ignoreLackOfGenericParameters && !method.ContainsGenericParameters) + throw new ArgumentException("Target method must have remaining open type parameters.", nameof(method)); + GenericMethod = method; } @@ -80,11 +77,7 @@ private TReturn InvokeInternal(TypeDefinition definition, object[] parameters) { if (!concreteMethods.TryGetValue(definition, out var method)) { - if (GenericMethod.ContainsGenericParameters) - method = GenericMethod.MakeGenericMethod(definition.Types); - else - method = GenericMethod; - + method = GenericMethod.MakeGenericMethod(definition.Types); concreteMethods.Add(definition, method); } @@ -120,11 +113,7 @@ private void InvokeInternal(TypeDefinition definition, object[] parameters) { if (!concreteMethods.TryGetValue(definition, out var method)) { - if (GenericMethod.ContainsGenericParameters) - method = GenericMethod.MakeGenericMethod(definition.Types); - else - method = GenericMethod; - + method = GenericMethod; concreteMethods.Add(definition, method); } From 01e0285ca509fd6e53c9502b57a7130b59c8de2e Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 12 Mar 2023 20:16:34 +0100 Subject: [PATCH 7/8] Make TypeDefinition public --- NeosModLoader/Utility/GenericMethodInvoker.cs | 37 ++---------- .../Utility/GenericTypeMethodsInvoker.cs | 58 +++++-------------- NeosModLoader/Utility/TypeDefinition.cs | 21 ++++--- 3 files changed, 33 insertions(+), 83 deletions(-) diff --git a/NeosModLoader/Utility/GenericMethodInvoker.cs b/NeosModLoader/Utility/GenericMethodInvoker.cs index 0d58745..9b62397 100644 --- a/NeosModLoader/Utility/GenericMethodInvoker.cs +++ b/NeosModLoader/Utility/GenericMethodInvoker.cs @@ -23,14 +23,9 @@ internal GenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParamet GenericMethod = method; } - public TReturn Invoke(TInstance instance, Type[] types, params object[] parameters) + public TReturn Invoke(TInstance instance, TypeDefinition definition, params object[] parameters) { - return InvokeInternal(instance, types, parameters); - } - - public TReturn Invoke(TInstance instance, Type type, params object[] parameters) - { - return InvokeInternal(instance, type, parameters); + return InvokeInternal(instance, definition, parameters); } internal TReturn InvokeInternal(TInstance? instance, TypeDefinition definition, object[]? parameters) @@ -38,7 +33,7 @@ internal TReturn InvokeInternal(TInstance? instance, TypeDefinition definition, if (!concreteMethods.TryGetValue(definition, out var method)) { if (GenericMethod.ContainsGenericParameters) - method = GenericMethod.MakeGenericMethod(definition.Types); + method = GenericMethod.MakeGenericMethod(definition.types); else method = GenericMethod; @@ -63,21 +58,11 @@ public GenericMethodInvoker(MethodInfo method) GenericMethod = method; } - public TReturn Invoke(Type[] types, params object[] parameters) - { - return InvokeInternal(types, parameters); - } - - public TReturn Invoke(Type type, params object[] parameters) - { - return InvokeInternal(type, parameters); - } - - private TReturn InvokeInternal(TypeDefinition definition, object[] parameters) + public TReturn Invoke(TypeDefinition definition, params object[] parameters) { if (!concreteMethods.TryGetValue(definition, out var method)) { - method = GenericMethod.MakeGenericMethod(definition.Types); + method = GenericMethod.MakeGenericMethod(definition.types); concreteMethods.Add(definition, method); } @@ -99,17 +84,7 @@ public GenericMethodInvoker(MethodInfo method) GenericMethod = method; } - public void Invoke(Type[] types, params object[] parameters) - { - InvokeInternal(types, parameters); - } - - public void Invoke(Type type, params object[] parameters) - { - InvokeInternal(type, parameters); - } - - private void InvokeInternal(TypeDefinition definition, object[] parameters) + public void Invoke(TypeDefinition definition, params object[] parameters) { if (!concreteMethods.TryGetValue(definition, out var method)) { diff --git a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs index f0840a9..cc9410a 100644 --- a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs +++ b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs @@ -27,64 +27,34 @@ public GenericTypeMethodsInvoker(Type genericType, Func(MethodInfo method, Type[] instanceTypes, object instance, Type[] methodTypes, params object[] parameters) + public TReturn Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, TypeDefinition methodDefinition, params object[] parameters) { - return (TReturn)InvokeInternal(method, instanceTypes, instance, methodTypes, parameters); + return (TReturn)InvokeInternal(method, instanceDefinition, instance, methodDefinition, parameters); } - public TReturn Invoke(MethodInfo method, Type[] instanceTypes, object instance, Type methodType, params object[] parameters) + public object Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, TypeDefinition methodDefinition, params object[] parameters) { - return (TReturn)InvokeInternal(method, instanceTypes, instance, methodType, parameters); + return InvokeInternal(method, instanceDefinition, instance, methodDefinition, parameters); } - public object Invoke(MethodInfo method, Type[] instanceTypes, object instance, Type[] methodTypes, params object[] parameters) + public TReturn Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, params object[] parameters) { - return InvokeInternal(method, instanceTypes, instance, methodTypes, parameters); + return (TReturn)InvokeInternal(method, instanceDefinition, instance, new TypeDefinition(), parameters); } - public object Invoke(MethodInfo method, Type instanceType, object instance, Type[] methodTypes, params object[] parameters) + public TReturn Invoke(MethodInfo method, TypeDefinition instanceDefinition, params object[] parameters) { - return InvokeInternal(method, instanceType, instance, methodTypes, parameters); + return (TReturn)InvokeInternal(method, instanceDefinition, null, new TypeDefinition(), parameters); } - public object Invoke(MethodInfo method, Type[] instanceTypes, object instance, Type methodType, params object[] parameters) + public object Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, params object[] parameters) { - return InvokeInternal(method, instanceTypes, instance, methodType, parameters); + return InvokeInternal(method, instanceDefinition, instance, new TypeDefinition(), parameters); } - public object Invoke(MethodInfo method, Type instanceType, object instance, Type methodType, params object[] parameters) + public object Invoke(MethodInfo method, TypeDefinition instanceDefinition, params object[] parameters) { - return InvokeInternal(method, instanceType, instance, methodType, parameters); - } - - public TReturn Invoke(MethodInfo method, Type instanceType, object instance, Type[] methodTypes, params object[] parameters) - { - return (TReturn)InvokeInternal(method, instanceType, instance, methodTypes, parameters); - } - - public TReturn Invoke(MethodInfo method, Type instanceType, object instance, Type methodType, params object[] parameters) - { - return (TReturn)InvokeInternal(method, instanceType, instance, methodType, parameters); - } - - public TReturn Invoke(MethodInfo method, Type instanceType, object instance, params object[] parameters) - { - return (TReturn)InvokeInternal(method, instanceType, instance, new TypeDefinition(), parameters); - } - - public TReturn Invoke(MethodInfo method, Type instanceType, params object[] parameters) - { - return (TReturn)InvokeInternal(method, instanceType, null, new TypeDefinition(), parameters); - } - - public object Invoke(MethodInfo method, Type[] instanceTypes, object instance, params object[] parameters) - { - return InvokeInternal(method, instanceTypes, instance, new TypeDefinition(), parameters); - } - - public object Invoke(MethodInfo method, Type[] instanceTypes, params object[] parameters) - { - return InvokeInternal(method, instanceTypes, null, new TypeDefinition(), parameters); + return InvokeInternal(method, instanceDefinition, null, new TypeDefinition(), parameters); } private static MethodInfo GetGenericMethodOfConcreteTypeDefault(MethodInfo needleMethod, Type concreteType) @@ -126,11 +96,11 @@ private static MethodInfo GetGenericMethodOfConcreteTypeDefault(MethodInfo needl }); } - private object InvokeInternal(MethodInfo method, TypeDefinition instanceTypes, object? instance, TypeDefinition methodTypes, object[]? parameters) + private object InvokeInternal(MethodInfo method, TypeDefinition instanceTypes, object? instance, TypeDefinition methodTypes, object[] parameters) { if (!concreteTypes.TryGetValue(instanceTypes, out var concreteType)) { - concreteType = GenericType.MakeGenericType(instanceTypes.Types); + concreteType = GenericType.MakeGenericType(instanceTypes.types); concreteTypes.Add(instanceTypes, concreteType); } diff --git a/NeosModLoader/Utility/TypeDefinition.cs b/NeosModLoader/Utility/TypeDefinition.cs index ae2aa48..14a2e2b 100644 --- a/NeosModLoader/Utility/TypeDefinition.cs +++ b/NeosModLoader/Utility/TypeDefinition.cs @@ -1,18 +1,18 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; namespace NeosModLoader.Utility { - internal readonly struct TypeDefinition : IEquatable + public readonly struct TypeDefinition : IEquatable, IEnumerable { - public readonly Type[] Types; + internal readonly Type[] types; - public int Length => Types.Length; + public int Length => types.Length; public TypeDefinition(params Type[] types) - { - Types = types ?? Array.Empty(); - } + => this.types = types ?? Array.Empty(); public static implicit operator TypeDefinition(Type[] types) => new(types); @@ -35,12 +35,17 @@ public override bool Equals(object obj) public bool Equals(TypeDefinition other) { - return Types.SequenceEqual(other.Types); + return types.SequenceEqual(other.types); } + public IEnumerator GetEnumerator() + => ((IEnumerable)types).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => types.GetEnumerator(); + public override int GetHashCode() { - return unchecked(Types.Aggregate(0, (acc, type) => (-136316459 * acc) + type.GetHashCode())); + return unchecked(types.Aggregate(0, (acc, type) => (31 * acc) + type.GetHashCode())); } } } From a43c25a54486b4378a47ba556fc7abcf85fa1290 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Mon, 13 Mar 2023 02:02:00 +0100 Subject: [PATCH 8/8] It's documentin time (for generic method invokers) --- NeosModLoader/Utility/GenericMethodInvoker.cs | 173 +++++++++++++----- .../Utility/GenericTypeMethodsInvoker.cs | 165 ++++++++++++++++- 2 files changed, 293 insertions(+), 45 deletions(-) diff --git a/NeosModLoader/Utility/GenericMethodInvoker.cs b/NeosModLoader/Utility/GenericMethodInvoker.cs index 9b62397..9266ac9 100644 --- a/NeosModLoader/Utility/GenericMethodInvoker.cs +++ b/NeosModLoader/Utility/GenericMethodInvoker.cs @@ -6,16 +6,28 @@ namespace NeosModLoader.Utility { - public sealed class GenericMethodInvoker + /// + /// Represents the base class for more specific generic method invokers. + /// + /// The type of the instances that the generic method gets invoked on. + /// The type of the generic method's return value. Use object if it depends on the generic type parameters of the method. + public abstract class BaseGenericMethodInvoker { private readonly Dictionary concreteMethods = new(); + /// + /// Gets the generic who's concrete versions will be in invoked by this generic method invoker. + /// public MethodInfo GenericMethod { get; } - public GenericMethodInvoker(MethodInfo method) : this(method, false) - { } - - internal GenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters) + /// + /// Creates a new generic method invoker for the given method which may not have generic type parameters. + /// + /// Use object for if it depends on the generic type parameters of the method. + /// + /// The generic method to invoke concrete version of. + /// Ignores the method lacking generic type parameters if true. + internal BaseGenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters) { if (!ignoreLackOfGenericParameters && !method.ContainsGenericParameters) throw new ArgumentException("Target method must have remaining open type parameters.", nameof(method)); @@ -23,12 +35,25 @@ internal GenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParamet GenericMethod = method; } - public TReturn Invoke(TInstance instance, TypeDefinition definition, params object[] parameters) - { - return InvokeInternal(instance, definition, parameters); - } + /// + /// Creates a new generic method invoker for the given open generic method. + /// + /// Use object for if it depends on the generic type parameters of the method. + /// + /// The generic method to invoke concrete version of. + internal BaseGenericMethodInvoker(MethodInfo method) : this(method, false) + { } - internal TReturn InvokeInternal(TInstance? instance, TypeDefinition definition, object[]? parameters) + /// + /// Invokes a concrete version of this invoker's GenericMethod using the given , + /// type parameter and method . + /// + /// The object instance to invoke the method on. Use null for static methods. + /// The generic type parameter definition to create the concrete method with.
+ /// May be ignored if doesn't contain generic parameters. + /// The parameters to invoke the method with. May be empty or null if there is none. + /// The result of the method invocation. + internal TReturn InvokeInternal(TInstance? instance, TypeDefinition definition, params object[]? parameters) { if (!concreteMethods.TryGetValue(definition, out var method)) { @@ -44,55 +69,119 @@ internal TReturn InvokeInternal(TInstance? instance, TypeDefinition definition, } } - public sealed class GenericMethodInvoker + /// + /// Represents a generic method invoker that invokes instance methods with a return value. + /// + /// + public sealed class GenericInstanceMethodInvoker : BaseGenericMethodInvoker { - private readonly Dictionary concreteMethods = new(); + /// + /// Creates a new generic method invoker for the given open generic instance method with a return value. + /// + /// Use object for if it depends on the generic type parameters of the method. + /// + /// The generic method to invoke concrete version of. + public GenericInstanceMethodInvoker(MethodInfo method) : base(method) + { } - public MethodInfo GenericMethod { get; } + internal GenericInstanceMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters) + : base(method, ignoreLackOfGenericParameters) + { } - public GenericMethodInvoker(MethodInfo method) + /// + /// Invokes a concrete version of this invoker's + /// GenericMethod + /// using the given , type parameter and method . + /// + /// The object instance to invoke the method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. + /// The result of the method invocation. + public TReturn Invoke(TInstance instance, TypeDefinition definition, params object[] parameters) { - if (!method.ContainsGenericParameters) - throw new ArgumentException("Target method must have remaining open type parameters.", nameof(method)); - - GenericMethod = method; + return InvokeInternal(instance, definition, parameters); } + } + /// + /// Represents a generic method invoker that invokes static methods with a return value. + /// + /// + public sealed class GenericStaticMethodInvoker : BaseGenericMethodInvoker + { + /// + /// Creates a new generic method invoker for the given open generic static method with a return value. + /// + /// Use object for if it depends on the generic type parameters of the method. + /// + /// The generic method to invoke concrete version of. + public GenericStaticMethodInvoker(MethodInfo method) : base(method) + { } + + /// + /// Invokes a concrete version of this invoker's static + /// GenericMethod + /// using the given type parameter and method . + /// + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. + /// The result of the method invocation. public TReturn Invoke(TypeDefinition definition, params object[] parameters) { - if (!concreteMethods.TryGetValue(definition, out var method)) - { - method = GenericMethod.MakeGenericMethod(definition.types); - concreteMethods.Add(definition, method); - } - - return (TReturn)method.Invoke(null, parameters); + return InvokeInternal(null, definition, parameters); } } - public sealed class GenericMethodInvoker + /// + /// Represents a generic method invoker that invokes static void methods. + /// + public sealed class GenericStaticVoidMethodInvoker : BaseGenericMethodInvoker { - private readonly Dictionary concreteMethods = new(); - - public MethodInfo GenericMethod { get; } + /// + /// Creates a new generic method invoker for the given open generic static void method. + /// + /// The generic method to invoke concrete version of. + public GenericStaticVoidMethodInvoker(MethodInfo method) : base(method) + { } - public GenericMethodInvoker(MethodInfo method) + /// + /// Invokes a concrete version of this invoker's static void + /// GenericMethod + /// using the given type parameter and method . + /// + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. + /// The result of the method invocation. + public void Invoke(TypeDefinition definition, params object[] parameters) { - if (!method.ContainsGenericParameters) - throw new ArgumentException("Target method must have remaining open type parameters.", nameof(method)); - - GenericMethod = method; + InvokeInternal(null, definition, parameters); } + } - public void Invoke(TypeDefinition definition, params object[] parameters) - { - if (!concreteMethods.TryGetValue(definition, out var method)) - { - method = GenericMethod; - concreteMethods.Add(definition, method); - } + /// + /// Represents a generic method invoker that invokes instance void methods. + /// + /// + public sealed class GenericVoidMethodInvoker : BaseGenericMethodInvoker + { + /// + /// Creates a new generic method invoker for the given open generic instance void method. + /// + /// The generic method to invoke concrete version of. + public GenericVoidMethodInvoker(MethodInfo method) : base(method) + { } - method.Invoke(null, parameters); + /// + /// Invokes a concrete version of this invoker's void + /// GenericMethod + /// using the given , type parameter and method . + /// + /// The object instance to invoke the method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. + public void Invoke(TInstance instance, TypeDefinition definition, params object[] parameters) + { + InvokeInternal(instance, definition, parameters); } } } diff --git a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs index cc9410a..1069d67 100644 --- a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs +++ b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs @@ -9,54 +9,195 @@ namespace NeosModLoader.Utility { + /// + /// Represents a generic method invoker that invokes potentially generic methods on generic types. + /// public sealed class GenericTypeMethodsInvoker { private readonly Dictionary concreteTypes = new(); + /// + /// Gets the generic who's concrete versions will have concrete methods invoked by this invoker. + /// public Type GenericType { get; } + /// + /// Gets the function that handles resolving passed in s of the GenericType to ones of the concrete one. + /// public Func GetGenericMethodOfConcreteType { get; } + /// + /// Creates a new generic type methods invoker with the given generic type and the default concrete method resolving function. + /// + /// The generic type who's concrete versions will have concrete methods invoked by this invoker. public GenericTypeMethodsInvoker(Type genericType) : this(genericType, GetGenericMethodOfConcreteTypeDefault) { } + /// + /// Creates a new generic type methods invoker with the given generic type and method resolving function. + /// + /// The generic type who's concrete versions will have concrete methods invoked by this invoker. + /// The function that handles resolving passed in s of the GenericType to ones of the concrete one. public GenericTypeMethodsInvoker(Type genericType, Func getGenericMethodOfConcreteType) { GenericType = genericType; GetGenericMethodOfConcreteType = getGenericMethodOfConcreteType; } + /// + /// Invokes a concrete version of the given generic method using the given , + /// type parameters and method on the + /// concrete version of this instance's GenericType created with the type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The instance to invoke the constructed concrete method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. public TReturn Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, TypeDefinition methodDefinition, params object[] parameters) { return (TReturn)InvokeInternal(method, instanceDefinition, instance, methodDefinition, parameters); } + /// + /// Invokes a concrete version of the given generic method using the given , + /// type parameters and method on the + /// concrete version of this instance's GenericType created with the type parameters. + /// + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The instance to invoke the constructed concrete method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. public object Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, TypeDefinition methodDefinition, params object[] parameters) { return InvokeInternal(method, instanceDefinition, instance, methodDefinition, parameters); } + /// + /// Invokes a concrete version of the given method on the concrete version of this instance's + /// GenericType created with the type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The instance to invoke the constructed concrete method on. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. public TReturn Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, params object[] parameters) { return (TReturn)InvokeInternal(method, instanceDefinition, instance, new TypeDefinition(), parameters); } + /// + /// Invokes a concrete version of the given static method of the concrete version of this instance's + /// GenericType created with the type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. public TReturn Invoke(MethodInfo method, TypeDefinition instanceDefinition, params object[] parameters) { return (TReturn)InvokeInternal(method, instanceDefinition, null, new TypeDefinition(), parameters); } + /// + /// Invokes a concrete version of the given method on the concrete version of this instance's + /// GenericType created with the type parameters. + /// + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The instance to invoke the constructed concrete method on. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. public object Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, params object[] parameters) { return InvokeInternal(method, instanceDefinition, instance, new TypeDefinition(), parameters); } + /// + /// Invokes a concrete version of the given static method of the concrete version of this instance's + /// GenericType created with the type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. public object Invoke(MethodInfo method, TypeDefinition instanceDefinition, params object[] parameters) { return InvokeInternal(method, instanceDefinition, null, new TypeDefinition(), parameters); } + /// + /// Invokes a concrete version of the given method on the concrete version of this instance's + /// GenericType created with the given 's type parameters. + /// + /// The generic method of the generic type to invoke. + /// The instance to invoke the constructed concrete method on. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + /// The is not descended from the GenericType. + public object Invoke(MethodInfo method, object instance, params object[] parameters) + { + return InvokeInternal(method, GetMatchingGenericTypeArguments(instance), instance, new TypeDefinition(), parameters); + } + + /// + /// Invokes a concrete version of the given generic method using the given , + /// type parameters and method on the + /// concrete version of this instance's GenericType created with the given 's type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The instance to invoke the constructed concrete method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + /// The is not descended from the GenericType. + public object Invoke(MethodInfo method, object instance, TypeDefinition methodDefinition, params object[] parameters) + { + return InvokeInternal(method, GetMatchingGenericTypeArguments(instance), instance, methodDefinition, parameters); + } + + /// + /// Invokes a concrete version of the given generic method using the given , + /// type parameters and method on the + /// concrete version of this instance's GenericType created with the given 's type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The instance to invoke the constructed concrete method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + /// The is not descended from the GenericType. + public TReturn Invoke(MethodInfo method, object instance, TypeDefinition methodDefinition, params object[] parameters) + { + return (TReturn)InvokeInternal(method, GetMatchingGenericTypeArguments(instance), instance, methodDefinition, parameters); + } + + /// + /// Invokes a concrete version of the given method on the concrete version of this instance's + /// GenericType created with the given 's type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The instance to invoke the constructed concrete method on. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + /// The is not descended from the GenericType. + public TReturn Invoke(MethodInfo method, object instance, params object[] parameters) + { + return (TReturn)InvokeInternal(method, GetMatchingGenericTypeArguments(instance), instance, new TypeDefinition(), parameters); + } + private static MethodInfo GetGenericMethodOfConcreteTypeDefault(MethodInfo needleMethod, Type concreteType) { Logger.DebugFuncInternal(() => $"Looking for: {needleMethod.FullDescription()}"); @@ -96,6 +237,24 @@ private static MethodInfo GetGenericMethodOfConcreteTypeDefault(MethodInfo needl }); } + private Type[] GetMatchingGenericTypeArguments(object instance) + { + var type = instance.GetType(); + + do + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == GenericType) + return type.GenericTypeArguments; + + type = type.BaseType; + } + while (type != null); + + throw new ArgumentException( + $"Provided instance [{instance.GetType().FullDescription()}] was not descended from {nameof(GenericType)} [{GenericType.FullDescription()}].", + nameof(instance)); + } + private object InvokeInternal(MethodInfo method, TypeDefinition instanceTypes, object? instance, TypeDefinition methodTypes, object[] parameters) { if (!concreteTypes.TryGetValue(instanceTypes, out var concreteType)) @@ -111,7 +270,7 @@ private object InvokeInternal(MethodInfo method, TypeDefinition instanceTypes, o private readonly struct ConcreteType { - public readonly Dictionary> MethodInvokers = new(); + public readonly Dictionary> MethodInvokers = new(); public readonly Type Type; public ConcreteType(Type type) @@ -121,11 +280,11 @@ public ConcreteType(Type type) public static implicit operator ConcreteType(Type type) => new(type); - public GenericMethodInvoker GetMethodInvoker(MethodInfo genericMethod, Func getMethod) + public BaseGenericMethodInvoker GetMethodInvoker(MethodInfo genericMethod, Func getMethod) { if (!MethodInvokers.TryGetValue(genericMethod, out var methodInvoker)) { - methodInvoker = new GenericMethodInvoker(getMethod(genericMethod, Type), true); + methodInvoker = new GenericInstanceMethodInvoker(getMethod(genericMethod, Type), true); MethodInvokers.Add(genericMethod, methodInvoker); }