Tomer Gabel's annoying spot on the 'net RSS 2.0
# Tuesday, 25 October 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, 25 October 2005 17:59:40 (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
<2024 December>
SunMonTueWedThuFriSat
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234
All Content © 2024, Tomer Gabel
Based on the Business theme for dasBlog created by Christoph De Baene (delarou)