Skip to content

Instantly share code, notes, and snippets.

@huoshan12345
Last active June 10, 2022 03:05
Show Gist options
  • Save huoshan12345/c42de446a23aa9a17fb6abf905479f25 to your computer and use it in GitHub Desktop.
Save huoshan12345/c42de446a23aa9a17fb6abf905479f25 to your computer and use it in GitHub Desktop.
Interface Base Invocation
public static class InterfaceBaseInvocationExtension
{
private readonly record struct InterfaceMethodInfo(Type InstanceType, Type InterfaceType, MethodInfo Method);
private static readonly ConcurrentDictionary<InterfaceMethodInfo, Delegate> _delegates = new();
private readonly struct Unit { }
public static void Base<TInterface>(this TInterface instance, Expression<Action<TInterface>> selector)
{
var (func, args) = GetDynamicMethod<TInterface, Unit>(instance, selector);
((Action<TInterface, object?[]>)func)(instance, args);
}
public static TReturn Base<TInterface, TReturn>(this TInterface instance, Expression<Func<TInterface, TReturn>> selector)
{
var (func, args) = GetDynamicMethod<TInterface, TReturn>(instance, selector);
return ((Func<TInterface, object?[], TReturn>)func)(instance, args);
}
private static (MethodInfo method, IReadOnlyList<Expression> args) GetMethodAndArguments(Expression exp) => exp switch
{
LambdaExpression lambda => GetMethodAndArguments(lambda.Body),
UnaryExpression unary => GetMethodAndArguments(unary.Operand),
MethodCallExpression methodCall => (methodCall.Method!, methodCall.Arguments),
MemberExpression { Member: PropertyInfo prop } => (prop.GetGetMethod(true) ?? throw new MissingMethodException($"No getter in propery {prop.Name}"), Array.Empty<Expression>()),
_ => throw new InvalidOperationException("The expression refers to neither a method nor a readable property.")
};
private static (Delegate, object?[]) GetDynamicMethod<TInterface, TReturn>(TInterface instance, LambdaExpression selector)
{
if (instance == null)
throw new ArgumentNullException(nameof(instance));
if (selector == null)
throw new ArgumentNullException(nameof(selector));
var (method, args) = GetMethodAndArguments(selector);
var evaluatedArguments = args.GetArgumentValues().ToArray();
var func = _delegates.GetOrAdd(new(instance.GetType(), typeof(TInterface), method), k =>
{
var interfaceMethod = GetInterfaceMethod(k);
var dynamicMethod = GetDynamicMethod(k.InterfaceType, interfaceMethod, args.Select(m => m.Type));
var ifReturnVoid = method.ReturnType == typeof(void);
return ifReturnVoid
? dynamicMethod.CreateDelegate<Action<TInterface, object[]>>()
: dynamicMethod.CreateDelegate<Func<TInterface, object[], TReturn>>();
});
return (func, evaluatedArguments);
}
public static IEnumerable<object?> GetArgumentValues(this IEnumerable<Expression> arguments)
{
return arguments.Select(e => e switch
{
ConstantExpression constant => constant.Value,
_ => Expression.Lambda<Func<object>>(Expression.Convert(e, typeof(object))).Compile().Invoke()
});
}
private static MethodInfo GetInterfaceMethod(InterfaceMethodInfo info)
{
var (instanceType, interfaceType, method) = info;
var parameters = method.GetParameters();
var genericArguments = method.GetGenericArguments();
var interfaceMethods = instanceType
.GetInterfaceMap(interfaceType)
.InterfaceMethods
.Where(m => IfMatch(method, genericArguments, parameters, m))
.ToArray();
var interfaceMethod = interfaceMethods.Length switch
{
0 => throw new MissingMethodException($"Can not find method {method.Name} in type {instanceType.Name}"),
> 1 => throw new AmbiguousMatchException($"Found more than one method {method.Name} in type {instanceType.Name}"),
1 when interfaceMethods[0].IsAbstract => throw new InvalidOperationException($"The method {interfaceMethods[0].Name} is abstract"),
_ => interfaceMethods[0]
};
if (method.IsGenericMethod)
interfaceMethod = interfaceMethod.MakeGenericMethod(method.GetGenericArguments());
return interfaceMethod;
}
private static bool IfMatch(MethodInfo method, Type[] genericArguments, ParameterInfo[] parameters, MethodInfo interfaceMethod)
{
var isSameType = method.DeclaringType == interfaceMethod.DeclaringType;
if (isSameType && method.Name != interfaceMethod.Name)
return false;
if (!isSameType && !interfaceMethod.Name.EndsWith("." + method.Name))
return false;
if (method.IsGenericMethod != interfaceMethod.IsGenericMethod)
return false;
if (method.IsGenericMethod)
{
if (method.IsGenericMethod && genericArguments.Length != interfaceMethod.GetGenericArguments().Length)
return false;
interfaceMethod = interfaceMethod.MakeGenericMethod(genericArguments);
}
if (method.ReturnType != interfaceMethod.ReturnType)
return false;
var interfaceMethodParmeters = interfaceMethod.GetParameters();
if (parameters.Length != interfaceMethodParmeters.Length)
return false;
foreach (var (paraType, interfaceParaType) in parameters.Zip(interfaceMethodParmeters).Select(m => (m.First.ParameterType, m.Second.ParameterType)))
{
if (paraType != interfaceParaType)
return false;
}
return true;
}
private static DynamicMethod GetDynamicMethod(Type interfaceType, MethodInfo method, IEnumerable<Type> argumentTypes)
{
var dynamicMethod = new DynamicMethod(
name: "__IL_" + method.Name,
returnType: method.ReturnType,
parameterTypes: new[] { interfaceType, typeof(object[]) },
owner: typeof(object),
skipVisibility: true);
var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
var i = 0;
foreach (var argumentType in argumentTypes)
{
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldelem, typeof(object));
if (argumentType.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, argumentType);
}
++i;
}
il.Emit(OpCodes.Call, method);
il.Emit(OpCodes.Ret);
return dynamicMethod;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment