abstract class:
public abstract class ReflectionSink : EventSink
{
protected MethodInfo mi;
protected object instance;
protected BindingFlags bindingFlags;
public ReflectionSink(MethodInfo mi)
{
this.mi=mi;
instance=null;
bindingFlags=BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Static;
}
public ReflectionSink(object instance, MethodInfo mi)
{
this.mi=mi;
this.instance=instance;
bindingFlags=BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance;
}
}
maintains information about the reflected method and the instance (if non-static) of the method to invoke and provides two constructors, one to handle static reflected methods and the other to handle reflected methods that are associated with an object instance.
The real work is done in the following for classes, derived from the abstract classes described above. SyncEventHandlerSink
(sorry about the alliteration!) handles invoking the event sink as specified in the System.EventHandler
delegate and consists of the constructor and the Invoke
method, which is very trivial in this case.
public class SyncEventHandlerSink : EventHandlerSink
{
public SyncEventHandlerSink(System.EventHandler eh) : base(eh) {}
public override IAsyncResult Invoke(object sender, object[] args)
{
try
{
eh(sender, new EventArgs(args));
}
catch (Exception e)
{
Dbg.Warn(false, new DbgKey("EvMgrInvokeError"), e.ToString());
}
return null;
}
}
Compare this to the asynchronous version of the EventHandler delegate call. Here we take advantage of a very poorly documented BeginInvoke
method.
public class AsyncEventHandlerSink : EventHandlerSink
{
public AsyncEventHandlerSink(System.EventHandler eh) : base(eh) {}
public override IAsyncResult Invoke(object sender, object[] args)
{
IAsyncResult res=null;
try
{
res=eh.BeginInvoke(sender, new EventArgs(args), null, null);
}
catch (Exception e)
{
Dbg.Warn(false, new DbgKey("EvMgrInvokeError"), e.ToString());
}
return res;
}
}
The synchronous event invocation using reflection isn’t that much more complicated. Here, we use the MethodInfo
class to invoke the event sink. This parameters for this object (instance
and bindingFlags
) have already been set up previously (discussed below).
public class SyncReflectionSink : ReflectionSink
{
public SyncReflectionSink(MethodInfo mi) : base(mi) {}
public SyncReflectionSink(object instance, MethodInfo mi) : base(instance, mi) {}
public override IAsyncResult Invoke(object sender, object[] args)
{
try
{
mi.Invoke(instance, bindingFlags, null, new object[] {sender, new EventArgs(args)}, null);
}
catch (Exception e)
{
Dbg.Warn(false, new DbgKey("EvMgrInvokeError"), e.ToString());
}
return null;
}
}
The most interesting implementation is the asynchronous reflected event sink. This requires using an intermediate delegate so that the event sink can be invoked as a separate thread. The MethodInfo
class doesn’t appear to have a BeginInvoke
as does System.EventHandler
.
public class AsyncReflectionSink : ReflectionSink
{
private delegate void RunAsync(object sender, object[] args);
private RunAsync run;
public AsyncReflectionSink(MethodInfo mi) : base(mi)
{
InitializeAsyncDelegate();
}
public AsyncReflectionSink(object instance, MethodInfo mi) : base(instance, mi)
{
InitializeAsyncDelegate();
}
private void InitializeAsyncDelegate()
{
run=new RunAsync(Run);
}
public override IAsyncResult Invoke(object sender, object[] args)
{
return run.BeginInvoke(sender, args, null, new Object());
}
private void Run(object sender, object[] args)
{
try
{
mi.Invoke(instance, bindingFlags, null, new object[] {sender, new EventArgs(args)}, null);
}
catch (Exception e)
{
Dbg.Warn(false, new DbgKey("EvMgrInvokeError"), e.ToString());
}
}
}
To construct the MethodInfo
object, a private member function parses the name of the event sink method. This name must be of the form:
assembly/namespace.class/method
.
The parser is quite simple:
private static MethodInfo GetMethodInfo(string reflection)
{
MethodInfo mi=null;
try
{
string[] info=reflection.Split(new char[] {'/'});
Assembly mainAssembly=Assembly.LoadFrom(info[0]);
Type[] types=mainAssembly.GetTypes();
Type type=mainAssembly.GetType(info[1]);
MethodInfo[] mis=type.GetMethods();
mi=type.GetMethod(info[2]);
}
catch (Exception e)
{
Dbg.Warn(false, new DbgKey("EvMgrNoMethod"), e.ToString()+"\n"+"Method:"+reflection);
}
return mi;
}
The EventManager
class consists of several overloaded methods for adding event sinks:
public static void AddSyncEventSink(string name, System.EventHandler eh)…
public static void AddAsyncEventSink(string name, System.EventHandler eh)…
public static void AddSyncEventSink(string name, string reflection)…
public static void AddSyncEventSink(string name, object instance, string reflection)…
public static void AddAsyncEventSink(string name, string reflection)…
public static void AddAsyncEventSink(string name, object instance, string reflection)…
which allows both static and non-static (instance based) events sinks to be specified as either synchronous or asynchronous calls.
Similarly, there are several flavors of the Invoke
method based on the information you need to supply:
public static IAsyncResult Invoke(string name)…
public static IAsyncResult Invoke(string name, object sender)…
public static IAsyncResult Invoke(string name, object[] args)…
public static IAsyncResult Invoke(string name, object sender, object[] args)…
For synchronous calls, the Invoke
method always returns null
. For asynchronous calls, the Invoke method returns an IAsyncResult
which can be useful for obtaining thread results.
One caveat here: the way the EventManager
is designed, whether the event sink is called synchronously or asynchronously is determined when the event sink is added to the Event
collection. It would be fairly trivial to expand this object so that the sync/async determination can be made when the event occurs.
Usage
I’ve included in the project a very simple demonstration of the usage:
static void Main()
{
Dbg.Initialize();
Dbg.LogOutput("out.txt");
EventManager.Initialize();
Events evs=new Events();
EventManager.AddAsyncEventSink("event1", new EventHandler(MyApp.Events.Event1));
EventManager.AddSyncEventSink("event2", evs, "csEventManager.exe/MyApp.Events/Event2");
EventManager.AddAsyncEventSink("event3", "csEventManager.exe/MyApp.Events/Event3");
EventManager.Invoke("event1");
EventManager.Invoke("event2");
EventManager.Invoke("event3");
MessageBox.Show("Application will exit when you click on the OK button\nand running threads will terminate.");
Dbg.Close();
}
Instrumentation
Aside from several warnings issued when an exception occurs, the EventManager
logs the event sinks in a text file specified by the Dbg
object (see my other articles):
Debug Logging Initialized: 10/16/2002 5:50:13 PM
EventManager.Invoke: event1
EventManager.Invoke: event2
EventManager.Invoke: event3
Conclusion
In previous programming efforts, I have found that creating an instrumented bridge between GUI generated events and event handlers has been both a productivity enhancement and has helped in debugging complex applications. An event manager can be a general solution which increases the flexibility of your code.