Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#

An instrumented synchronous/asynchronous event manager utilizing EventHandler and Reflection

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
16 Oct 20026 min read 98K   1K   85  
Implements an instrumented event manager which can be used to invoke event sinks both synchronously and asynchronously. The event sink can be declared using either the System.EventHandler delegate or by reflection.
using System;
using System.Collections;
using System.Reflection;

namespace MyApp
{
	/// <summary>
	/// A wrapper for parameters sent to the event handlers.
	/// </summary>
	public class EventArgs : System.EventArgs
	{
		public object[] args;

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="args">The event's parameters or null.</param>
		public EventArgs(object[] args) {this.args=args;}
	}

	/// <summary>
	/// Abstract base class for all event handlers.
	/// </summary>
	public abstract class EventSink
	{
		/// <summary>
		/// Invokes the event.
		/// </summary>
		/// <param name="sender">The originator of the event.</param>
		/// <param name="args">Parameters to pass to the event handler.</param>
		/// <returns>An IAsyncResult object for asynchronous events, or null if the event is invoked synchronously.</returns>
		public abstract IAsyncResult Invoke(object sender, object[] args);
	}

	/// <summary>
	/// Abstract class that manages events declared by specifying the instance and method that will handle the event.
	/// </summary>
	public abstract class EventHandlerSink : EventSink
	{
		protected EventHandler eh;

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="eh">The method that will handle the event.</param>
		public EventHandlerSink(System.EventHandler eh)
		{
			this.eh=eh;
		}
	}

	/// <summary>
	/// Abstract class that manages events bound at runtime and declared through reflection (the assembly, namespace, instance, and method names)
	/// of the method that will handle the event.
	/// </summary>
	public abstract class ReflectionSink : EventSink
	{
		protected MethodInfo mi;
		protected object instance;
		protected BindingFlags bindingFlags;

		/// <summary>
		/// Constructor using information compiled by the EventManager object.  This method is used for the
		/// runtime binding of static methods.
		/// </summary>
		/// <param name="mi">A MethodInfo object.</param>
		public ReflectionSink(MethodInfo mi)
		{
			this.mi=mi;
			instance=null;
			bindingFlags=BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Static;
		}

		/// <summary>
		/// Constructor using the method's object instance and information compiled by the EventManager object.  This
		/// method is used for the runtime binding of non-static methods.
		/// </summary>
		/// <param name="instance">The instance associated with the reflection method.</param>
		/// <param name="mi">A MethodInfo object.</param>
		public ReflectionSink(object instance, MethodInfo mi)
		{
			this.mi=mi;
			this.instance=instance;
			bindingFlags=BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance;
		}
	}

	/// <summary>
	/// SyncEvent handles the synchronous invocation of events bound using an event delegate.
	/// </summary>
	public class SyncEventHandlerSink : EventHandlerSink
	{
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="eh">The method that will handle the event.</param>
		public SyncEventHandlerSink(System.EventHandler eh) : base(eh)  {}

		/// <summary>
		/// Invoke the synchronous event handler as declared by the delegate.
		/// </summary>
		/// <param name="sender">The originator of the event.</param>
		/// <param name="args">Any parameters passed to the event.</param>
		/// <returns>This method always returns a null because the event is invoked synchronously.</returns>
		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;
		}
	}

	/// <summary>
	/// ReflectionSyncEvent handles the synchronous invocation of events bound by reflection.
	/// </summary>
	public class SyncReflectionSink : ReflectionSink
	{
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="mi">A MethodInfo object.</param>
		public SyncReflectionSink(MethodInfo mi) : base(mi) {}
		
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="instance">The instance associated with the reflection method.</param>
		/// <param name="mi">A MethodInfo object.</param>
		public SyncReflectionSink(object instance, MethodInfo mi) : base(instance, mi) {}

		/// <summary>
		/// Invoke the synchronous event handler as determined by the reflection information.
		/// </summary>
		/// <param name="sender">The originator of the event.</param>
		/// <param name="args">Any parameters passed to the event.</param>
		/// <returns>This method always returns a null because the event is invoked synchronously.</returns>
		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;
		}
	}

	/// <summary>
	/// AsyncEvent handles the asynchronous invocation of events bound using an event delegate.
	/// </summary>
	public class AsyncEventHandlerSink : EventHandlerSink
	{
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="eh">The method that will handle the event.</param>
		public AsyncEventHandlerSink(System.EventHandler eh) : base(eh) {}

		/// <summary>
		/// Invoke the asynchronous event handler as declared by the delegate.
		/// </summary>
		/// <param name="sender">The originator of the event.</param>
		/// <param name="args">Any parameters passed to the event.</param>
		/// <returns>This method always returns an IAsyncResult.</returns>
		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;
		}
	}

	/// <summary>
	/// ReflectionAsyncEvent handles the asynchronous invocation of events bound by reflection.
	/// </summary>
	public class AsyncReflectionSink : ReflectionSink
	{
		/// <summary>
		/// References our internal asynchronous method.  This is necessary so that we can
		/// invoke the reflection method asynchronously.
		/// </summary>
		private delegate void RunAsync(object sender, object[] args);
		private RunAsync run;

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="mi">A MethodInfo object.</param>
		public AsyncReflectionSink(MethodInfo mi) : base(mi)
		{
			InitializeAsyncDelegate();
		}

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="instance">The instance associated with the reflection method.</param>
		/// <param name="mi">A MethodInfo object.</param>
		public AsyncReflectionSink(object instance, MethodInfo mi) : base(instance, mi)
		{
			InitializeAsyncDelegate();
		}

		/// <summary>
		/// Instantiates the delegate for the asynchronous handler.
		/// </summary>
		private void InitializeAsyncDelegate()
		{
			run=new RunAsync(Run);
		}

		/// <summary>
		/// Invokes the reflection method asynchronously.
		/// </summary>
		/// <param name="sender">The originator of the event.</param>
		/// <param name="args">Any parameters passed to the handler.</param>
		/// <returns>This method always returns an IAsyncResult object.</returns>
		public override IAsyncResult Invoke(object sender, object[] args)
		{
			return run.BeginInvoke(sender, args, null, new Object());
		}

		/// <summary>
		/// This method is invoked asynchronously and invokes the reflection method.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		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());
			}
		}
	}

	/// <summary>
	/// A very thin wrapper for the collection of event handlers.
	/// </summary>
	public class EventCollection : SortedList
	{
	}

	/// <summary>
	/// A singleton class.  Manages the invocation of synchronous, asynchronous, delegate and reflect bound
	/// event handlers.
	/// </summary>
	public class EventManager
	{
		static EventCollection evList=new EventCollection();

		/// <summary>
		/// Initializes internal data structures.
		/// </summary>
		public static void Initialize()
		{
			Dbg.Problems.Add(new DbgKey("EvMgrInvokeError"),
				"Cannot invoke event.",
				new String[] {
							   "A system-wide error has occurred."});

			Dbg.Problems.Add(new DbgKey("EvMgrNoEvent"),
				"Missing event.",
				new String[] {
							   "The specified event does not exist in the event manager event collection."});

			Dbg.Problems.Add(new DbgKey("EvMgrNoMethod"),
				"Unable to locate method.",
				new String[] {
							   "The assembly could not be found.",
							   "The method type does not exist.",
							   "The method itself could not be found."});
		}

		/// <summary>
		/// Adds a synchronous event handler to the event collection.
		/// </summary>
		/// <param name="name">The event name.  This is NOT the method name, but a abstract label for referencing the event later.</param>
		/// <param name="eh">The method that handles the event.  Using the EventHandler delegate, both static and non-static methods may be specified.</param>
		public static void AddSyncEventSink(string name, System.EventHandler eh)
		{
			evList.Add(name, new SyncEventHandlerSink(eh));
		}

		/// <summary>
		/// Adds an asynchronous event handler to the event collection.
		/// </summary>
		/// <param name="name">The event name.  This is NOT the method name, but a abstract label for referencing the event later.</param>
		/// <param name="eh">The method that handles the event.  Using the EventHandler delegate, both static and non-static methods may be specified.</param>
		public static void AddAsyncEventSink(string name, System.EventHandler eh)
		{
			evList.Add(name, new AsyncEventHandlerSink(eh));
		}

		/// <summary>
		/// Adds a synchronous static event handler to the event collection using reflection.
		/// </summary>
		/// <param name="name">The event name.  This is NOT the method name, but a abstract label for referencing the event later.</param>
		/// <param name="reflection">The reflected method name.  This must be in the format: "assembly/namespace.class/method"</param>
		public static void AddSyncEventSink(string name, string reflection)
		{
			MethodInfo mi=GetMethodInfo(reflection);
			if (mi!=null)
			{
				evList.Add(name, new SyncReflectionSink(mi));
			}
		}

		/// <summary>
		/// Adds a synchronous instantiated event to the event collection using reflection.
		/// </summary>
		/// <param name="name">The event name.  This is NOT the method name, but a abstract label for referencing the event later.</param>
		/// <param name="instance">The instance containing the specified reflected method.</param>
		/// <param name="reflection">The reflected method name.  This must be in the format: "assembly/namespace.class/method"</param>
		public static void AddSyncEventSink(string name, object instance, string reflection)
		{
			MethodInfo mi=GetMethodInfo(reflection);
			if (mi!=null)
			{
				evList.Add(name, new SyncReflectionSink(instance, mi));
			}
		}

		/// <summary>
		/// Adds an asynchronous static event handler to the event collection using reflection.
		/// </summary>
		/// <param name="name">The event name.  This is NOT the method name, but a abstract label for referencing the event later.</param>
		/// <param name="reflection">The reflected method name.  This must be in the format: "assembly/namespace.class/method"</param>
		public static void AddAsyncEventSink(string name, string reflection)
		{
			MethodInfo mi=GetMethodInfo(reflection);
			if (mi!=null)
			{
				evList.Add(name, new AsyncReflectionSink(mi));
			}
		}

		/// <summary>
		/// Adds a synchronous instantiated event to the event collection using reflection.
		/// </summary>
		/// <param name="name">The event name.  This is NOT the method name, but a abstract label for referencing the event later.</param>
		/// <param name="instance">The instance containing the specified reflected method.</param>
		/// <param name="reflection">The reflected method name.  This must be in the format: "assembly/namespace.class/method"</param>
		public static void AddAsyncEventSink(string name, object instance, string reflection)
		{
			MethodInfo mi=GetMethodInfo(reflection);
			if (mi!=null)
			{
				evList.Add(name, new AsyncReflectionSink(instance, mi));
			}
		}

		/// <summary>
		/// Invoke an event.
		/// </summary>
		/// <param name="name">The abstract event name.</param>
		/// <returns>For synchronous events, returns null, otherwise returns an IAsyncResult object.</returns>
		public static IAsyncResult Invoke(string name)
		{
			return Invoke(name, null, null);
		}

		/// <summary>
		/// Invoke an event.
		/// </summary>
		/// <param name="name">The abstract event name.</param>
		/// <param name="sender">The instance of the sender.</param>
		/// <returns>For synchronous events, returns null, otherwise returns an IAsyncResult object.</returns>
		public static IAsyncResult Invoke(string name, object sender)
		{
			return Invoke(name, sender, null);
		}

		/// <summary>
		/// Invoke an event.
		/// </summary>
		/// <param name="name">The abstract event name.</param>
		/// <param name="args">A list of parameters passed to the event handler.</param>
		/// <returns>For synchronous events, returns null, otherwise returns an IAsyncResult object.</returns>
		public static IAsyncResult Invoke(string name, object[] args)
		{
			return Invoke(name, null, args);
		}

		/// <summary>
		/// Invoke an event.
		/// </summary>
		/// <param name="name">The abstract event name.</param>
		/// <param name="sender">The instance of the sender.</param>
		/// <param name="args">A list of parameters passed to the event handler.</param>
		/// <returns>For synchronous events, returns null, otherwise returns an IAsyncResult object.</returns>
		public static IAsyncResult Invoke(string name, object sender, object[] args)
		{
			IAsyncResult res=null;

			if (evList.Contains(name))
			{
				Dbg.WriteLine("EventManager.Invoke: "+name);
				res=((EventSink)evList[name]).Invoke(sender, args);
			}
			else
			{
				Dbg.Warn(false, new DbgKey("EvMgrNoEvent"), name);
			}
			return res;
		}

		/// <summary>
		/// Returns a MethodInfo object with the parsed reflect string.
		/// </summary>
		/// <param name="reflection">A reflection method name, in the format "assembly/namespace.class/method".</param>
		/// <returns></returns>
		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;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions