Introduction
This is a concept piece for something I've figured we should all be able to do at this point with regards to software development, but it seems we're still years away from it. I was inspired to resurrect this idea from a conversation I had with Jim Crafton today (June 10, 2009). It wasn't anything specific that we talked about, but it inspired me to put this together.
The idea is to keep components completely separated from each other, treating them as producers and consumers of information, and to wire-up the communication between them (which becomes the application domain glue, if you will) using metadata. It's a simple and not original concept. Thus:
- You have components, such as UI elements, business logic, persistent storage interfaces, services, and so forth.
- These all implement consumer methods (event handlers) that are discoverable via Reflection or attributes decorating the methods.
- As well, there are events that the components (producers) can fire. A UI event will typically be something like a button click, list selection, or textbox change. A business component event will typically be a data value change. A data layer event will typically be a transaction response or "here's some data" event.
- Now, what we should be able to do, by drawing it, is wire up producer events to consumer methods, thus connecting the components, and implement the application-specific functionality.
For this prototype, I'm not going to create a visual editor--I'm going to illustrate the concept by showing you the end result of what would happen if you were to wire up events and methods via metadata. Sound familiar? It should. I expect (since I haven't written a line of code yet as I write this introduction) that I'll use XML for this wire-up (don't cringe, Jim).
Also, this approach can be implemented to perform the wire-up at runtime or at compile-time by generating the necessary code from the metadata. I've chosen runtime for this prototype.
The biggest problem I'm facing is what to call this infrastructure. I figure I'd go for something really basic. Cx. "C" because it's an infrastructure for componentizing your application, "x" because anything cool has to have an "x" in it. And, if you say it really fast and slur the "C" and "x" together, well, you get the idea, haha.
Why Not Do This All In XML?
It's definitely straightforward enough to do this with MyXaml and I would assume XAML. The difference is primarily in the formatting of the XML. For example, instead of listing assemblies using xmlns
attributes, I'm defining the assemblies by specifying their path and without all the additional stuff that would otherwise be required: version numbers, etc. Also, the assemblies are clearly defined in the metadata as being either a "visual" component or a "business" component. I think that helps in understanding the concept. Besides, as a future enhancement, I intend to support multiple components per assembly, and list them under the assembly itself. What I've come to discover over the years working with XML is that it's important to choose a schema (and thus the XML format) that's appropriate for the task. And of course, I could have created the classes for the object graph and used MyXaml or MycroXaml to instantiate the graph, but that would have been overkill for the task at hand.
What About Existing Frameworks?
Microsoft's Composite UI Application Block (CAB), Spring.NET, NInject, are frameworks that support concepts such as Inversion of Control (IoC), Dependency Injection (DI), and Aspect Oriented Programming (AOP). They all deal with creating an architecture that supports a clean separation of concern between components, so if you find this article intriguing, I'd recommend you look into these and other frameworks. However, keep in mind that while these frameworks claim to improve maintainability, modularity, and so forth, the only way to achieve that (assuming the framework is designed well) is if your application code utilizes that framework in a consistent, well thought out manner.
Ah, well, I'm not saying that my prototype Cx framework meets that criteria as well. However, having worked with Spring.NET and CAB for a while, I wanted to reduce the problem (is there a problem actually?) down to its simplest form: how does a component with some important information communicate that information to another component without creating a tight coupling between the two? And, can the wiring up of these components be done in a highly visual manner, such as drawing a line from the producer's event to the consumer's handler? Thus, I created this prototype because visually, this wire-up would be very simple to implement. Maybe one of the WPF gods would be interested in helping me out with that!
Case Study Requirements
I'm going to choose something fairly simple--a basic calculator--for the case study. The calculator will have four components:
- a numeric keypad UI (buttons for the numbers 0 through 9 and a decimal point)
- an arithmetic operator UI (buttons for the four basic operators, an equal button, and a clear button)
- a display UI (for the number and result)
- a business unit for processing the operations
Component Communication
First, let's look at the communication between each component.
- A button press on the numeric keypad UI updates the display UI, appending the number to the string displayed in the display UI
- A change in the display UI updates the current value in the business unit data model
- The Clear button on the operator UI tells the business unit to clear its state and set the current value to 0
- An operator or equal button invokes the appropriate operator method on the business unit
- An update to the business unit's current value updates the display UI
The Events
Given the above requirements for communication, we can describe the minimum events required. All components participate in being information producers and thus have at least one event.
The numeric keypad UI component requires:
- an event for each button representing the digits 0-9 and the decimal point
The operator UI component requires:
- an operator add event
- an operator subtract event
- an operator multiply event
- an operator divide event
- an equals event
- a clear event
The display UI component requires:
- a text value changed event
The business unit component requires:
- a current value changed event
The Consumers
Only two components are consumers: the display UI component and the business UI component. These are the components that are going to consume to other components' events (nothing prevents a component from consuming to itself).
The display UI component has methods that can consume:
- set display text
- append display text
- set state to display new text
The business unit component:
- current value is updated
- operation plus
- operation minus
- operation multiply
- operation divide
- operation equals
- operation clear
State Issues
Let's look briefly at state issues in the display UI and business unit components.
The display UI component needs to know whether to begin a new display text or append to the existing display text when the "append display text" method is invoked. This is determined as follows: the component is initially in the "display as new text" state. When the first append display text method is called, the component goes into the "append" state. When the "set display text" method is called, the component returns to the "display as new text" state. This state can also be forced by the operator events.
The business unit also needs to retain some state information regarding the disposition of the stack. Basically, when the clear method is called (we're going for very simplistic here), the stack is cleared.
Wire-Up
Now, let's look at what the event wire-up looks like.
- The numeric keypad component events wire up to the append display text method on the display component.
- The display component's text value changed event is wired up to the business unit's current value updated method.
- Any operator event (operator UI component) is wired up to the set state to display new text method (display UI component).
- The operator events (operator UI component) are wired up to their respective methods in the business component.
- The clear and equal events (operator UI component) are wired up to their respective methods in the business component.
- The business unit's current value changed event is wired up to the display component's set display text event.
Data binding could be used between the textbox in the display component and the business model. I chose not to use data binding at this point to keep this case study consistent and simple.
Concluding The Case Study Requirements
I've now defined all the events, interfaces, states, and wire-ups necessary to create a basic calculator from discrete components. Granted, this seems to be overkill, but the point here is that we're using the calculator as proof of concept. What we're really vetting here is the infrastructure necessary to support this kind of programming style.
Implementation
All the UI components are implemented as user controls, basically as a container for a collection of controls. The actual layout of the components in the application form is described in XML. The reader familiar with my articles will be shocked when they realize that I didn't use XML to define the component UIs!
Please realize that none of the components knows anything about the other components, and the only assembly reference required by the components is for the interfaces (stubs basically) that components implement and specialized EventArg class helpers to wire-up producers and consumers.
Also, rather than start off with the infrastructure, I'm going to describe the concept from the top down, looking first at the startup application, then the visual components, then the business unit, and finally the infrastructure that supports the assembly loading and wire-up.
The Metadata File
For reference in the discussion below, here is the metadata file:
="1.0"="utf-8"
<Cx>
<Components>
<VisualComponent Name="Display"
Assembly="..\..\..\TextDisplayComponent\bin\debug\TextDisplayComponent.dll"
Location="10, 10"/>
<VisualComponent Name="Keypad"
Assembly="..\..\..\NumericKeypadComponent\bin\debug\NumericKeypadComponent.dll"
Location="10, 40"/>
<VisualComponent Name="Operators"
Assembly="..\..\..\OperatorComponent\bin\debug\OperatorComponent.dll"
Location="130, 40"/>
<BusinessComponent Name="Calculator"
Assembly="..\..\..\BusinessUnitComponent\bin\debug\BusinessUnitComponent.dll"/>
</Components>
<Wireups>
<WireUp Producer="Keypad.KeypadEvent" Consumer="Display.OnChar"/>
<WireUp Producer="Operators.btnPlus.Click" Consumer="Calculator.Add"/>
<WireUp Producer="Operators.btnMinus.Click" Consumer="Calculator.Subtract"/>
<WireUp Producer="Operators.btnMultiply.Click" Consumer="Calculator.Multiply"/>
<WireUp Producer="Operators.btnDivide.Click" Consumer="Calculator.Divide"/>
<WireUp Producer="Operators.btnEqual.Click" Consumer="Calculator.Equal"/>
<WireUp Producer="Operators.btnPlus.Click" Consumer="Display.StateIsNewText"/>
<WireUp Producer="Operators.btnMinus.Click" Consumer="Display.StateIsNewText"/>
<WireUp Producer="Operators.btnMultiply.Click" Consumer="Display.StateIsNewText"/>
<WireUp Producer="Operators.btnDivide.Click" Consumer="Display.StateIsNewText"/>
<WireUp Producer="Operators.btnEqual.Click" Consumer="Display.StateIsNewText"/>
<WireUp Producer="Operators.btnClear.Click" Consumer="Calculator.Clear"/>
<WireUp Producer="Display.DisplayTextChanged" Consumer="Calculator.SetCurrentValue"/>
<WireUp Producer="Calculator.CurrentValueChanged" Consumer="Display.OnText"/>
</Wireups>
</Cx>
The Startup Application
The startup application consists of two pieces: initializing the infrastructure, and adding the visual components to the application form.
Initializing The Infrastructure
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
CxApp cx = new CxApp();
cx.LoadXml(Path.GetFullPath("cx.xml"));
cx.LoadVisualComponents();
cx.LoadBusinessComponents();
cx.WireUpComponents();
Application.Run(new Form1(cx.VisualComponents));
}
As we can see, the infrastructure initialization consists of loading the the XML metadata, then loading the visual and business components, and wiring up the components. Everything gets instantiated before the wire-up occurs (in fact, that's a pre-requisite of wiring up the events).
Note: more sophisticated frameworks allow for late initialization and wire-up, but to do so requires instantiating the class through a helper service of the framework, something like:
Foo foo=Framework.CreateObject<Foo>();
or is handled automatically when a component definition has dependencies that must be instantiated at the same time.
The Form
object gets the collection of visual components and lays them out on the form as described in the XML metadata:
public Form1(List<ICxComponent> visualComponents)
{
InitializeComponent();
foreach (ICxComponent comp in visualComponents)
{
string[] loc=comp.Location.Split(',');
Point p = new Point(Convert.ToInt32(loc[0].Trim()),
Convert.ToInt32(loc[1].Trim()));
Control ctrl = (Control)comp.Instance;
ctrl.Location = p;
Controls.Add(ctrl);
}
}
The result is a form composing of the three visual components:
The Display Component
The display component consists of a user control with a TextBox
control:
The code implements everything I discussed above in the requirements:
public partial class TextDisplay : UserControl, ICxVisualComponent
{
public event CxStringDlgt DisplayTextChanged;
protected enum State
{
NewText,
AddChar,
}
protected State state;
public TextDisplay()
{
InitializeComponent();
tbDisplay.TextChanged += new EventHandler(OnTextChanged);
state = State.NewText;
}
public void OnChar(object sender, CxEventArgs<char> args)
{
switch (state)
{
case State.AddChar:
tbDisplay.Text = tbDisplay.Text + args.Data;
break;
case State.NewText:
tbDisplay.Text = args.Data.ToString();
state = State.AddChar;
break;
}
}
public void OnText(object sender, CxEventArgs<string> args)
{
tbDisplay.Text = args.Data;
state = State.NewText;
}
public void StateIsNewText(object sender, EventArgs args)
{
state = State.NewText;
}
protected void OnTextChanged(object sender, EventArgs e)
{
if (DisplayTextChanged != null)
{
DisplayTextChanged(this, new CxEventArgs<string>(tbDisplay.Text));
}
}
}
Barring my inconsistent method naming convention, you can see how the component handles:
- its state (whether characters clear the current display or append to the current display)
- is capable of consuming char and text messages
- is capable of producing a text changed message
This is simple enough, and not being entangled with any other functionality, would be a piece of cake to unit test.
The Numeric Keypad Component
This component is a producer of character messages, where the character corresponds to the button pressed. The implementation is trivial (read easily unit tested):
public partial class NumericKeypad : UserControl, ICxVisualComponent
{
public event CxCharDlgt KeypadEvent;
public NumericKeypad()
{
InitializeComponent();
}
protected virtual void RaiseKeypadEvent(char c)
{
if (KeypadEvent != null)
{
KeypadEvent(this, new CxEventArgs<char>(c));
}
}
protected void KeypadClick(object sender, EventArgs e)
{
string padItem = (string)((Control)sender).Tag;
RaiseKeypadEvent(padItem[0]);
}
}
Again note my inconsistent naming convention. It's somewhat of a struggle to decide how to call a method that fires an event vs. handles an event.
The Operator Component
This component is a bit different. Since the buttons are "commands", there is no implementation other than what Visual Studio creates:
public Operator()
{
InitializeComponent();
}
Instead, being command events, the metadata describes the wire-up by directly referencing the button click events, for example:
<WireUp Producer="Operators.btnPlus.Click" Consumer="Display.StateIsNewText"/>
The Business Unit Component
The code for this component should be self-explanatory. At this point, it would be good to review the metadata to see how the business logic is involved in the UI.
public class Calculator : ICxBusinessComponent
{
public event CxStringDlgt CurrentValueChanged;
protected enum PendingOperation
{
None,
Add,
Subtract,
Multiply,
Divide,
}
protected PendingOperation pendingOperation;
protected string lastValue;
protected string currentValue;
public string CurrentValue
{
get { return currentValue; }
set
{
if (currentValue != value)
{
currentValue = value;
OnCurrentValueChanged();
}
}
}
public Calculator()
{
pendingOperation = PendingOperation.None;
}
public void SetCurrentValue(object sender, CxEventArgs<string> args)
{
CurrentValue = args.Data;
}
public void Add(object sender, EventArgs e)
{
Calculate();
lastValue = currentValue;
pendingOperation = PendingOperation.Add;
}
public void Subtract(object sender, EventArgs e)
{
Calculate();
lastValue = currentValue;
pendingOperation = PendingOperation.Subtract;
}
public void Multiply(object sender, EventArgs e)
{
Calculate();
lastValue = currentValue;
pendingOperation = PendingOperation.Multiply;
}
public void Divide(object sender, EventArgs e)
{
Calculate();
lastValue = currentValue;
pendingOperation = PendingOperation.Divide;
}
public void Equal(object sender, EventArgs e)
{
Calculate();
pendingOperation = PendingOperation.None;
}
public void Clear(object sender, EventArgs e)
{
CurrentValue = "0";
pendingOperation = PendingOperation.None;
}
protected void Calculate()
{
try
{
switch (pendingOperation)
{
case PendingOperation.Add:
CurrentValue = Convert.ToString(Convert.ToDouble(lastValue) +
Convert.ToDouble(currentValue));
break;
case PendingOperation.Subtract:
CurrentValue = Convert.ToString(Convert.ToDouble(lastValue) -
Convert.ToDouble(currentValue));
break;
case PendingOperation.Multiply:
CurrentValue = Convert.ToString(Convert.ToDouble(lastValue) *
Convert.ToDouble(currentValue));
break;
case PendingOperation.Divide:
CurrentValue = Convert.ToString(Convert.ToDouble(lastValue) /
Convert.ToDouble(currentValue));
break;
}
}
catch
{
CurrentValue = "Error";
}
pendingOperation = PendingOperation.None;
}
protected void OnCurrentValueChanged()
{
if (CurrentValueChanged != null)
{
CurrentValueChanged(this, new CxEventArgs<string>(currentValue));
}
}
}
The Infrastructure
The following describes the code that implements the minimal infrastructure for the Cx prototype.
Interfaces
There are a few interfaces I use to abstract out the concrete implementation, which are defined in the Cx.Interfaces assembly. These are very light-weight interfaces, mostly to represent the structure of the components.
namespace Cx.Interfaces
{
public interface ICxComponent
{
ICxComponentClass Instance { get; }
}
public interface ICxVisualComponent
{
string Location { get; }
}
public interface ICxBusinessComponent
{
}
public interface ICxComponentClass
{
}
public interface ICxVisualComponentClass : ICxComponentClass
{
}
public interface ICxBusinessComponentClass : ICxComponentClass
{
}
}
Loading Components
There are two methods for loading components--one for visual components and one for business components. They're very similar, so I'll only show you the visual component loader:
public void LoadVisualComponents()
{
foreach (CxVisualComponent comp in from el in xdoc.Root.Elements(
XName.Get("Components")).Elements("VisualComponent")
select new CxVisualComponent()
{
Name = el.Attribute(XName.Get("Name")).Value,
Assembly = el.Attribute(XName.Get("Assembly")).Value,
Location=el.Attribute(XName.Get("Location")).Value,
})
{
ICxComponentClass compInst = AcquireComponent(comp.Assembly,
typeof(ICxVisualComponentClass));
comp.Instance = compInst;
comp.Type = compInst.GetType();
components.Add(comp.Name, comp);
visCompList.Add(comp);
}
}
Both business and visual components are added to a component collection (one common thing about all these implementations is that they ultimately have at least one container that holds all the different pieces), and the visual components are placed into a separate list so that they can be easily instantiated by the application's form.
The AcquireComponent Method
This is the important call. Why? Because implicit in this call is that the assembly gets immediately loaded and the visual or business component is instantiated now. Deferring assembly loading and instantiation of the component is not supported, so we have to realize that this can create a huge overhead on application startup, as well as other problems if components, on initialization, want to communicate with services, databases, and so forth that may not be available or introduce their own delays.
It is vital, when using one of these frameworks, that you understand your component initialization process, perhaps put some of the work into a worker thread, so that you don't bog down the application startup. It is also vital that you consider which components must be initialized immediately and which can be deferred, loaded only if required.
protected virtual ICxComponentClass AcquireComponent(string assyPath, Type componentType)
{
ICxComponentClass compInst = null;
Assembly assy = Assembly.LoadFrom(assyPath);
Type t = FindImplementor(assy, componentType);
compInst = InstantiateComponent(t);
return compInst;
}
Of course, the problem with deferred loading and instantiation is that you don't know whether the component throws an exception until you specifically do whatever it is necessary to activate the component instantiation.
With deferred instantiation, it is vital that you thoroughly unit test your component, because it is quite possible (and easy) to not realize, when writing your acceptance test plan or other QA procedures, that a specific sequence of steps may be necessary to fully exercise a deferred-load component. Certainly, ad-hoc testing will almost always miss these deferred components.
The FindImplementor Method
Looking at this method, you should realize another drawback of my little prototype--it permits only one component per assembly. This is an artificial requirement that needs to be removed before this becomes a viable framework.
protected virtual Type FindImplementor(Assembly assy, Type targetInterface)
{
IEnumerable<Type> cxComponents = from classType in assy.GetTypes() where
classType.IsClass && classType.IsPublic
from classInterface in classType.GetInterfaces() where classInterface==targetInterface
select classType;
if (cxComponents.Count<Type>() == 0)
{
throw new CxException("Expected assembly " + assy.FullName +
" to have one class implementing "+targetInterface.Name);
}
if (cxComponents.Count<Type>() > 1)
{
throw new CxException("Expected assembly " + assy.FullName +
" to have one and only one class implementing "+targetInterface.Name);
}
return cxComponents.ElementAt<Type>(0);
}
If you think about it, it isn't even necessary to have visual and business components inherit from an interface. The reason I want to take this approach, however, is that it disturbs me that several of these professional frameworks identify components by strings (assembly names, class names, method names, tags, etc.). If you have a typo in your string definition, you won't know it until runtime. In the end, I prefer using interfaces and attributes declaring "I'm a producer event" or "I'm a consumer method" rather than relying on someone typing the name of an assembly, method, or tag correctly every time--which is why we should all really be using a visual designer for wiring up the components. Certainly, the metadata could go through a check process against the assemblies, and that should be done whether we have a visual designer or not, to ensure that any changes made outside of the designer doesn't break the metadata wire-up.
The InstantiateComponent Method
This is a very small method, and the salient point here is that I expect the metadata to describe exactly where the assembly can be found. A more professional approach would include an assembly resolver that would aid the .NET framework in locating the assembly, based perhaps on some application-specific configuration information, so that the path itself can be removed from the metadata. This, of course, facilitates deployment.
protected virtual ICxComponentClass InstantiateComponent(Type t)
{
ICxComponentClass inst = null;
inst = (ICxComponentClass)Activator.CreateInstance(t);
return inst;
}
Wiring Up Components
The real fun is in wiring up the components. I'm going to present all the methods involved in this process. The piece that I fought with the most was the Delegate.CreateDelegate
call. Ultimately, the problem was that I was passing in the CxComponent
instance rather than the component instance as the target parameter, much to my embarrassment when I realized the problem. The other interesting piece is the DrillInto
method, which is useful in writing metadata like Operators.btnPlus.Click
, as it allows one to drill into the object graph to get to the desired event. This should be familiar to anyone using WPF.
protected void WireUp(string producer, string consumer)
{
object producerTarget = GetProducerTarget(producer);
object consumerTarget = GetConsumerComponent(consumer).Instance;
EventInfo ei = GetEventInfo(producer);
MethodInfo mi = GetMethodInfo(consumer);
Delegate dlgt = Delegate.CreateDelegate(ei.EventHandlerType, consumerTarget, mi);
ei.AddEventHandler(producerTarget, dlgt);
}
protected object GetProducerTarget(string producer)
{
string[] parts = producer.Split('.');
object obj = components[parts[0]].Instance;
obj = DrillInto(obj, parts);
return obj;
}
protected CxComponent GetConsumerComponent(string consumer)
{
string[] consumerParts = consumer.Split('.');
return components[consumerParts[0]];
}
protected EventInfo GetEventInfo(string producer)
{
string[] parts = producer.Split('.');
object obj = components[parts[0]].Instance;
obj = DrillInto(obj, parts);
EventInfo ei = obj.GetType().GetEvent(parts[parts.Length-1]);
return ei;
}
protected MethodInfo GetMethodInfo(string consumer)
{
string[] parts = consumer.Split('.');
MethodInfo mi = components[parts[0]].Type.GetMethod(parts[1],
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.Static);
return mi;
}
protected object DrillInto(object obj, string[] parts)
{
int n = 1;
while (n < parts.Length - 1)
{
obj = obj.GetType().GetField(parts[n],
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance).GetValue(obj);
++n;
}
return obj;
}
A Couple Common Delegates
For command events, like add, subtract, etc., there is no data, so we wire up the UI event itself (in this case). For events in which there is data, for example, when the display component's text changes, we end up wiring up the control's event and then re-firing our own event with the data. This way, the data is always pushed to the consumer, rather than the consumer having to query the producer for the data--for example, casting the sender to a TextBox
to get at the Text
property. This makes life a little more annoying, especially since there are events that can be accessed (via the drill into mechanism) from component objects, and events that are there to help with the Cx producer-consumer wire-up.
namespace Cx.EventArgs
{
public delegate void CxCharDlgt(object sender, CxEventArgs<char> args);
public delegate void CxStringDlgt(object sender, CxEventArgs<string> args);
public class CxEventArgs<T> : System.EventArgs
{
public T Data { get; set; }
public CxEventArgs(T val)
{
Data = val;
}
}
}
Obviously, we can add more common delegates here, and you can use the CxEventArgs<T>
class to define your own component-specific arguments.
Conclusion
Imagine if programming could be done this way, where you define small components that are instantiated by a configuration file and all information exchange and commands between them is wired up with some sort of a visual tool, using producer "events" and consumer methods. This is the way programming should be done, and reminds me of the sci-fi movie concept of programming, which is basically connecting up components in new configurations. So hopefully, this article has piqued your interest in such a concept.
One thing I like about this approach is that unit testing becomes trivial. The dependencies (entanglement) between components is eliminated, so your unit tests consist of two things:
- firing data/commands to test normal code paths
- firing data/commands to test edge cases
The need to perform complex initialization due to component dependencies is eliminated. Since data/commands are associated with events produced by components that the unit test simulates for testing a specific consumer component, a lot of the unit test can end up being scripted. Keep in mind that one can also script the component's "result" events along with the expected values. This benefit should not be understated, and I think it should be strived for when using any framework of this kind.
Have I Re-Invented The Wheel?
I don't think so. I think I've taken a unique and lightweight approach to the holy grail (was there actually a problem that needed solving?) of fully separating components by implementing a very simple producer-consumer pattern. And again, the purpose of this prototype is to lay down the foundation for creating a visual designer and to extend the framework (keeping it lightweight) into something actually useable. But, if someone else has already done this work, let me know!
Producer-Consumer vs. Publisher-Subscriber
The code I present is a producer-consumer pattern, in that components produce data or commands via .NET's event capability, and other components consume those events, using the Cx framework to wire up the event to the event handler. This is slightly different from a publisher-subscriber pattern, in which data/commands are published (either in an event or in a queue of some sort) and subscribers acquire the data or commands (either by hooking the event directly or monitoring the queue). Both patterns have their similarities and subtle differences.
Recursive Property Change Events
If you were wondering, recursive notifications are most easily addressed by only firing a property change event when the property value actually changes--pretty standard practice.
EventHandler<TEventArgs> vs. CEventArgs<T>
Why am I not using EventHandler<TEventArgs>
? No particular reason, and keep in mind, there's actually nothing stopping you from doing so yourself. In my particular case, I like the fact that CxEventArgs<T>
derives from EventArgs
, so you can still maintain the purity of the event signature, and also, EventHandler<char>
does not work--you get the compiler error There is no boxing conversion from 'char' to System.EventArgs.
Some Interesting Things That We Can Do
Here are a few thoughts of what else can be done with this prototype. The neat thing about this is that these ideas can be applied uniformly to any code that utilizes Cx.
Adding Event Monitoring
When an event is initially wired up, being multicast, we can add an internal logging handler to log when the event is fired.
Logging The Event Data
Any object implements ToString()
, so we can also log the data associated with the event.
Asynchronous Events
It would be simple enough to specify that a consumer can execute in a thread. The wire-up can maintain information for each consumer as to whether it executes on the current thread, is executed asynchronously on a new thread, or is assigned to a thread pool.
Monitoring Execution Time
We could easily add a stopwatch feature to monitor how long a consumer takes (or all consumers take) to execute.
Safe Event Execution
What happens if a consumer throws an exception? Should other consumers still be notified? Should the event chain be terminated? These questions can be addressed by modifying the event wire-up in Cx to instead call into a safe event execution method.
Execution Chains
We can also play with the execution chain itself. Do all consumers get a chance to handle the event, or do we stop when the first consumer indicates that it has handled the event? What about more sophisticated event chains (like WPF supports), such as object graphs, in which we test whether a parent or child can handle the event?