whatwg - Adding event handlers by using reflection




what is the html living standard (4)

I am adding dynamically controls to page by using LoadControl and Controls.Add. I need somehow to wrap Init and Load event handlers of this loaded controls into my code. So it should be such order of events SomeMyCode() -> Control.Init() -> AnotherMyCode() and the same for Load SomeMyCode() -> Control.Load() -> AnotherMyCode().
My idea was to Get List of Control's event handlers for Init and Load events and add first and last event handers with code I should to run. But I cannot figure out how to do this.


An event is a multi-cast delegate, you would retrieve that event as a type of System.Delegate use Delegate.Combine to combine the two instances and then set the delegate to the combined delegate.

C# has nice shorthand syntax for this:

class SomeClass
{
    public event Action<string> TextEvent;
}

You would write something like this: (I'm feeling a bit lazy and won't check this, you'll have to work out the kinks yourself)

var obj = // instance of SomeClass...
var t = typeof(SomeClass); // you need the type object
var member = t.GetEvent("TextEvent"); 
member.AddEventHandler(obj, new Action<string>(delegate(string s)){}); // done!

Here is a draft of a working solution:

    protected void Page_Load(object sender, EventArgs e)
    {
        Control ctrl = this.LoadControl("WebUserControl1.ascx");
        PropertyInfo propertyInfo = typeof(Control).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
        EventHandlerList eventHandlerList = propertyInfo.GetValue(ctrl, new object[]{}) as EventHandlerList;
        FieldInfo fieldInfo = typeof(Control).GetField("EventLoad", BindingFlags.NonPublic | BindingFlags.Static);

        if(fieldInfo == null)
            return;

        object eventKey = fieldInfo.GetValue(ctrl);
        Delegate eventHandler = eventHandlerList[eventKey] as Delegate;

        foreach(EventHandler item in eventHandler.GetInvocationList()) {
            ctrl.Load -= item;
        }

        ctrl.Load += ctrl_Load;
        foreach (EventHandler item in eventHandler.GetInvocationList()){
            ctrl.Load += item;
        }
        ctrl.Load += ctrl_Load;

        this.Controls.Add(ctrl);
    }

    void ctrl_Load(object sender, EventArgs e)
    {
        //throw new NotImplementedException();
    }
}

You cannot forcibly inject an event handler in front of other handlers already subscribed to an event. If you need method A to be called first, then you'll need to subscribe A first.


Re your comment, that is incredibly hacky. First, you can't even rely on an event having a delegate-field backer; it can be an EventHandlerList or other - which don't nevessarily expose handy hooks short of reversing the removal.

Second (and more important), it breaks every rule of encapsulation.

In general, if you want to be first, the best approach is to override the OnFoo() method. The second-best is to subscribe first.


C# Delgates and events

Here is the code to do it:

 class Program
  {
    static void Main(string[] args)
    {
      // Create publisher.
      var pub = Activator.CreateInstance(typeof(Publisher));
      // Get the event.
      var addEvent = typeof(Publisher).GetEvent("Event");

      // Create subscriber.
      var sub = Activator.CreateInstance(typeof(Subscriber));
      // Get the method.
      var handler = typeof(Subscriber).GetMethod("Handle");
      // Create a valid delegate for it.
      var handlerDelegate = MakeEventHandlerDelegate(handler, sub);

      // Add the event.
      addEvent.AddEventHandler(pub, handlerDelegate);

      // Call the raise method.
      var raise = typeof(Publisher).GetMethod("Raise");
      raise.Invoke(pub, new object[] { "Test Value" });
      Console.ReadLine();
    }

    static Delegate MakeEventHandlerDelegate(MethodInfo methodInfo, object target)
    {
      ParameterInfo[] info = methodInfo.GetParameters();
      if (info.Length != 2)
        throw new ArgumentOutOfRangeException("methodInfo");
      if (!typeof(EventArgs).IsAssignableFrom(info[1].ParameterType))
        throw new ArgumentOutOfRangeException("methodInfo");
      if (info[0].ParameterType != typeof(object))
        throw new ArgumentOutOfRangeException("methodInfo");

      return Delegate.CreateDelegate(typeof(EventHandler<>).MakeGenericType(info[1].ParameterType), target, methodInfo);
    }
  }

  class Args : EventArgs
  {
    public string Value { get; set; }
  }

  class Publisher
  {
    public event EventHandler<Args> Event;

    public void Raise(string value)
    {
      if (Event != null)
      {
        Args a = new Args { Value = value };
        Event(this, a);
      }
    }
  }

  class Subscriber
  {
    public void Handle(object sender, Args args)
    {
      Console.WriteLine("Handle called with {0}.", args.Value);
    }
  }