I have a WPF control with very poor documentation.
In the codebehind I'd like to reflect over the events that the control fires using GetType().GetEVents()
and add a handler to each one which simply prints out the name of the event.
This would allow me to see what interacting with the control is actually doing.
So far I have:
foreach (var e in GetType().GetEvents())
{
var name = e.Name;
var handler = new Action<object,object>( (o1,o2) =>Console.WriteLine(name));
try
{
e.AddEventHandler(
this,
Delegate.CreateDelegate(
e.EventHandlerType,
handler.Target,
handler.Method
));
}
catch (Exception ex)
{
Console.WriteLine( "Failed to bind to event {0}", e.Name);
}
}
Which seems to work when the event signature is (object,EventArgs)
but fails to bind when on certain other events.
Is there a way to do this without necessarily knowing the signature of the event?
Function Call Binding This type of binding is done during compilation by the compiler and linker. The compiler has full knowledge of the function definition and the places where this function is called in the program. The C language typically uses this type of binding.
n static binding, function calls are resolved at compile time by the compiler itself. The binding of all the static and private functions/methods of a class happens at compile time. In dynamic binding, function calls are resolved at run time. Function overriding in OOP is possible due to dynamic/late binding.
Yes, it is possible (almost everything is possible with C ) however it is usually not covenient, C++ is better suited for such a job.
By default, C++ matches a function call with the correct function definition at compile time. This is called static binding. You can specify that the compiler match a function call with the correct function definition at runtime; this is called dynamic binding.
Look at the example found here: http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx
They use the delegate's Invoke-method to get the signature.
You could use the System.Linq.Expressions.Expression
class to generate dynamic handlers matching the signature of the event - into which you simply place a call to Console.WriteLine
.
The Expression.Lambda
method (have provided a link to the specific overload you'd need) can be used to generate a Func<>
or, more likely, Action<>
of the correct type.
You reflect the delegate type of the event (grabbing it's Invoke
method as mentioned by @Davio) to pull out all the arguments and create ParameterExpression
s for each of those to supply to the lambda method.
Here's a complete solution that you can paste into a standard unit test, I'll explain afterwards in a follow-up edit:
public class TestWithEvents
{
//just using random delegate signatures here
public event Action Handler1;
public event Action<int, string> Handler2;
public void RaiseEvents(){
if(Handler1 != null)
Handler1();
if(Handler2 != null)
Handler2(0, "hello world");
}
}
public static class DynamicEventBinder
{
public static Delegate GetHandler(System.Reflection.EventInfo ev) {
string name = ev.Name;
// create an array of ParameterExpressions
// to pass to the Expression.Lambda method so we generate
// a handler method with the correct signature.
var parameters = ev.EventHandlerType.GetMethod("Invoke").GetParameters().
Select((p, i) => Expression.Parameter(p.ParameterType, "p" + i)).ToArray();
// this and the Compile() can be turned into a one-liner, I'm just
// splitting it here so you can see the lambda code in the Console
// Note that we use the Event's type for the lambda, so it's tightly bound
// to that event.
var lambda = Expression.Lambda(ev.EventHandlerType,
Expression.Call(typeof(Console).GetMethod(
"WriteLine",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(string) },
null), Expression.Constant(name + " was fired!")), parameters);
//spit the lambda out (for bragging rights)
Console.WriteLine(
"Compiling dynamic lambda {0} for event \"{1}\"", lambda, name);
return lambda.Compile();
}
//note - an unsubscribe might be handy - which would mean
//caching all the events that were subscribed for this object
//and the handler. Probably makes more sense to turn this type
//into an instance type that binds to a single object...
public static void SubscribeAllEvents(object o){
foreach(var e in o.GetType().GetEvents())
{
e.AddEventHandler(o, GetHandler(e));
}
}
}
[TestMethod]
public void TestSubscribe()
{
TestWithEvents testObj = new TestWithEvents();
DynamicEventBinder.SubscribeAllEvents(testObj);
Console.WriteLine("Raising events...");
testObj.RaiseEvents();
//check the console output
}
An outline - we start with a type that has some events (I'm using Action
but it should work with anything), and has a method that we can use to test-fire all those events that have subscribers.
then to the DynamicEventBinder
class, which has two methods: GetHandler
- to get a handler for a particular event for a particular type; and SubscribeAllEvents
which binds all those events for a given instance of that type - which simply loops over all the events, calling AddEventHandler
for each, calling GetHandler
to get the handler.
The GetHandler
method is where the meat and bones is - and does exactly as I suggest in the outline.
A delegate type has an Invoke
member compiled into it by the compiler which mirrors the signature of any handler that it can be bound to. So, we reflect that method and get any parameters it has, creating Linq ParameterExpression
instances for each. Naming the parameter is a nicety, it's the type here that matters.
Then we build a single-line lambda, whose body is basically:
Console.WriteLine("[event_name] was fired!");
(Note here that the event's name is pulled into the dynamic code and incorporated into a constant string as far as the code is concerned)
When we build the lambda, we also tell the Expression.Lambda
method the type of delegate we intend to build (bound directly to the delegate type of the event), and by passing the ParameterExpression
array that we created before, it will generate a method which has that many parameters. We use the Compile
method to actually compile the dynamic code, which gives us a Delegate
which we can then use as an argument to AddEventHandler
.
I sincerely hope this explains what we've done - if you've not worked with Expressions and dynamic code before it can be mind-bending stuff. Indeed, some of the people I work with simply call this voodoo.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With