Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e200b08
commit 187790a
Showing
13 changed files
with
277 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
src/NStore.Core.Tests/Processing/FastMethodInvokerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<TargetException>(() => | ||
FastMethodInvoker.CallDynamically(_target, "FailPublic", new object()) | ||
); | ||
} | ||
|
||
[Fact] | ||
public void invoker_on_private_method_should_not_mask_exception() | ||
{ | ||
Assert.Throws<TargetException>(() => | ||
FastMethodInvoker.CallDynamically(_target, "FailPrivate", new object()) | ||
); | ||
} | ||
|
||
//[Fact] | ||
//public void invoker_on_private_methods_should_not_mask_exception() | ||
//{ | ||
// Assert.Throws<TargetException>(() => | ||
// FastMethodInvoker.CallNonPublicIfExists(_target, new[] { "FailPrivate", "FailPrivate" }, new object()) | ||
// ); | ||
//} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
|
||
/// <summary> | ||
/// Simple method that accepts an object and return void. | ||
/// </summary> | ||
/// <param name="param"></param> | ||
public void DoSomething(string param) | ||
{ | ||
Param = param; | ||
} | ||
|
||
/// <summary> | ||
/// Simple method that accepts an object and return void. | ||
/// </summary> | ||
/// <param name="param"></param> | ||
private void DoSomethingPrivate(string param) | ||
{ | ||
Param = param; | ||
} | ||
|
||
/// <summary> | ||
/// Simple method that accepts an object and return void. | ||
/// </summary> | ||
/// <param name="param"></param> | ||
public string DoSomethingReturn(string param) | ||
{ | ||
Param = param; | ||
return $"processed {param}"; | ||
} | ||
|
||
/// <summary> | ||
/// Simple method that accepts an object and return void. | ||
/// </summary> | ||
/// <param name="param"></param> | ||
public string DoSomethingWithObjectReturn(object param) | ||
{ | ||
return "processed"; | ||
} | ||
|
||
public object CallDoSomethingReturn(string param) | ||
{ | ||
return DoSomethingReturn(param); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
|
||
/// <summary> | ||
/// Cache of appliers, for each domain object I have a dictionary of actions | ||
/// </summary> | ||
private static ConcurrentDictionary<string, Func<Object, Object, Object>> lcgCacheFunction = new ConcurrentDictionary<string, Func<Object, Object, Object>>(); | ||
|
||
public static Func<Object, Object, Object> 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<Object, Object>)retmethod.CreateDelegate(typeof(Action<Object, Object>)); | ||
return (a, b) => { action(a, b); return null; }; | ||
} | ||
|
||
public static Func<Object, Object, Object> 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<Object, Object, Object>)retmethod.CreateDelegate(typeof(Func<Object, Object, Object>)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.