Last active
June 10, 2022 03:05
-
-
Save huoshan12345/c42de446a23aa9a17fb6abf905479f25 to your computer and use it in GitHub Desktop.
Interface Base Invocation
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
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