Tomer Gabel's annoying spot on the 'net RSS 2.0
# Tuesday, October 25, 2005

So I found myself in the predicament where a particular class I was using had multiple event sources and I was attempting to fire those events. I encountered two issues, the first of which was that I was trying to invoke the delegates from an outside class, which is impossible even though they're public:

An event can be used as the left-hand operand of the += and -= operators (Section 7.13.3). These operators are used, respectively, to attach event handlers to or to remove event handlers from an event, and the access modifiers of the event control the contexts in which such operations are permitted.

Since += and -= are the only operations that are permitted on an event outside the type that declares the event, external code can add and remove handlers for an event, but cannot in any other way obtain or modify the underlying list of event handlers.

(via MSDN)

This means I had to implement an OnEvent() triger method on the provider class for all events, even though the provider class was intended merely as a container and the events were intended to be fired elsewhere (think of a remotable event container and a seperate execution piepline). This meant I had to provide a lot of boiler-plate code in both consumer and provider classes to make use of these events. I would've written a generic method to handle this which accepts a Delegate parameter; enter the second issue: you'll notice that a non-specialized delegate (Delegate as opposed to, say, an instance of AsyncDelegate) does not have a BeginInvoke method. After a little research I've found some inspiration in Eric Gunnerson and Juval Lowy's classic TechNet presentation on C# best practices, however that particular implementation wasn't convenient for my uses, nor was it compliant with .NET 1.1 specifications (which require that you call EndInvoke on any asynchronous invocation to avoid resource leaks). I eventually went on to write a class that'll do the work for me.

The basic idea is to call the delegate's BeginInvoke and EndInvoke methods via Reflection. To invoke a method via Reflection you (obviously) require an invocation target; this wouldn't be an issue if the target hadn't been an event. Apparently the EventInfo class returned by Type.GetEvent() has no provision for obtaining an actual instance; instead, you have to obtain the event instance as though it were a field. Check out the following code:

// Gather requisite Reflection data

private Type m_handler;
private object m_eventInstance;

...

m_eventInstance = target.GetType().GetField( eventName, BindingFlags.Instance | BindingFlags.NonPublic ).GetValue( target );
m_handler       = target.GetType().GetEvent( eventName, BindingFlags.Instance | BindingFlags.Public    ).EventHandlerType;

// Call BeginInvoke

m_handler.InvokeMember(
"BeginInvoke",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod,
null,
m_eventInstance,
paraList.ToArray()
);

You can find a complete AsyncHelper class and associated (very minimal) example code here. Note that at current delegates with out or ref parameters are not supported; these could be inferred by Reflection and added to the EndInvoke method call, but I fail to see how calling such delegates asynchronously in a generic fashion would be useful.

Tuesday, October 25, 2005 5:59:40 PM (Jerusalem Standard Time, UTC+02:00)  #    -
Development
Tracked by:
http://www.ayende.com/Blog/2005/10/25/GenericFiringOfAsynchoronousEvents.aspx [Pingback]
Me!
Send mail to the author(s) Be afraid.
Archive
<October 2024>
SunMonTueWedThuFriSat
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789
All Content © 2024, Tomer Gabel
Based on the Business theme for dasBlog created by Christoph De Baene (delarou)