How to Change Order of Event Handlers Execution at Run Time


Introduction

In previous article (Advances in .NET Event Handling) we have analyzed problems that occur when a class attempts to audit events raised from an object. The problem outlined in that article is that auditor cannot precisely determine order in which events have been raised because some of the events are recursively raised. In those cases, deepest event raised will be handled by the auditor before outer events, and that is because auditor has subscribed to all events as the last one in the sequence of all handlers.

In this article we shall demonstrate how this problem can be resolved. Generally, .NET Framework does not provide a solution to the problem and what follows here is based on its undocumented features. So take the solution proposed below with caution.

How are Events Implemented in .NET

Declaration of every event in .NET types is based on a delegate type. It is not possible to declare an event without previously defining a delegate type which specifies the signature of the method that can be used to handle the event. This requirement is only of syntactical purpose, rather than functional, and that can be seen if you try to access the event as a kind of a variable. In that case, compile error will be reported, telling that only += and -= operations are allowed on the event.

What happens under the hood when an event is declared is that a private field is created, having the same name as the event and type System.MulticastDelegate. This type is basically used to enlist delegate instances (pointers to functions in C/C++ terms) subscribed to the event, i.e. functions that will be invoked when event is raised. So whenever a += operator is used on the event, new delegate instance is appended at the end of the so-called invoke list of the event. Whenever -= operator is executed, a matching delegate is removed from the invoke list.

The problem about the auditors is that they are typically subscribed to events when other built-in objects have already done that. So auditors, which should be the first one to receive each event before any changes to the system are made, actually fall to be the last ones informed about the event. Even worse, recursive events cause order of invocations of auditor's event handlers to be mixed up, as demonstrated in Advances in .NET Event Handling article.

So we need a method to subscribe auditor at the first position in the invoke list no matter how many other objects have already been subscribed, or otherwise auditor cannot perform its task correctly. The following section will give complete solution to this requirement.

Reordering Invoke List Members in MulticastDelegate Instance

In this section we shall start with a class declaring one event. Here is the definition of that class:

public class EventServer
{
    public delegate void TestEventDelegate();
    public event TestEventDelegate TestEvent;
    public void DoWork()
    {
        if (TestEvent != null)
            TestEvent();
    }
}

This class raises event when DoWork method is invoked. So here is the code which subscribes to the event and causes it to be raised:

class Program
{
    private static void Handler1()
    {
        Console.WriteLine("Handler1");
    }
    private static void Handler2()
    {
        Console.WriteLine("Handler2");
    }
    static void Main(string[] args)
    {

        EventServer es = new EventServer();
        es.TestEvent += new EventServer.TestEventDelegate(Handler1);
        es.TestEvent += new EventServer.TestEventDelegate(Handler2);
 
        es.DoWork();
 
    }
}

This piece of code subscribes two handlers to the event and then invokes the DoWork method, consequently causes event handlers to be invoked. As can be expected, output of this program is:

Handler1
Handler2

What we want to do now is to add another handler, called Audit, which just prints string "Audit", but in such way that it is executed before both Handler1 and Handler2 methods. If we simply subscribe it in the same way as previous two handlers, then it will come at the end of the invoke list, and output of the application will be:

Handler1
Handler2
Audit

So simply subscribing to the event does not suffice. We have to access instance of MulticastDelegate which implements TestEvent and then to reorder its invocation list.

First step in doing so is to find the MulticastDelegate instance:

System.Reflection.BindingFlags bf = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
System.Reflection.FieldInfo field = es.GetType().GetField("TestEvent", bf);
MulticastDelegate dlg = (MulticastDelegate)field.GetValue(es);

This piece of code searches for the instance-level private field called TestEvent. Once found, its value is extracted in the third line and cast to MulticastDelegate. In this way we can easily access delegate's invoke list. However, this code does not work correctly if event is declared in base class, simply because GetField method does not return private fields of parent classes. So we have to walk up the hierarchy programmatically all until the desired field is found. Here is the corrected code:

System.Reflection.BindingFlags bf = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
System.Reflection.FieldInfo field = null;

Type curType = es.GetType();

while (field == null)
{
    field = curType.GetField("TestEvent", bf);
    if (field == null)
        curType = curType.BaseType;
}

MulticastDelegate dlg = (MulticastDelegate)field.GetValue(es);
Delegate[] subscribers = dlg.GetInvocationList();

What happens next is that we are removing all elements of the invoke list from the delegate. Here is the code:

Delegate cur = dlg;
for (int i = 0; i < subscribers.Length; i++)
    cur = Delegate.RemoveAll(cur, subscribers[i]);

Note that this operation does not modify event's invoke list, but we are working on a separate instance all the time. Once all changes are finished, we will set field's value to this multicast delegate and thus new invocation list will take effect.

Now that all existing subscriptions have been removed from the multicast delegate, we are ready to create new invoke list. That will be done in such a way that auditor's handler comes first in the list, and only after it we will enlist previous subscribers in exactly the same order as they used to have:

Delegate[] newSubscriptions = new Delegate[subscribers.Length + 1];
newSubscriptions[0] = new EventServer.TestEventDelegate(Audit);
Array.Copy(subscribers, 0, newSubscriptions, 1, subscribers.Length);

cur = Delegate.Combine(newSubscriptions);

field.SetValue(es, cur);

The last line in this code has effectively returned the multicast delegate reference back into the object, but this new instance will have a modified invoke list compared to previous one.

Here is the complete code which inserts new event handler to the front of the invocation list:

class Program
{
    private static void Handler1()
    {
        Console.WriteLine("Handler1");
    }
    private static void Handler2()
    {
        Console.WriteLine("Handler2");
    }
    private static void Audit()
    {
        Console.WriteLine("Audit");
    }
    static void Main(string[] args)
    {

        DerivedSender es = new DerivedSender();
        es.TestEvent += new EventServer.TestEventDelegate(Handler1);
        es.TestEvent += new EventServer.TestEventDelegate(Handler2);

        System.Reflection.BindingFlags bf = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
        System.Reflection.FieldInfo field = null;

        Type curType = es.GetType();

        while (field == null)
        {
            field = curType.GetField("TestEvent", bf);
            if (field == null)
                curType = curType.BaseType;
        }

        MulticastDelegate dlg = (MulticastDelegate)field.GetValue(es);
        Delegate[] subscribers = dlg.GetInvocationList();

        Delegate cur = dlg;
        for (int i = 0; i < subscribers.Length; i++)
            cur = Delegate.RemoveAll(cur, subscribers[i]);

        Delegate[] newSubscriptions = new Delegate[subscribers.Length + 1];
        newSubscriptions[0] = new EventServer.TestEventDelegate(Audit);
        Array.Copy(subscribers, 0, newSubscriptions, 1, subscribers.Length);

        cur = Delegate.Combine(newSubscriptions);

        field.SetValue(es, cur);
        es.DoWork();
        Console.ReadLine();
 
    }
}

Output of the program now looks like this:

Audit
Handler1
Handler2

And that is exactly what we wanted to see. Although it has come last to subscribe to the event, auditor has eventually been invoked first.

Conclusion

In this article we have shown how we can access and modify invocation list of an event exposed by an arbitrary .NET object. Technique demonstrated above is based on undocumented features of .NET compilers and should be taken with caution. However, this technique has been used by the author in practice because there is no regular, proposed way to achieve the same goal. It is really a shame that .NET languages are hiding events implementation behind syntactical obstacles, although event handling is based on a relatively clearly defined delegate system. It would probably be a better idea to expose delegate interface of all events, so to allow programs to read and/or reorder members of the invocation list. Syntax would then be only of help to forbid callers to bring delegate behind the event in an irregular state (e.g. to set it to null), but otherwise would have no significant role like it has now. Without such help from .NET creators, we will have to use techniques similar to one described in this article to perform subtle tasks as event auditing.

This analysis continues in next article titled Auditing Events in .NET Applications (http://www.c-sharpcorner.com/UploadFile/b81385/8776/), which explains even more general implementations of event handlers in .NET Framework. Please continue reading this series on advances in .NET event handling.


Recommended Free Ebook
Similar Articles