Domanda

Come ti iscriveresti dinamicamente a un evento C# in modo che, data un'istanza Object e un nome String contenente il nome dell'evento, ti iscrivi a quell'evento e fai qualcosa (ad esempio scrivi sulla console) quando quell'evento è stato attivato?

Sembrerebbe che usare Reflection non sia possibile e vorrei evitare di dover usare Reflection.Emit se possibile, poiché attualmente (per me) sembra l'unico modo per farlo.

/MODIFICARE: Non conosco la firma del delegato necessaria per l'evento, questo è il nocciolo del problema

/MODIFICA 2: Sebbene la controvarianza dei delegati sembri un buon piano, non posso fare il presupposto necessario per utilizzare questa soluzione

È stato utile?

Soluzione

È possibile compilare alberi delle espressioni per utilizzare metodi void senza argomenti come gestori eventi per eventi di qualsiasi tipo.Per accogliere altri tipi di gestori eventi, è necessario associare in qualche modo i parametri del gestore eventi agli eventi.

 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();
     }
 }

Altri suggerimenti

Non è una soluzione completamente generale, ma se tutti i tuoi eventi sono della forma void foo (oggetto O, t args), in cui T deriva da eventiArgs, puoi usare la contravarianza delegata per cavarsela.In questo modo (dove la firma di KeyDown non è la stessa di 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!");
    }

È possibile iscriversi a un evento utilizzando Reflection

var o = new SomeObjectWithEvent;
o.GetType().GetEvent("SomeEvent").AddEventHandler(...);

http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx

Ora ecco il problema che dovrai risolvere.I delegati richiesti per ciascun gestore eventi avranno firme diverse.Dovrai trovare un modo per creare questi metodi in modo dinamico, il che probabilmente significa Reflection.Emit, oppure dovrai limitarti a un determinato delegato in modo da poterlo gestire con il codice compilato.

Spero che questo ti aiuti.

Prova LinFu: ha un gestore di eventi universale che ti consente di associarti a qualsiasi evento in fase di runtime.Ad esempio, ecco che puoi associare un gestore all'evento Click di un pulsante dinamico:

// 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 ti consente di associare i tuoi gestori a qualsiasi evento, indipendentemente dalla firma del delegato.Godere!

Potete trovare qui:http://www.codeproject.com/KB/cs/LinFuPart3.aspx

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
}

Recentemente ho scritto una serie di post sul blog che descrivono eventi di test unitari e una delle tecniche di cui parlo descrive la sottoscrizione di eventi dinamici.Ho usato la riflessione e MSIL (emissione di codice) per gli aspetti dinamici, ma tutto è ben strutturato.Utilizzando la classe DynamicEvent, gli eventi possono essere iscritti dinamicamente in questo modo:

EventPublisher publisher = new EventPublisher();

foreach (EventInfo eventInfo in publisher.GetType().GetEvents())
{
    DynamicEvent.Subscribe(eventInfo, publisher, (sender, e, eventName) =>
    {
        Console.WriteLine("Event raised: " + eventName);
    });
}

Una delle caratteristiche del pattern che ho implementato è che inserisce il nome dell'evento nella chiamata al gestore dell'evento in modo da sapere quale evento è stato generato.Molto utile per i test unitari.

L'articolo del blog è piuttosto lungo in quanto descrive una tecnica di test unitario degli eventi, ma vengono forniti il ​​codice sorgente completo e i test e una descrizione dettagliata di come è stata implementata la sottoscrizione degli eventi dinamici è dettagliata nell'ultimo post.

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

Ciò che desideri può essere ottenuto utilizzando l'iniezione di dipendenza.Per esempio Blocco dell'app dell'interfaccia utente composita Microsoft fa esattamente quello che hai descritto

Questo metodo aggiunge a un evento, un gestore dinamico che chiama un metodo OnRaised, passando i parametri dell'evento come array di oggetti:

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 ha questa firma:

void OnRaised(string name, object[] parameters);

Intendi qualcosa come:

//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 );

Questo non effettua l'abbonamento, ma fornirà il metodo da chiamare.

Modificare:

Il post è stato chiarito dopo questa risposta, il mio esempio non aiuterà se non conosci il tipo.

Tuttavia, tutti gli eventi in .Net dovrebbero seguire il modello di eventi predefinito, quindi finché lo seguirai funzionerà con EventHandler di base.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top