Last active
June 3, 2024 16:48
-
-
Save Daniel-Miller/902a7b435a7409941696c07eb5dd87e1 to your computer and use it in GitHub Desktop.
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
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
// The original source code for this class is in this repository - https://github.com/gautema/CQRSlite | |
namespace CustomReflection | |
{ | |
public static class DynamicInvoker | |
{ | |
private sealed class CompiledMethodInfo | |
{ | |
private readonly Func<object, object[], object> _func; | |
internal CompiledMethodInfo(MethodInfo methodInfo, Type type) | |
{ | |
var instanceExpression = Expression.Parameter(typeof(object), "instance"); | |
var argumentsExpression = Expression.Parameter(typeof(object[]), "arguments"); | |
var parameterInfos = methodInfo.GetParameters(); | |
var argumentExpressions = new Expression[parameterInfos.Length]; | |
for (var i = 0; i < parameterInfos.Length; ++i) | |
{ | |
var parameterInfo = parameterInfos[i]; | |
argumentExpressions[i] = Expression.Convert(Expression.ArrayIndex(argumentsExpression, Expression.Constant(i)), parameterInfo.ParameterType); | |
} | |
var callExpression = Expression.Call(!methodInfo.IsStatic ? Expression.Convert(instanceExpression, type) : null, methodInfo, argumentExpressions); | |
if (callExpression.Type == typeof(void)) | |
{ | |
var action = Expression.Lambda<Action<object, object[]>>(callExpression, instanceExpression, argumentsExpression).Compile(); | |
_func = (instance, arguments) => | |
{ | |
action(instance, arguments); | |
return null; | |
}; | |
} | |
else | |
{ | |
_func = Expression.Lambda<Func<object, object[], object>>(Expression.Convert(callExpression, typeof(object)), instanceExpression, argumentsExpression).Compile(); | |
} | |
} | |
internal object Invoke(object instance, params object[] arguments) | |
{ | |
return _func(instance, arguments); | |
} | |
} | |
private const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; | |
private static volatile Dictionary<int, CompiledMethodInfo> _cachedMembers = new Dictionary<int, CompiledMethodInfo>(); | |
private static readonly object _lockObj = new object(); | |
internal static object Invoke<T>(this T obj, string methodname, params object[] args) | |
{ | |
var type = obj.GetType(); | |
var hash = Hash(type, methodname, args); | |
var exists = _cachedMembers.TryGetValue(hash, out var method); | |
if (exists) return method?.Invoke(obj, args); | |
lock (_lockObj) | |
{ | |
//Recheck if exist inside lock in case another thread has added it. | |
exists = _cachedMembers.TryGetValue(hash, out method); | |
if (exists) return method?.Invoke(obj, args); | |
var argtypes = GetArgTypes(args); | |
var m = GetMember(type, methodname, argtypes); | |
method = m == null ? null : new CompiledMethodInfo(m, type); | |
var dict = new Dictionary<int, CompiledMethodInfo>(_cachedMembers) {{hash, method}}; | |
_cachedMembers = dict; | |
return method?.Invoke(obj, args); | |
} | |
} | |
private static int Hash(Type type, string methodname, object[] args) | |
{ | |
var hash = 23; | |
hash = hash * 31 + type.GetHashCode(); | |
hash = hash * 31 + methodname.GetHashCode(); | |
for (var index = 0; index < args.Length; index++) | |
{ | |
var argtype = args[index].GetType(); | |
hash = hash * 31 + argtype.GetHashCode(); | |
} | |
return hash; | |
} | |
private static Type[] GetArgTypes(object[] args) | |
{ | |
var argtypes = new Type[args.Length]; | |
for (var i = 0; i < args.Length; i++) | |
{ | |
var argtype = args[i].GetType(); | |
argtypes[i] = argtype; | |
} | |
return argtypes; | |
} | |
private static MethodInfo GetMember(Type type, string name, Type[] argtypes) | |
{ | |
while (true) | |
{ | |
var methods = type.GetMethods(bindingFlags).Where(m => m.Name == name).ToArray(); | |
var member = methods.FirstOrDefault(m => m.GetParameters().Select(p => p.ParameterType).SequenceEqual(argtypes)) ?? | |
methods.FirstOrDefault(m => m.GetParameters().Select(p => p.ParameterType).ToArray().Matches(argtypes)); | |
if (member != null) | |
{ | |
return member; | |
} | |
var t = type.GetTypeInfo().BaseType; | |
if (t == null) | |
{ | |
return null; | |
} | |
type = t; | |
} | |
} | |
private static bool Matches(this Type[] arr, Type[] args) | |
{ | |
if (arr.Length != args.Length) return false; | |
for (var i = 0; i < args.Length; i++) | |
{ | |
if (!arr[i].IsAssignableFrom(args[i])) | |
return false; | |
} | |
return true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment