C# Dynamic Event Abbonamento

Come dinamica di sottoscrivere un evento di C# in modo che, dato un Oggetto di istanza e di un nome di Stringa contenente il nome dell’evento, è sottoscrivere l’evento e fare qualcosa (scrivere nella console per esempio) quando l’evento è stato licenziato?

Sembrerebbe utilizzo di Riflessione questo non è possibile e vorrei evitare di dover utilizzare la Reflection.Emettono, se possibile, come è attualmente (a me) sembra che l’unico modo di farlo.

/EDIT: non so la firma del delegato necessari per l’evento, questo è il nocciolo del problema

/EDIT 2: anche se delegare la controvarianza mi sembra un buon piano, non riesco a fare il presupposto necessario per utilizzare questa soluzione

InformationsquelleAutor DAC | 2008-09-05

 

9 Replies
  1. 28

    È possibile compilare espressione alberi di utilizzare metodi void senza argomenti, come i gestori di eventi per eventi di qualsiasi tipo. Per ospitare altri eventi gestore tipi, è necessario mappare il gestore di eventi parametri per gli eventi in qualche modo.

     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();
         }
     }
    • Inferno, espressione alberi sono così cool. Ho scritto un codice simile una volta attraverso la Riflessione.Emettono. Che cosa un dolore.
    • Grande pezzo di codice. Si può forse mostrare come cambiare il supporto argomenti? L’ho cambiato per ottenere il metodo con gli argomenti, ma mi da “variabile ‘x’ di tipo ‘System.Stringa’ fatto dall’ambito di applicazione “, ma non è definito” quando cerco di creare il delegato. Grazie
    • Fatto—ho aggiunto un altro esempio.
    • Così come sarebbe questo lavoro per il normale pattern di Eventhandler: (object sender, Eventargs e)?
    • Si dovrebbe aggiungere un sovraccarico, Delegate Create<T1,T2>(EventInfo evt, Action<T1,T2> d).
    • Dire che mi hanno seguito nel mio “Osservabili” eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,i=>Console.WriteLine(i+”!”))); Esattamente come faccio a passare gli argomenti? Presumo che la passo a Creat()? Ma che cosa succede da lì? Grazie.
    • Passare SomethingHappenedWithArg()

  2. 8

    Non è completamente una soluzione generale, ma se tutti gli eventi sono della forma
    void Foo(object o, T args) , dove T deriva da EventArgs, quindi è possibile utilizzare il delegato controvarianza per ottenere via con esso. Come questo (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!");
        }
  3. 3

    È possibile iscriversi a un evento usando la Riflessione

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

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

    Ora qui sta andando essere il problema che si intende risolvere. I delegati necessari per ogni gestore di evento avrà diverse firme. Si sta andando ad avere per trovare la via per creare questi metodi in modo dinamico, il che significa che probabilmente Riflessione.Emettere, o si sta andando ad avere per limitare la vostra auto in un certo delegare in modo che si può gestire con la compilazione del codice.

    Spero che questo aiuta.

  4. 2
    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
    }
  5. 2

    Provare LinFu-è un evento universale gestore che permette di associare ad ogni evento in fase di runtime. Ecco, ad esempio, è possibile associare un gestore per l’evento Click di un pulsante dinamico:

    //Nota: Il CustomDelegate firma è definito come: 
    //public delegate oggetto CustomDelegate(params object[] args); 
    CustomDelegate gestore di delega = 
    { 
    Console.WriteLine("Bottone Cliccato!"); 
    return null; 
    }; 
    
    Pulsante myButton = new Button(); 
    //Connessione al gestore per l'evento 
    EventBinder.BindToEvent("Click", myButton, handler); 
    

    LinFu consente di associare il tuo gestori, per ogni evento, indipendentemente dalla firma del delegato. Godere di!

    Si può trovare qui:
    http://www.codeproject.com/KB/cs/LinFuPart3.aspx

  6. 1

    Recentemente ho scritto una serie di post di blog che descrive l’unità di test di eventi, e una delle tecniche discutere descrive la dinamica di sottoscrizione degli eventi. Ho usato la riflessione e il codice MSIL (codice emittente), per gli aspetti dinamici, ma questo è tutto avvolto bene. Utilizzando il DynamicEvent classe, gli eventi possono essere sottoscritto in modo dinamico 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 modello che ho implementato è stato che inserisce il nome dell’evento in la chiamata al gestore di eventi per sapere quali eventi è stata sollevata. Molto utile per i test di unità.

    Blog l’articolo è piuttosto lungo, in quanto è la descrizione di un evento unità tecnica di test, ma il codice sorgente completo e le prove sono forniti, e una descrizione dettagliata di come evento dinamico di sottoscrizione è stato implementato è dettagliato nel post precedente.

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

    • Applausi per il commento, abbiamo avuto qualche interruzione di ieri dopo la patch il nostro web server.
  7. 0

    Questo metodo aggiunge a un evento, un gestore dinamico che chiama un metodo OnRaised, passando i parametri dell’evento come un 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);
  8. -1

    Vuoi dire 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 fare l’abbonamento, ma darà il metodo da chiamare.

    Edit:

    Post è stato chiarito dopo questa risposta, il mio ad esempio non vi aiuterà se non si conosce il tipo.

    Tuttavia tutti gli eventi .Netto dovrebbe seguire l’evento predefinito modello, così come è stato seguito questo lavoro di base con l’EventHandler.

Lascia un commento