|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Article and Code Update (16-07-2008)I've updated the article and code based on reader comments—thank you very much for reading and voting! The original event tracer class would trace only events that followed the .NET Framework design pattern for events—that's most of the events you'll encounter but not all of the ones that C# allows. I've added a second event tracer class that will trace many of the rest of the events you might find. I've also improved the robustness of the class. Contents
IntroductionThe events of any object can be traced, by use of a single class, through the use of .NET Reflection. Have you tried to understand the patterns of raised events of a complicated object? I needed to work with a very capable, very full-featured grid control from a ISV's popular control collection. It wasn't very long before I was very puzzled trying to figure out which of the many events provided by the control were the ones I wanted, and in which order they were raised. I started to write individual event handlers for the events I was interested in, but there were so many I thought I'd be better off generating tracing event handlers for all the events using a nice editor macro. But it turned out there were over 260 events for this grid control, and they were declared at various levels of the class hierarchy. I looked for a better way. .NET Reflection turned out to be a very easy way to get access to the definition of the class I was interested in—or any other class for that matter. It is a simple process to get all the events that a class declares, and then to attach handlers to any or all of the events. This article presents the simple-but-general event hooking/tracing class I built, which is provided (with a demo) as a download. Usage of the Tracer ClassTo use the
The event handler must match the following public delegate void OnEventHandler( object sender,
object target,
string eventName,
EventArgs e );
The So, for example, a simple trace routine may look like this: private void OnEvent( object sender,
object target,
string eventName,
EventArgs e )
{
string s = String.Format( "{0} - args {1} - sender {2} - target {3}",
eventName,
e.ToString( ),
sender ?? "null",
target ?? "null" );
System.Diagnostics.Trace.TraceInformation( s );
}
Now all that is left is to create an event EventTracer.Tracer tracer = new EventTracer.Tracer( targetObject, OnEvent );
tracer.HookAllEvents( );
Alternatively, you could just hook specific events by name: tracer.UnhookAllEvents( );
tracer.HookEvent( "Click" );
tracer.HookEvent( "DoubleClick" );
The Order In Which Events Occur - Does It Really Trace ALL Events?It is probably the case that at least some of the events you hook with the tracer you're already handling in your application. In that case, does the tracer's event handler execute before or after your event handler? For events without user-specified accessors ( (If the class implements its own Well, actually, no. The namespace System {
public delegate void EventHandler( object sender, EventArgs e );
}
The event's public class MyEventArgs : EventArgs {
...
}
public event EventHandler<MyEventArgs> MyEvent;
But even though all of the events in the .NET Framework meet this pattern, and thus can be hooked by the I've implemented a second class, public delegate void OnEventHandler( object target,
string eventName,
object[] parameters );
When this
The Tracer Class ImplementationThere are two important aspects of the implementation of the
Getting the Events that the Target Object OffersGetting the events couldn't be easier. The Since we're interested in all of the EventInfo[] events = m_target.GetType().GetEvents( BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.Public |
BindingFlags.FlattenHierarchy );
A second call to Adding an Event HandlerNow, to actually hook the event, one only needs to create a Therefore, the Here's how that works, where EventProxy proxy = new EventProxy( this, m_target, eventInfo.Name );
MethodInfo handler = typeof( EventProxy ).GetMethod( "OnEvent" );
Delegate d = Delegate.CreateDelegate( eventInfo.EventHandlerType,
proxy,
handler );
eventInfo.AddEventHandler( m_target, d );
Note that when creating the In .NET 2.0, the CLR changed so that methods are matched to signatures with looser rules called contravariance, which allows a "more general" method to be made into a public void OnEvent( object sender, EventArgs e );
... will match any event that implements the .NET Framework's standard design pattern for events. In the actual implementation, the Handling Other Kinds of EventsI implemented classTracerEx to handle more kinds of events than just those that match the .NET Framework event design pattern. Contravariance is pretty nice when matching methods to delegates, but it doesn't give you arbitrary wildcards. The number of parameters of the method and delegate must be the same, none of the delegate parameters can be value types, and none of the delegate parameters can be passed as ref parameters or out parameters. (These are limitations defined by the CLR.) To handle events like that, you need to generate code on the fly—and for two different approaches see articles and code by Martin Carolan and "Death_Child". I wanted to avoid that so I was willing to accept some restrictions on the kinds of events that TracerEx could handle. I implemented 11 event handlers, for events with zero to 10 parameters, where all of the event handlers accept parameters of type object:
public void OnEvent0( ) { ... }
public void OnEvent1( object p1 ) { ... }
public void OnEvent2( object p1, object p2 ) { ... }
public void OnEvent3( object p1, object p2, object p3 ) { ... }
...
public void OnEvent10( object p1, object p2, object p3, object p4 ,
object p5, object p6, object p7, object p8, object p9, object p10 ) { ... }
Then, I choose which of these event handlers to pass to Other Implementation DetailsBoth The only other implementation detail that seems worth mentioning is that the The class The Complete Tracer Class Interfacenamespace EventTracer
{
public sealed class Tracer : IDisposable
{
public delegate void OnEventHandler( object sender,
object target,
string eventName,
EventArgs e );
// Create an event tracer on a particular target object, using a
// a particular delegate to handle the traced events.
public Tracer( object target, OnEventHandler handler );
// Property: The type of the target object.
public Type TheType { get; }
// Property: The target object.
public object TheTarget { get; }
// Property: A collection of all the
// traceable events raised by the target object.
public ReadOnlyCollection<string> EventNames { get; }
// Predicate: Returns true iff the parameter is a valid event name.
public bool IsValidEvent( string eventName );
// Property: A collection of all the untraceable events
// raised by the target object.
// These are the events that don't conform to the .NET Framework's event design
// pattern.
public ReadOnlyCollection<string> UntraceableEventNames { get; }
// Property: The number of events currently hooked.
public int EventsHookedCount { get; }
// Predicate: Returns true iff the parameter names a hooked event.
public bool IsHookedEvent( string eventName );
// Action: Hook the named event of the target object. Your event
// handler will be called whenever the target object raises the event.
// (It is harmless to hook an event that is already hooked.)
public void HookEvent( string eventName );
// Action: Hook all events raised by the target object.
public void HookAllEvents( );
// Action: Unhook the named event. Your event handler will no longer
// be called when the target object raises the event. (It is harmless
// to unhook an event that is not hooked.)
public void UnhookEvent( string eventName );
// Action: Unhook all events.
public void UnhookAllEvents();
public void Dispose();
}
}
Related Work
Ending RemarksThis class is very useful, even though it is so simple. I've tested it on .NET 2.0 and .NET 3.0. Try it yourself—and be sure to let me know if you find any problems. In fact, let me know if you like or dislike this article—it's my first for CodeProject. (I appreciate the comments I've already received.) I thank Giorgi Dalakishvili for pointing out the article by Martin Carolan, which I was not aware of, and "Death_Child" for reminding me that the Article Revision History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||