Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / Windows Forms

How Programming Should Be Done

Rate me:
Please Sign up or sign in to vote.
4.90/5 (48 votes)
15 Jun 2009CPOL18 min read 77.7K   395   171   24
Cx: A prototype on component loose coupling.

Image 1

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:

  1. a numeric keypad UI (buttons for the numbers 0 through 9 and a decimal point)
  2. an arithmetic operator UI (buttons for the four basic operators, an equal button, and a clear button)
  3. a display UI (for the number and result)
  4. a business unit for processing the operations

Image 2

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:

XML
<?xml version="1.0" encoding="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

C#
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:

C#
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:

C#
public Form1(List<ICxComponent> visualComponents)
{
  InitializeComponent();

  foreach (ICxComponent comp in visualComponents)
  {
    // Isn't there a Parse method to do this???
    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:

Image 3

The Display Component

The display component consists of a user control with a TextBox control:

Image 4

The code implements everything I discussed above in the requirements:

C#
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

Image 5

This component is a producer of character messages, where the character corresponds to the button pressed. The implementation is trivial (read easily unit tested):

C#
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

Image 6

This component is a bit different. Since the buttons are "commands", there is no implementation other than what Visual Studio creates:

C#
public Operator()
{
  InitializeComponent();
}

Instead, being command events, the metadata describes the wire-up by directly referencing the button click events, for example:

XML
<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.

C#
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.

C#
namespace Cx.Interfaces
{
  /// <summary>
  /// Base interface for a component instance.
  /// </summary>
  public interface ICxComponent
  {
    ICxComponentClass Instance { get; }
  }

  /// <summary>
  /// Properties and methods specific to visual components.
  /// </summary>
  public interface ICxVisualComponent
  {
    string Location { get; }
  }

  /// <summary>
  /// Properties and methods specific to business components.
  /// </summary>
  public interface ICxBusinessComponent
  {
    // None right now.
  }

  /// <summary>
  /// Base class for defining a class as a component. Should not be
  /// directly inherited from a component class.
  /// </summary>
  public interface ICxComponentClass
  {
  }

  /// <summary>
  /// Any class inheriting this interface is a visual component.
  /// </summary>
  public interface ICxVisualComponentClass : ICxComponentClass
  {
  }

  /// <summary>
  /// Any class inheriting this interface is a business component.
  /// </summary>
  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:

C#
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.

C#
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.

C#
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.

C#
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.

C#
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;
}

/// <summary>
/// Follow the field chain until we get to the event name.
/// Allows us to do things like [component].[property].[property].[eventname]
/// </summary>
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.

C#
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:

  1. firing data/commands to test normal code paths
  2. 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?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


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

 
SuggestionTake also a look to EBC (Event Based Components) Pin
ChrKon15-Sep-11 20:15
ChrKon15-Sep-11 20:15 
GeneralIt is the problem of coding c# or coding XML PinPopular
reborn_zhang18-Sep-09 9:51
reborn_zhang18-Sep-09 9:51 
General"The biggest problem I'm facing is what to call this infrastructure" Pin
arthur_m8-Aug-09 7:45
arthur_m8-Aug-09 7:45 
Generalfascinating ! a few trivial responses [modified] Pin
BillWoodruff9-Jul-09 21:39
professionalBillWoodruff9-Jul-09 21:39 
GeneralRe: fascinating ! a few trivial responses Pin
Marc Clifton10-Jul-09 5:15
mvaMarc Clifton10-Jul-09 5:15 
GeneralVery interesting but... Pin
gardnerbp24-Jun-09 11:23
gardnerbp24-Jun-09 11:23 
GeneralRe: Very interesting but... Pin
Marc Clifton24-Jun-09 16:06
mvaMarc Clifton24-Jun-09 16:06 
gardnerbp wrote:
However I'm sensing that there is a lot of overlap with the Managed Extensibility Framework at http://codeplex.com/mef


Interesting--yes, I can see how there appears to be overlap. However, Cx is really a playground for me to explore IoC and dynamic composition. I'm almost done with another installment, which has proven to be very illuminating in the pros and cons of these kind of frameworks. I'll be curious to see where MEF goes. I'm surprised to hear though that it's going to make its way into .NET 4.0. Why not leave it separate?

Marc

Will work for food.
Interacx


I'm not overthinking the problem, I just felt like I needed a small, unimportant, uninteresting rant! - Martin Hart Turner


GeneralXCode/Interface Builder Pin
L Hills23-Jun-09 1:06
L Hills23-Jun-09 1:06 
GeneralRe: XCode/Interface Builder Pin
Marc Clifton25-Jun-09 6:29
mvaMarc Clifton25-Jun-09 6:29 
GeneralLovely... Pin
Keld Ølykke22-Jun-09 21:49
Keld Ølykke22-Jun-09 21:49 
GeneralRe: Lovely... Pin
Marc Clifton23-Jun-09 14:42
mvaMarc Clifton23-Jun-09 14:42 
GeneralFantastic Article Pin
Lee Humphries22-Jun-09 14:24
professionalLee Humphries22-Jun-09 14:24 
GeneralReason for my 5... Pin
dbswinford22-Jun-09 12:58
dbswinford22-Jun-09 12:58 
GeneralRe: Reason for my 5... Pin
Marc Clifton25-Jun-09 6:30
mvaMarc Clifton25-Jun-09 6:30 
QuestionMicrosoft's Composite UI Application Block (CAB), Spring.NET, NInject. Pin
xFreeCoder15-Jun-09 21:13
xFreeCoder15-Jun-09 21:13 
AnswerRe: Microsoft's Composite UI Application Block (CAB), Spring.NET, NInject. Pin
Marc Clifton16-Jun-09 4:07
mvaMarc Clifton16-Jun-09 4:07 
AnswerRe: Microsoft's Composite UI Application Block (CAB), Spring.NET, NInject. Pin
Giorgi Dalakishvili16-Jun-09 9:55
mentorGiorgi Dalakishvili16-Jun-09 9:55 
GeneralI like the concepts presented here...just one suggestion.. [modified] Pin
Charles T II15-Jun-09 9:16
Charles T II15-Jun-09 9:16 
GeneralRe: I like the concepts presented here...just one suggestion.. Pin
Marc Clifton15-Jun-09 9:21
mvaMarc Clifton15-Jun-09 9:21 
GeneralRe: I like the concepts presented here...just one suggestion.. Pin
Charles T II15-Jun-09 10:59
Charles T II15-Jun-09 10:59 
GeneralWhoops Pin
PIEBALDconsult15-Jun-09 5:55
mvePIEBALDconsult15-Jun-09 5:55 
GeneralRe: Whoops Pin
Marc Clifton15-Jun-09 6:04
mvaMarc Clifton15-Jun-09 6:04 
GeneralNice Summary of an Important Concept Pin
sam.hill15-Jun-09 5:13
sam.hill15-Jun-09 5:13 
GeneralRe: Nice Summary of an Important Concept Pin
Marc Clifton15-Jun-09 5:56
mvaMarc Clifton15-Jun-09 5:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.