example - C#Dynamic Event Subscription
eventargs c# example (9)
¿Cómo se suscribiría dinámicamente a un evento C # para que, dada una instancia de objeto y un nombre de cadena que contenga el nombre del evento, se suscriba a ese evento y haga algo (escriba a la consola por ejemplo) cuando se haya disparado?
Parecería que usar Reflection esto no es posible y me gustaría evitar tener que usar Reflection.Emit si es posible, ya que actualmente (para mí) parece ser la única forma de hacerlo.
/ EDITAR: No sé la firma del delegado que se necesita para el evento, este es el núcleo del problema
/ EDIT 2: aunque la contravariancia del delegado parece un buen plan, no puedo hacer la suposición necesaria para usar esta solución
¿Te refieres a algo como:
//reflect out the method to fire as a delegate
EventHandler eventDelegate =
( EventHandler ) Delegate.CreateDelegate(
typeof( EventHandler ), //type of event delegate
objectWithEventSubscriber, //instance of the object with the matching method
eventSubscriberMethodName, //the name of the method
true );
Esto no hace la suscripción, pero le dará el método para llamar.
Editar:
La publicación fue aclarada después de esta respuesta, mi ejemplo no ayudará si no sabes el tipo.
Sin embargo, todos los eventos en .Net deben seguir el patrón de evento predeterminado, de modo que, mientras lo haya seguido, esto funcionará con el EventHandler básico.
Es posible suscribirse a un evento usando Reflection
var o = new SomeObjectWithEvent;
o.GetType().GetEvent("SomeEvent").AddEventHandler(...);
http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx
Ahora aquí va a ser el problema que tendrás que resolver. Los delegados necesarios para cada controlador de eventos tendrán diferentes firmas. Tendrá que buscar formas de crear dinámicamente estos métodos, lo que probablemente signifique Reflection.Emit, o tendrá que limitarse a un determinado delegado para que pueda manejarlo con un código compilado.
Espero que esto ayude.
Este método agrega a un evento, un controlador dinámico que llama a un método OnRaised
, pasando los parámetros del evento como una matriz de objetos:
void Subscribe(object source, EventInfo ev)
{
var eventParams = ev.EventHandlerType.GetMethod("Invoke").GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
var eventHandler = Expression.Lambda(ev.EventHandlerType,
Expression.Call(
instance: Expression.Constant(this),
method: typeof(EventSubscriber).GetMethod(nameof(OnRaised), BindingFlags.NonPublic | BindingFlags.Instance),
arg0: Expression.Constant(ev.Name),
arg1: Expression.NewArrayInit(typeof(object), eventParams.Select(p => Expression.Convert(p, typeof(object))))),
eventParams);
ev.AddEventHandler(source, eventHandler.Compile());
}
OnRaised
tiene esta firma:
void OnRaised(string name, object[] parameters);
Hace poco escribí una serie de publicaciones de blog que describían eventos de pruebas unitarias, y una de las técnicas que analizo describe la suscripción a eventos dinámicos. Usé la reflexión y MSIL (emisión de código) para los aspectos dinámicos, pero todo está muy bien. Usando la clase DynamicEvent, los eventos se pueden suscribir dinámicamente de la siguiente manera:
EventPublisher publisher = new EventPublisher();
foreach (EventInfo eventInfo in publisher.GetType().GetEvents())
{
DynamicEvent.Subscribe(eventInfo, publisher, (sender, e, eventName) =>
{
Console.WriteLine("Event raised: " + eventName);
});
}
Una de las características del patrón que implementé fue que inyecta el nombre del evento en la llamada al controlador de eventos para que sepa qué evento se ha presentado. Muy útil para pruebas unitarias.
El artículo del blog es bastante extenso ya que describe una técnica de prueba de unidad de evento, pero se proporcionan un código fuente completo y pruebas, y en la última publicación se detalla una descripción detallada de cómo se implementó la suscripción dinámica al evento.
http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/
Lo que desea se puede lograr usando la inyección de dependencia. Por ejemplo, el bloque de aplicaciones de interfaz de usuario compuesto de Microsoft hace exactamente lo que describiste
No es una solución completamente general, pero si todos sus eventos son de la forma void Foo (objeto o, T args), donde T deriva de EventArgs, entonces puede usar delegate contravariance para salirse con la suya. Así (donde la firma de KeyDown no es la misma que la de Click):
public Form1()
{
Button b = new Button();
TextBox tb = new TextBox();
this.Controls.Add(b);
this.Controls.Add(tb);
WireUp(b, "Click", "Clickbutton");
WireUp(tb, "KeyDown", "Clickbutton");
}
void WireUp(object o, string eventname, string methodname)
{
EventInfo ei = o.GetType().GetEvent(eventname);
MethodInfo mi = this.GetType().GetMethod(methodname, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
Delegate del = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);
ei.AddEventHandler(o, del);
}
void Clickbutton(object sender, System.EventArgs e)
{
MessageBox.Show("hello!");
}
Pruebe LinFu: tiene un controlador de eventos universal que le permite vincularse a cualquier evento en tiempo de ejecución. Por ejemplo, aquí está usted, puede vincular un controlador al evento Click de un botón dinámico:
// Note: The CustomDelegate signature is defined as: // public delegate object CustomDelegate(params object[] args); CustomDelegate handler = delegate { Console.WriteLine("Button Clicked!"); return null; }; Button myButton = new Button(); // Connect the handler to the event EventBinder.BindToEvent("Click", myButton, handler);
LinFu le permite vincular a sus controladores a cualquier evento, independientemente de la firma del delegado. ¡Disfrutar!
Puede encontrarlo aquí: http://www.codeproject.com/KB/cs/LinFuPart3.aspx
Puede compilar árboles de expresiones para usar métodos vacíos sin ningún argumento como controladores de eventos para eventos de ningún tipo. Para acomodar otros tipos de controladores de eventos, debe asignar los parámetros del manejador de eventos a los eventos de alguna manera.
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class ExampleEventArgs : EventArgs
{
public int IntArg {get; set;}
}
class EventRaiser
{
public event EventHandler SomethingHappened;
public event EventHandler<ExampleEventArgs> SomethingHappenedWithArg;
public void RaiseEvents()
{
if (SomethingHappened!=null) SomethingHappened(this, EventArgs.Empty);
if (SomethingHappenedWithArg!=null)
{
SomethingHappenedWithArg(this, new ExampleEventArgs{IntArg = 5});
}
}
}
class Handler
{
public void HandleEvent() { Console.WriteLine("Handler.HandleEvent() called.");}
public void HandleEventWithArg(int arg) { Console.WriteLine("Arg: {0}",arg); }
}
static class EventProxy
{
//void delegates with no parameters
static public Delegate Create(EventInfo evt, Action d)
{
var handlerType = evt.EventHandlerType;
var eventParams = handlerType.GetMethod("Invoke").GetParameters();
//lambda: (object x0, EventArgs x1) => d()
var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x"));
var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"));
var lambda = Expression.Lambda(body,parameters.ToArray());
return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
}
//void delegate with one parameter
static public Delegate Create<T>(EventInfo evt, Action<T> d)
{
var handlerType = evt.EventHandlerType;
var eventParams = handlerType.GetMethod("Invoke").GetParameters();
//lambda: (object x0, ExampleEventArgs x1) => d(x1.IntArg)
var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")).ToArray();
var arg = getArgExpression(parameters[1], typeof(T));
var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"), arg);
var lambda = Expression.Lambda(body,parameters);
return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
}
//returns an expression that represents an argument to be passed to the delegate
static Expression getArgExpression(ParameterExpression eventArgs, Type handlerArgType)
{
if (eventArgs.Type==typeof(ExampleEventArgs) && handlerArgType==typeof(int))
{
//"x1.IntArg"
var memberInfo = eventArgs.Type.GetMember("IntArg")[0];
return Expression.MakeMemberAccess(eventArgs,memberInfo);
}
throw new NotSupportedException(eventArgs+"->"+handlerArgType);
}
}
static class Test
{
public static void Main()
{
var raiser = new EventRaiser();
var handler = new Handler();
//void delegate with no parameters
string eventName = "SomethingHappened";
var eventinfo = raiser.GetType().GetEvent(eventName);
eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,handler.HandleEvent));
//void delegate with one parameter
string eventName2 = "SomethingHappenedWithArg";
var eventInfo2 = raiser.GetType().GetEvent(eventName2);
eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,handler.HandleEventWithArg));
//or even just:
eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,()=>Console.WriteLine("!")));
eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,i=>Console.WriteLine(i+"!")));
raiser.RaiseEvents();
}
}
public TestForm()
{
Button b = new Button();
this.Controls.Add(b);
MethodInfo method = typeof(TestForm).GetMethod("Clickbutton",
BindingFlags.NonPublic | BindingFlags.Instance);
Type type = typeof(EventHandler);
Delegate handler = Delegate.CreateDelegate(type, this, method);
EventInfo eventInfo = cbo.GetType().GetEvent("Click");
eventInfo.AddEventHandler(b, handler);
}
void Clickbutton(object sender, System.EventArgs e)
{
// Code here
}