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.