Skip to content

Commit

Permalink
Starting of fast method invoker.
Browse files Browse the repository at this point in the history
  • Loading branch information
alkampfergit committed Sep 18, 2023
1 parent e200b08 commit 187790a
Show file tree
Hide file tree
Showing 13 changed files with 277 additions and 45 deletions.
6 changes: 3 additions & 3 deletions src/NStore.Core.Tests/NStore.Core.Tests.csproj
Expand Up @@ -17,16 +17,16 @@
<Compile Include="..\TestGlobalSuppressions.cs" Link="TestGlobalSuppressions.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="xunit" Version="2.5.0" />
<PackageReference Include="xunit" Version="2.5.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0-beta1-build3642" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
77 changes: 77 additions & 0 deletions 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<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())
// );
//}
}
}
17 changes: 0 additions & 17 deletions src/NStore.Core.Tests/Processing/MethodInvokerTests.cs
Expand Up @@ -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();
Expand Down
73 changes: 73 additions & 0 deletions 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();
}

/// <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);
}
}
}
10 changes: 6 additions & 4 deletions src/NStore.Core/NStore.Core.csproj
Expand Up @@ -6,12 +6,12 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<PackageProjectUrl>https://github.com/ProximoSrl/NStore</PackageProjectUrl>
<RepositoryUrl>https://github.com/ProximoSrl/NStore</RepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">True</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">True</ContinuousIntegrationBuild>
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">True</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">True</ContinuousIntegrationBuild>
<RepositoryType>Git</RepositoryType>
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
<AssemblyName>NStore.Core</AssemblyName>
Expand All @@ -32,5 +32,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
</ItemGroup>
</Project>
97 changes: 97 additions & 0 deletions 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;
}

/// <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>));
}
}
}
6 changes: 3 additions & 3 deletions src/NStore.Domain.Tests/NStore.Domain.Tests.csproj
Expand Up @@ -9,16 +9,16 @@
<Compile Include="..\TestGlobalSuppressions.cs" Link="TestGlobalSuppressions.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="xunit" Version="2.5.0" />
<PackageReference Include="xunit" Version="2.5.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0-beta1-build3642" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Expand Up @@ -25,16 +25,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="xunit" Version="2.5.0" />
<PackageReference Include="xunit" Version="2.5.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0-beta1-build3642" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Expand Up @@ -27,16 +27,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="xunit" Version="2.5.0" />
<PackageReference Include="xunit" Version="2.5.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0-beta1-build3642" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down

0 comments on commit 187790a

Please sign in to comment.