From 187790aaf7396f3054bd2b31785ef321653243ee Mon Sep 17 00:00:00 2001 From: Gian Maria Ricci Date: Thu, 21 Oct 2021 19:27:37 +0200 Subject: [PATCH] Starting of fast method invoker. --- .../NStore.Core.Tests.csproj | 6 +- .../Processing/FastMethodInvokerTests.cs | 77 +++++++++++++++ .../Processing/MethodInvokerTests.cs | 17 ---- .../Processing/ProcessingTestClasses.cs | 73 ++++++++++++++ src/NStore.Core/NStore.Core.csproj | 10 +- .../Processing/FastMethodInvoker.cs | 97 +++++++++++++++++++ .../NStore.Domain.Tests.csproj | 6 +- .../NStore.Persistence.LiteDB.Tests.csproj | 6 +- .../NStore.Persistence.Mongo.Tests.csproj | 6 +- .../NStore.Persistence.MsSql.Tests.csproj | 6 +- .../NStore.Persistence.Sqlite.Tests.csproj | 6 +- .../NStore.Persistence.Tests.csproj | 6 +- .../NStore.Sample.Tests.csproj | 6 +- 13 files changed, 277 insertions(+), 45 deletions(-) create mode 100644 src/NStore.Core.Tests/Processing/FastMethodInvokerTests.cs create mode 100644 src/NStore.Core.Tests/Processing/ProcessingTestClasses.cs create mode 100644 src/NStore.Core/Processing/FastMethodInvoker.cs diff --git a/src/NStore.Core.Tests/NStore.Core.Tests.csproj b/src/NStore.Core.Tests/NStore.Core.Tests.csproj index 76a29524..4d2a7770 100644 --- a/src/NStore.Core.Tests/NStore.Core.Tests.csproj +++ b/src/NStore.Core.Tests/NStore.Core.Tests.csproj @@ -17,16 +17,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/NStore.Core.Tests/Processing/FastMethodInvokerTests.cs b/src/NStore.Core.Tests/Processing/FastMethodInvokerTests.cs new file mode 100644 index 00000000..b96f2e7c --- /dev/null +++ b/src/NStore.Core.Tests/Processing/FastMethodInvokerTests.cs @@ -0,0 +1,77 @@ +using System; +using NStore.Core.Processing; +using Xunit; + +namespace NStore.Core.Tests.Processing +{ + public class FastMethodInvokerTests + { + private readonly Target _target = new Target(); + + [Fact] + public void invoker_correctly_invoke_method() + { + //even if we know that the method we are invoking is an action (void return) wrapped + //fast method invoker let you use the return value, that is se to null by default + //this reduce the need for the callre to know if the called method returns + //or not returns a value. + var voidRet = FastMethodInvoker.CallDynamically(_target, "DoSomething", "hello"); + Assert.Equal("hello", _target.Param); + Assert.Null(voidRet); + } + + [Fact] + public void invoker_correctly_invoke_private_method() + { + var voidRet = FastMethodInvoker.CallDynamically(_target, "DoSomethingPrivate", "hello"); + Assert.Equal("hello", _target.Param); + Assert.Null(voidRet); + } + + [Fact] + public void invoker_do_not_have_problem_if_method_does_not_exists() + { + FastMethodInvoker.CallDynamically(_target, "Not_existing_method", "hello"); + Assert.Null(_target.Param); + } + + [Fact] + public void invoker_correctly_invoke_method_with_return_type() + { + var result = (string)FastMethodInvoker.CallDynamically(_target, "DoSomethingReturn", "hello"); + Assert.Equal("hello", _target.Param); + Assert.Equal("processed hello", result); + } + + [Fact] + public void invoker_correctly_dispatch_to_object() + { + var result = (string)FastMethodInvoker.CallDynamically(_target, "DoSomethingWithObjectReturn", new object()); + Assert.Equal("processed", result); + } + + [Fact] + public void invoker_on_optional_public_method_should_not_mask_exception() + { + Assert.Throws(() => + FastMethodInvoker.CallDynamically(_target, "FailPublic", new object()) + ); + } + + [Fact] + public void invoker_on_private_method_should_not_mask_exception() + { + Assert.Throws(() => + FastMethodInvoker.CallDynamically(_target, "FailPrivate", new object()) + ); + } + + //[Fact] + //public void invoker_on_private_methods_should_not_mask_exception() + //{ + // Assert.Throws(() => + // FastMethodInvoker.CallNonPublicIfExists(_target, new[] { "FailPrivate", "FailPrivate" }, new object()) + // ); + //} + } +} \ No newline at end of file diff --git a/src/NStore.Core.Tests/Processing/MethodInvokerTests.cs b/src/NStore.Core.Tests/Processing/MethodInvokerTests.cs index 3cc3e194..347e0b54 100644 --- a/src/NStore.Core.Tests/Processing/MethodInvokerTests.cs +++ b/src/NStore.Core.Tests/Processing/MethodInvokerTests.cs @@ -4,23 +4,6 @@ namespace NStore.Core.Tests.Processing { - internal class TargetException : Exception - { - } - - internal class Target - { - public void FailPublic(object p) - { - throw new TargetException(); - } - - private void FailPrivate(object p) - { - throw new TargetException(); - } - } - public class MethodInvokerTests { private readonly Target _target = new Target(); diff --git a/src/NStore.Core.Tests/Processing/ProcessingTestClasses.cs b/src/NStore.Core.Tests/Processing/ProcessingTestClasses.cs new file mode 100644 index 00000000..ac3cfee6 --- /dev/null +++ b/src/NStore.Core.Tests/Processing/ProcessingTestClasses.cs @@ -0,0 +1,73 @@ +using System; + +namespace NStore.Core.Tests.Processing +{ + internal class TargetException : Exception + { + } + + internal class Target + { + public string Param { get; private set; } + + public void FailPublic(object p) + { + throw new TargetException(); + } + + public void FailPublicConditionally(object p) + { + if (p == null) + { + throw new TargetException(); + } + } + + private void FailPrivate(object p) + { + throw new TargetException(); + } + + /// + /// Simple method that accepts an object and return void. + /// + /// + public void DoSomething(string param) + { + Param = param; + } + + /// + /// Simple method that accepts an object and return void. + /// + /// + private void DoSomethingPrivate(string param) + { + Param = param; + } + + /// + /// Simple method that accepts an object and return void. + /// + /// + public string DoSomethingReturn(string param) + { + Param = param; + return $"processed {param}"; + } + + /// + /// Simple method that accepts an object and return void. + /// + /// + public string DoSomethingWithObjectReturn(object param) + { + return "processed"; + } + + public object CallDoSomethingReturn(string param) + { + return DoSomethingReturn(param); + } + } +} diff --git a/src/NStore.Core/NStore.Core.csproj b/src/NStore.Core/NStore.Core.csproj index 2de46fb8..6120308a 100644 --- a/src/NStore.Core/NStore.Core.csproj +++ b/src/NStore.Core/NStore.Core.csproj @@ -6,12 +6,12 @@ false https://github.com/ProximoSrl/NStore https://github.com/ProximoSrl/NStore - true + true true snupkg - true - True - True + true + True + True Git LICENSE.md NStore.Core @@ -32,5 +32,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + \ No newline at end of file diff --git a/src/NStore.Core/Processing/FastMethodInvoker.cs b/src/NStore.Core/Processing/FastMethodInvoker.cs new file mode 100644 index 00000000..740627bd --- /dev/null +++ b/src/NStore.Core/Processing/FastMethodInvoker.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace NStore.Core.Processing +{ + public static class FastMethodInvoker + { + static BindingFlags NonPublic = BindingFlags.NonPublic | BindingFlags.Instance; + static BindingFlags Public = BindingFlags.Public | BindingFlags.Instance; + + public static object CallDynamically(object instance, string methodName, object @parameter) + { + var paramType = @parameter.GetType(); + var cacheKey = $"{instance.GetType().FullName}/{methodName}/{paramType.FullName}"; + if (!lcgCacheFunction.TryGetValue(cacheKey, out var caller)) + { + var mi = instance.GetType().GetMethod( + methodName, + Public | NonPublic, + null, + new Type[] { paramType }, + null + ); + + if (mi != null) + { + if (mi.ReturnType == typeof(void)) + { + caller = ReflectAction(instance.GetType(), mi); + } + else + { + caller = ReflectFunction(instance.GetType(), mi); + } + } + else + { + caller = null; + } + + lcgCacheFunction[cacheKey] = caller; + } + if (caller != null) + { + return caller(instance, @parameter); + } + + return null; + } + + /// + /// Cache of appliers, for each domain object I have a dictionary of actions + /// + private static ConcurrentDictionary> lcgCacheFunction = new ConcurrentDictionary>(); + + public static Func ReflectAction(Type objType, MethodInfo methodinfo) + { + DynamicMethod retmethod = new DynamicMethod( + "Invoker" + methodinfo.Name, + (Type)null, + new Type[] { typeof(Object), typeof(Object) }, + objType, + true); //methodinfo.GetParameters().Single().ParameterType + ILGenerator ilgen = retmethod.GetILGenerator(); + ilgen.Emit(OpCodes.Ldarg_0); + ilgen.Emit(OpCodes.Castclass, objType); + ilgen.Emit(OpCodes.Ldarg_1); + ilgen.Emit(OpCodes.Callvirt, methodinfo); + ilgen.Emit(OpCodes.Ret); + + //To have similar functionalities we simply wrap action invocation in another function that + //simply return null. Caller can simply ignore the return value. + var action = (Action)retmethod.CreateDelegate(typeof(Action)); + return (a, b) => { action(a, b); return null; }; + } + + public static Func ReflectFunction(Type objType, MethodInfo methodinfo) + { + DynamicMethod retmethod = new DynamicMethod( + "Invoker" + methodinfo.Name, + typeof(Object), + new Type[] { typeof(Object), typeof(Object) }, + objType, + true); //methodinfo.GetParameters().Single().ParameterType + ILGenerator ilgen = retmethod.GetILGenerator(); + ilgen.Emit(OpCodes.Ldarg_0); + ilgen.Emit(OpCodes.Castclass, objType); + ilgen.Emit(OpCodes.Ldarg_1); + ilgen.Emit(OpCodes.Callvirt, methodinfo); + ilgen.Emit(OpCodes.Ret); + return (Func)retmethod.CreateDelegate(typeof(Func)); + } + } +} \ No newline at end of file diff --git a/src/NStore.Domain.Tests/NStore.Domain.Tests.csproj b/src/NStore.Domain.Tests/NStore.Domain.Tests.csproj index 4a066901..eda515ce 100644 --- a/src/NStore.Domain.Tests/NStore.Domain.Tests.csproj +++ b/src/NStore.Domain.Tests/NStore.Domain.Tests.csproj @@ -9,16 +9,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/NStore.Persistence.LiteDB.Tests/NStore.Persistence.LiteDB.Tests.csproj b/src/NStore.Persistence.LiteDB.Tests/NStore.Persistence.LiteDB.Tests.csproj index a7d5ecbc..ebcffa09 100644 --- a/src/NStore.Persistence.LiteDB.Tests/NStore.Persistence.LiteDB.Tests.csproj +++ b/src/NStore.Persistence.LiteDB.Tests/NStore.Persistence.LiteDB.Tests.csproj @@ -25,16 +25,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/NStore.Persistence.Mongo.Tests/NStore.Persistence.Mongo.Tests.csproj b/src/NStore.Persistence.Mongo.Tests/NStore.Persistence.Mongo.Tests.csproj index 7b91aa5b..83c41fdf 100644 --- a/src/NStore.Persistence.Mongo.Tests/NStore.Persistence.Mongo.Tests.csproj +++ b/src/NStore.Persistence.Mongo.Tests/NStore.Persistence.Mongo.Tests.csproj @@ -27,16 +27,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/NStore.Persistence.MsSql.Tests/NStore.Persistence.MsSql.Tests.csproj b/src/NStore.Persistence.MsSql.Tests/NStore.Persistence.MsSql.Tests.csproj index 68fece2f..a4054948 100644 --- a/src/NStore.Persistence.MsSql.Tests/NStore.Persistence.MsSql.Tests.csproj +++ b/src/NStore.Persistence.MsSql.Tests/NStore.Persistence.MsSql.Tests.csproj @@ -26,19 +26,19 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/NStore.Persistence.Sqlite.Tests/NStore.Persistence.Sqlite.Tests.csproj b/src/NStore.Persistence.Sqlite.Tests/NStore.Persistence.Sqlite.Tests.csproj index 490ae048..6feb6717 100644 --- a/src/NStore.Persistence.Sqlite.Tests/NStore.Persistence.Sqlite.Tests.csproj +++ b/src/NStore.Persistence.Sqlite.Tests/NStore.Persistence.Sqlite.Tests.csproj @@ -29,18 +29,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/NStore.Persistence.Tests/NStore.Persistence.Tests.csproj b/src/NStore.Persistence.Tests/NStore.Persistence.Tests.csproj index 6036a23f..4dee06f1 100644 --- a/src/NStore.Persistence.Tests/NStore.Persistence.Tests.csproj +++ b/src/NStore.Persistence.Tests/NStore.Persistence.Tests.csproj @@ -10,7 +10,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -18,9 +18,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/NStore.Sample.Tests/NStore.Sample.Tests.csproj b/src/NStore.Sample.Tests/NStore.Sample.Tests.csproj index 8de20c96..df35d3a9 100644 --- a/src/NStore.Sample.Tests/NStore.Sample.Tests.csproj +++ b/src/NStore.Sample.Tests/NStore.Sample.Tests.csproj @@ -8,16 +8,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive