using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; namespace ZeroLevel.Services.Invokation { /// /// Method call wrapper /// public class InvokeWrapper : IInvokeWrapper { /// /// Cahce /// protected readonly Dictionary _invokeCachee = new Dictionary(); #region Static helpers /// /// Creates a compiled expression for a quick method call, returns the identifier of the expression and a delegate for the call. /// /// Wrapped method /// Expression ID and Delegate Tuple protected static Tuple CreateCompiledExpression(MethodInfo method) { var targetArg = Expression.Parameter(typeof(object)); var argsArg = Expression.Parameter(typeof(object[])); var parameters = method.GetParameters(); Expression body = Expression.Call( method.IsStatic ? null : Expression.Convert(targetArg, method.DeclaringType), // the type in which the method is declared method, parameters.Select((p, i) => Expression.Convert(Expression.ArrayIndex(argsArg, Expression.Constant(i)), p.ParameterType))); if (body.Type == typeof(void)) body = Expression.Block(body, Expression.Constant(null)); else if (body.Type.IsValueType) body = Expression.Convert(body, typeof(object)); var identity = CreateMethodIdentity(method.Name, parameters.Select(p => p.ParameterType).ToArray()); return new Tuple(identity.ToString(), Expression.Lambda(body, targetArg, argsArg).Compile()); } /// /// Wraps Delegate Call /// /// Wrapped delegate /// Expression ID and Delegate Tuple protected static Tuple CreateCompiledExpression(Delegate handler) { return CreateCompiledExpression(handler.GetMethodInfo()); } #endregion #region Helpers /// /// ID uniquely identifying method at the type level (but not at the global level) /// /// Method name /// Method Argument Types /// internal static string CreateMethodIdentity(string name, params Type[] argsTypes) { var identity = new StringBuilder(name); identity.Append("("); if (null != argsTypes) { for (var i = 0; i < argsTypes.Length; i++) { identity.Append(argsTypes[i].Name); if ((i + 1) < argsTypes.Length) identity.Append("."); } } identity.Append(")"); return identity.ToString(); } #endregion #region Configure by Type public IEnumerable Configure() { return Configure(typeof(T)); } public IEnumerable Configure(string methodName) { return Configure(typeof(T), methodName); } public IEnumerable Configure(Func filter) { return Configure(typeof(T), filter); } public IEnumerable Configure(Type type) { var result = type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)?.Select(CreateCompiledExpression); Configure(result); return result.Select(r => r.Item1).ToList(); } public IEnumerable Configure(Type type, string methodName) { var result = type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)?.Where(m => m.Name.Equals(methodName)) ?.Select(CreateCompiledExpression); Configure(result); return result.Select(r => r.Item1).ToList(); } public IEnumerable ConfigureGeneric(Type type, string methodName) { var result = type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)?.Where(m => m.Name.Equals(methodName)) ?.Select(method => method.MakeGenericMethod(typeof(T))).Select(CreateCompiledExpression); Configure(result); return result.Select(r => r.Item1).ToList(); } public IEnumerable ConfigureGeneric(Type instanceType, Type genericType, string methodName) { var result = instanceType.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)?.Where(m => m.Name.Equals(methodName)) ?.Select(method => method.MakeGenericMethod(genericType)).Select(CreateCompiledExpression); Configure(result); return result.Select(r => r.Item1).ToList(); } public IEnumerable ConfigureGeneric(Type type, Func filter) { var result = type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)?.Where(filter) ?.Select(method => method.MakeGenericMethod(typeof(T))).Select(CreateCompiledExpression); Configure(result); return result.Select(r => r.Item1).ToList(); } public IEnumerable ConfigureGeneric(Type instanceType, Type genericType, Func filter) { var result = instanceType.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)?.Where(filter) ?.Select(method => method.MakeGenericMethod(genericType)).Select(CreateCompiledExpression); Configure(result); return result.Select(r => r.Item1).ToList(); } public IEnumerable Configure(Type type, Func filter) { var result = type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)?.Where(filter) ?.Select(CreateCompiledExpression); Configure(result); return result.Select(r => r.Item1).ToList(); } #endregion #region Configure by MethodInfo /// /// Cache the specified method /// /// Method /// Call ID public string Configure(MethodInfo method) { var invoke = CreateCompiledExpression(method); Configure(invoke); return invoke.Item1; } /// /// Cache the specified delegate /// /// Delegate /// Call ID public string Configure(Delegate handler) { var invoke = CreateCompiledExpression(handler); Configure(invoke); return invoke.Item1; } public IEnumerable Configure(IEnumerable list) { var result = list?.Select(CreateCompiledExpression); if (null != result) { Configure(result); return result.Select(r => r.Item1).ToList(); } return Enumerable.Empty(); } #endregion #region Configuration /// /// Filling the cache from the list of methods with identifiers /// protected void Configure(IEnumerable> list) { foreach (var invoke in list) { Configure(invoke); } } /// /// Adding a call to the cache /// protected void Configure(Tuple invoke) { _invokeCachee[invoke.Item1] = invoke.Item2; } #endregion #region Invoking /// /// Calling a static method by identifier, if there is no method in the cache, a KeyNotFoundException exception will be thrown /// /// Call ID /// Method Arguments /// Execution result public object InvokeStatic(string identity, object[] args) { if (_invokeCachee.ContainsKey(identity)) { return _invokeCachee[identity](null, args); } throw new KeyNotFoundException(String.Format("Not found method with identity '{0}'", identity)); } /// /// Calling a method by identifier; if there is no method in the cache, KeyNotFoundException will be thrown. /// /// The instance on which the method is called /// Call ID /// Method Arguments /// Execution result public object Invoke(object target, string identity, object[] args) { if (_invokeCachee.ContainsKey(identity)) { return _invokeCachee[identity](target, args); } throw new KeyNotFoundException($"Not found method with identity '{identity}'"); } public object Invoke(object target, string identity) { if (_invokeCachee.ContainsKey(identity)) { return _invokeCachee[identity](target, null); } throw new KeyNotFoundException($"Not found method with identity '{identity}'"); } /// /// Execution of a static cached method /// /// Method name /// Method Arguments /// /// Execution result public object Invoke(string methodName, object[] args) { return InvokeStatic(CreateMethodIdentity(methodName, args.Select(a => a.GetType()).ToArray()), args); } #endregion #region Helpers /// /// Request call id for method /// /// Method name /// Method argument type list /// Call ID public string GetInvokerIdentity(string methodName, params Type[] argsTypes) { return CreateMethodIdentity(methodName, argsTypes); } /// /// Request for delegate to wrap method /// /// Call ID /// Delegate public Invoker GetInvoker(string identity) { if (_invokeCachee.ContainsKey(identity)) { return _invokeCachee[identity]; } return null; } /// /// Request for delegate to wrap method /// /// Method name /// Method argument type list /// Delegate public Invoker GetInvoker(string methodName, params Type[] argsTypes) { return GetInvoker(CreateMethodIdentity(methodName, argsTypes)); } #endregion #region Factories public static IInvokeWrapper Create() { return new InvokeWrapper(); } #endregion } }