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;
}
Angelina ErinPosted Aug 11, 2011, 12:12 AM
Helpful Article