Если вы уверены, что хотите пойти по пути codegen, возможно, потому что вы хотите поддерживать нестандартные события, вы можете сделать это следующим образом:
Когда вы хотите присоединиться к событию, создайте класс, у которого есть метод, соответствующий типу делегата события. Тип также будет иметь поле, содержащее переданный параметр. (Ближе к вашему дизайну будет поле, которое содержит ссылку на this
экземпляр MyEventTriggeringClass
, но я думаю, что в этом есть смысл.) Это поле устанавливается в конструкторе.
Метод вызовет invokedMethod
, передав parameter
в качестве параметра. (Это означает, что invokedMethod
должен быть общедоступным и может быть сделан статическим, если у вас нет другой причины сохранять нестатичность.)
Когда мы закончим создание класса, создайте его экземпляр, создайте делегат для метода и прикрепите его к событию.
public class MyEventTriggeringClass
{
private static readonly ConstructorInfo ObjectCtor =
typeof(object).GetConstructor(Type.EmptyTypes);
private static readonly MethodInfo ToBeInvoked =
typeof(MyEventTriggeringClass)
.GetMethod("InvokedMethod",
BindingFlags.Public | BindingFlags.Static);
private readonly ModuleBuilder m_module;
public MyEventTriggeringClass()
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("dynamicAssembly"),
AssemblyBuilderAccess.RunAndCollect);
m_module = assembly.DefineDynamicModule("dynamicModule");
}
public void Attach(object source, string @event, object parameter)
{
var e = source.GetType().GetEvent(@event);
if (e == null)
return;
var handlerType = e.EventHandlerType;
var dynamicType = m_module.DefineType("DynamicType" + Guid.NewGuid());
var thisField = dynamicType.DefineField(
"parameter", typeof(object),
FieldAttributes.Private | FieldAttributes.InitOnly);
var ctor = dynamicType.DefineConstructor(
MethodAttributes.Public, CallingConventions.HasThis,
new[] { typeof(object) });
var ctorIL = ctor.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, ObjectCtor);
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
ctorIL.Emit(OpCodes.Stfld, thisField);
ctorIL.Emit(OpCodes.Ret);
var dynamicMethod = dynamicType.DefineMethod(
"Invoke", MethodAttributes.Public, typeof(void),
GetDelegateParameterTypes(handlerType));
var methodIL = dynamicMethod.GetILGenerator();
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldfld, thisField);
methodIL.Emit(OpCodes.Call, ToBeInvoked);
methodIL.Emit(OpCodes.Ret);
var constructedType = dynamicType.CreateType();
var constructedMethod = constructedType.GetMethod("Invoke");
var instance = Activator.CreateInstance(
constructedType, new[] { parameter });
var sink = Delegate.CreateDelegate(
handlerType, instance, constructedMethod);
e.AddEventHandler(source, sink);
}
private static Type[] GetDelegateParameterTypes(Type handlerType)
{
return handlerType.GetMethod("Invoke")
.GetParameters()
.Select(p => p.ParameterType)
.ToArray();
}
public static void InvokedMethod(object parameter)
{
Console.WriteLine("Value of parameter = " + parameter ?? "(null)");
}
}
Это все еще не заботится обо всех возможных событиях, все же. Это потому, что делегат события может иметь тип возвращаемого значения. Это будет означать предоставление возвращаемого типа сгенерированному методу и возвращение некоторого значения (вероятно, default(T)
) из него.
Существует (по крайней мере) одна возможная оптимизация: не создавайте новый тип каждый раз, а кешируйте их. Когда вы пытаетесь присоединиться к событию с той же подписью, что и предыдущая, используйте use его класс.