Introduction
In previous article titled "How to Change Order of Event Handlers Execution at
Run Time" (http://www.c-sharpcorner.com/UploadFile/b81385/8741/)
we have provided certain level of insight into implementation of event handling
mechanism in .NET. In this article we are pushing further down into murky depths
of .NET event driven application internals. Several concepts used to implement
event subscriptions in practice (all strictly based on MSDN) will be explained
and their functioning used to implement otherwise hardly implementable entity:
event auditor.
Event auditing is a common task in programming and it boils down to getting
informed about events (all or some) that have been raised and about their exact
order of being raised. First goal can be relatively easily met by simply adding
subscribers to all events of interest. But the second goal may become a source
of trouble because entity which audits events would have to be the first one to
subscribe to all events or otherwise recursive event raising might cause auditor
to receive invocations in reversed order of events.
We have discussed this issue of reversed event handlers invocations in full
detail in previous article titled How to Change Order of Event Handlers
Execution at Run Time and we will not repeat the analysis in this article.
Instead, solution presented there will be pushed further in this article so that
it now covers wider set of classes that can be audited.
Solution presented in previous article is not complete because it covers only
the simplest method in which event handlers are declared. But most of the
classes that expose multiple events (like most of the GUI classes) do not use
this method, but rather employ data structure to store handlers for all their
events at one place. This method is explained in MSDN article
How to: Handle
Multiple Events Using Event Properties, so please refer to that text before
proceeding.
Our previous solution solves only the case when invocation list of the event
handler can be accessed directly by searching for the non-public field with name
equal to event name. However, when class uses collection, like EventHandlerList
class, then we cannot be certain which EventHandler delegate instance is
connected with which event. This is because EventHandler instances are inserted
into collection using System.Object keys which are only known to the class which
implements the events.
In the following sections we will first examine the problem of accessing
EventHandler instances for events, and then we will propose a solution which
allows auditor to insert own event handlers at the beginning of every invocation
list in the system, thus ensuring that all event notifications will be received
by the auditor in their correct order.
Problem Statement
In this section we will try to find all EventHandler instances within a given
object such that they are either declared directly, or stored in an
EventHandlerList instance. We will not cover other endless possibilities, like
using custom collections to store event handlers.
We will start with a custom form which publishes event named CustomEvent, in
addition to all events inherited from System.Windows.Forms.Form class:
class
MyForm: Form
{
public event
EventHandler CustomEvent;
public void
DoWork()
{
if (CustomEvent !=
null)
CustomEvent(this, new
EventArgs());
}
}
This form can be instantiated and events handled in the application like this:
static
void CustomEventHandler(object
sender, EventArgs e)
{
Console.WriteLine("CustomEvent
handled.");
((Form)sender).Text =
"Changed text";
}
static
void FormTextChanged(object
sender, EventArgs e)
{
Console.WriteLine("TextChanged
handled.");
}
static
void FormShown(object
sender, EventArgs e)
{
Console.WriteLine("Shown
handled.");
((MyForm)sender).DoWork();
}
static
void Main(string[]
args)
{
MyForm form = new MyForm();
form.Shown +=
new EventHandler(FormShown);
form.TextChanged += new EventHandler(FormTextChanged);
form.CustomEvent += new EventHandler(CustomEventHandler);
form.ShowDialog();
}
This is simple case in which events
are raised recursively: Shown event causes form's DoWork method to be invoked,
which in turn raises CustomEvent, further causing form's text to be changed.
However, since main function is the only subscriber in the system, all events
will be noted on the output in their correct order. Output of this program looks
like this:
Shown handled.
CustomEvent handled.
TextChanged handled.
Now we can try to build a new class, called MyFormAuditor, which subscribes the
same set of events and simply prints them out to the console. Note that auditor
should not change state of the system because that could cause changes in other
events order and argument values. Auditing class could look like this:
class
MyFormAuditor
{
public MyFormAuditor(MyForm
form)
{
form.CustomEvent += new
EventHandler(CustomEvent);
form.Shown += new
EventHandler(Shown);
form.TextChanged += new
EventHandler(TextChanged);
}
void CustomEvent(object
sender, EventArgs e) {
Console.WriteLine("Audit
CustomEvent."); }
void Shown(object
sender, EventArgs e) {
Console.WriteLine("Audit
Shown."); }
void TextChanged(object
sender, EventArgs e) {
Console.WriteLine("Audit
TextChanged."); }
}
This class can be used to subscribe all events of interest on any instance of
MyForm class, but we must understand that such subscriptions typically come last
in invocation lists, simply because objects are instantiated and linked before
auditor comes into play. Now we can change the main function, so that auditor is
added and linked with the form:
static
void Main(string[]
args)
{
MyForm form = new MyForm();
form.Shown +=
new EventHandler(FormShown);
form.TextChanged += new EventHandler(FormTextChanged);
form.CustomEvent += new EventHandler(CustomEventHandler);
MyFormAuditor auditor = new MyFormAuditor(form);
form.ShowDialog();
}
This is typical situation in
practice – all objects are instantiated and all event handlers subscribed before
auditor is created. Output now looks like this:
Shown handled.
CustomEvent handled.
TextChanged handled.
Audit TextChanged.
Audit CustomEvent.
Audit Shown.
As we can see, auditor has received event notifications in reverse order. This
is consequence of recursive events, i.e. events being raised from inside other
event handlers. In order to correct the issue, we will have to discover where
event handlers are stored in the MyForm instance and then to insert new
delegates at the front of their invocation lists.
Listing Event Handlers
First step in solving the auditing problem is to find EventHandler delegates
contained in target object. As stated above, event handlers are either found
instantiated on their own (default implementation in .NET), or as members of
EventHandlerList instance, which is typical for most of the complex classes. For
instance, all classes deriving from System.ComponentModel.Component class, and
that means Windows forms and controls, use EventHandlerList to store subscribed
event handlers.
In the first case, EventHandler instance is declared as a non-public field with
name equal to corresponding event's name. In the second case, object contains
non-public field named events, of type EventHandlerList, and it also declares
number of static non-public fields of type System.Object, which are used as keys
to the list. In both cases any particular EventHandler instance can be
discovered by examining contained fields, and it is done by the following
function:
static
void ExamineSubscribers(object
target)
{
Hashtable objects =
new Hashtable();
List<EventHandlerList>
handlerLists = new
List<EventHandlerList>();
BindingFlags bf =
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Static;
Type t = target.GetType();
while (t != null)
{
FieldInfo[] fields = t.GetFields(bf);
for (int i = 0;
i < fields.Length; i++)
{
if (fields[i].FieldType ==
typeof(System.Object)
&& fields[i].GetValue(target) != null)
objects.Add(fields[i].GetValue(target), fields[i].Name);
else if
(fields[i].FieldType == typeof(EventHandlerList))
handlerLists.Add((EventHandlerList)fields[i].GetValue(target));
else if
(fields[i].FieldType == typeof(EventHandler)
&& t.GetEvent(fields[i].Name, bf) != null)
{
EventHandler eh = (EventHandler)fields[i].GetValue(target);
Delegate[] invocationList =
eh.GetInvocationList();
if (invocationList.Length > 0)
Console.WriteLine("{0}
event handler(s) directly subscribed to event {1}", invocationList.Length,
fields[i].Name);
}
}
t = t.BaseType;
}
Join the conversation! Your thoughts help the community grow.