2021-11-05 02:46:37 +01:00

157 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
namespace astealz.SmartSpawnController.Utils
{
public delegate T PropertyGetter<out T>(object instance);
public delegate void PropertySetter<in T>(object instance, T value);
public delegate T MethodInvoker<in T1, out T>(object instance, T1 arg1);
public delegate void MethodInvoker<in T>(object instance, T value);
// methods in this class creates a dynamic methods (and return a delegate) for quick access to a property or method which cannot be called by a direct call
// this approach is about 5x faster than reflection
static class Emit
{
// delegate for property getter
public static PropertyGetter<T> CreateDynamicPropertyGetter<T>(
PropertyInfo propertyInfo,
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)
{
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo));
if (!typeof(T).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArgumentException($"Property type '{propertyInfo.PropertyType}' does not match return type '{typeof(T)}'");
var getterMethodInfo = propertyInfo.GetGetMethod(true);
if (getterMethodInfo == null)
throw new InvalidOperationException($"Can't find getter for property '{propertyInfo.Name}' in '{propertyInfo.DeclaringType}'");
var dynMethod = new DynamicMethod($"__get_{typeof(T).Name}_prop_{propertyInfo.Name}", typeof(T), new[] { typeof(object) }, propertyInfo.DeclaringType, true);
var ilGen = dynMethod.GetILGenerator();
if (getterMethodInfo.IsStatic)
{
ilGen.Emit(OpCodes.Call, getterMethodInfo);
}
else
{
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
ilGen.Emit(OpCodes.Callvirt, getterMethodInfo);
}
ilGen.Emit(OpCodes.Ret);
return (PropertyGetter<T>)dynMethod.CreateDelegate(typeof(PropertyGetter<T>));
}
// delegate for property setter
public static PropertySetter<T> CreateDynamicPropertySetter<T>(
PropertyInfo propertyInfo,
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)
{
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo));
if (!typeof(T).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArgumentException($"Property type '{propertyInfo.PropertyType}' does not match the type of value '{typeof(T)}'");
var setterMethodInfo = propertyInfo.GetSetMethod(true);
if (setterMethodInfo == null)
throw new InvalidOperationException($"Can't find setter for property '{propertyInfo.Name}' in '{propertyInfo.DeclaringType}'");
var dynMethod = new DynamicMethod($"__set_{typeof(T).Name}_prop_{propertyInfo.Name}", null, new[] { typeof(object), typeof(T) }, propertyInfo.DeclaringType);
var ilGen = dynMethod.GetILGenerator();
if (setterMethodInfo.IsStatic)
{
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Call, setterMethodInfo);
ilGen.Emit(OpCodes.Ret);
}
else
{
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Callvirt, setterMethodInfo);
}
ilGen.Emit(OpCodes.Ret);
return (PropertySetter<T>)dynMethod.CreateDelegate(typeof(PropertySetter<T>));
}
// delegate to invoke a method with one argument and return value
public static MethodInvoker<T1, TR> CreateDynamicMethodInvoker<T1, TR>(MethodInfo methodInfo)
{
if (methodInfo == null)
throw new ArgumentNullException(nameof(methodInfo));
if (!typeof(TR).IsAssignableFrom(methodInfo.ReturnType))
throw new ArgumentException($"Method return type '{methodInfo.ReturnType}' does not match return type '{typeof(TR)}'");
var dynMethod = new DynamicMethod(
$"__invoke_{typeof(TR).Name}_method_{methodInfo.Name}", // method name is only for debugging
typeof(TR),
new[] { typeof(object), typeof(T1) }, // add method arguments here (T2, T3, T4...)
methodInfo.DeclaringType,
true);
var ilGen = dynMethod.GetILGenerator();
if (methodInfo.IsStatic)
{
// if target method is static push first argument on stack
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Call, methodInfo);
}
else
{
// otherwise push instance and cast it to type where method is declared
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Castclass, methodInfo.DeclaringType);
// then push method arguments
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Callvirt, methodInfo);
}
ilGen.Emit(OpCodes.Ret);
return (MethodInvoker<T1, TR>)dynMethod.CreateDelegate(typeof(MethodInvoker<T1, TR>));
}
public static MethodInvoker<T> CreateDynamicMethodInvoker<T>(MethodInfo methodInfo)
{
if (methodInfo == null)
throw new ArgumentNullException(nameof(methodInfo));
if (!typeof(void).Equals(methodInfo.ReturnType))
throw new ArgumentException($"Method return type '{methodInfo.ReturnType}' does not match return type 'void'");
var dynMethod = new DynamicMethod(
$"__invoke_void_method_{methodInfo.Name}", // method name is only for debugging
typeof(void),
new[] { typeof(object), typeof(T) }, // add method arguments here (T2, T3, T4...)
methodInfo.DeclaringType,
true);
var ilGen = dynMethod.GetILGenerator();
if (methodInfo.IsStatic)
{
// if target method is static push first argument on stack
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Call, methodInfo);
}
else
{
// otherwise push instance and cast it to type where method is declared
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Castclass, methodInfo.DeclaringType);
// then push method arguments
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Callvirt, methodInfo);
}
ilGen.Emit(OpCodes.Ret);
return (MethodInvoker<T>)dynMethod.CreateDelegate(typeof(MethodInvoker<T>));
}
}
}