Custom event subscription methods can help remove redundant code.
When working with a data-layer and trying to retrieve some list or collection for consumption by a number of different UI elements you run into the situation of having lots of redundant code. As an example, a common approach might have to utilize the following fields, properties, methods and events to cover all of the bases.
public class ComplexDataLayer {
private bool dataAvailable = false;
private ArrayList dataCollection = null;
private readonly object loadingLock = new object();
private bool loading = false;
// Fire an event for consumers
public event EventHandler DataLoaded;
// So you can see if data is
public bool DataAvailable { get { return dataAvailable; } }
// Use a strongly-typed array instead
public ArrayList DataCollection {
get { return this.dataCollection; }
}
// Tell the darn thing to load
public void RefreshDataAsync() {
lock(loadingLock) { if ( loading ) { return; } loading = true; }
// Schedule Async Loading
}
// Finish the data load
private void RefreshDataAsyncCallback() {
lock(loadingLock) {
dataCollection = loadedData;
dataAvailable = true;
loading = false;
}
if ( DataLoaded != null )
DataLoaded(this, EventArgs.Empty);
}
}
Wow, that is a bunch of code to handle all of the potential code-paths. The pattern allows a newly loaded control to investigate and find if data is available. They would do this by checking DataAvailable and DataCollection. They can then load their collections. They still have to subscribe to the DataLoaded event in case someone refreshes the data source. Optionally, if the data wasn't already available they'll instead call RefreshDataAsync to get some data and wait for the event to be loaded. We can get rid of much of the above code by writing custom event subscription code. For now, let's see what the client consuming the above data source might look like.
private void Control_Loaded(object sender, EventArgs e) {
DataLayer.DataLoaded += new EventHandler(this.UpdateList);
if ( DataLayer.DataAvailable ) {
UpdateList(DataLayer, EventArgs.Empty);
} else {
DataLayer.LoadDataAsync();
}
}
private void UpdateList(object sender, EventArgs e) {
ArrayList foo = DataLayer.DataCollection;
if ( foo != null ) { ... }
}
That relays the basics of a system where data is available in more than one location depending on the current state of the application. You have to check properties like DataAvailable to get an intial set of data and hook events to get updates. It would be nice to have a single code-path that handled loading our data and getting refreshed data.
public class SimpleDataLayer {
private EventHandler dataLoaded;
private ArrayList dataCollection = null;
private readonly object loadingLock = new object();
private bool loading = false;
private bool autoLoad = false;
public event EventHandler DataLoaded {
add {
dataLoaded = (EventHandler) Delegate.Combine(dataLoaded, value);
if ( dataCollection != null ) {
if ( value != null )
value(this, EventArgs.Empty);
} else if ( autoLoad ) {
RefreshDataAsync();
}
}
remove {
dataLoaded = (EventHandler) Delegate.Remove(dataLoaded, value);
}
}
public bool AutoLoadData { get { return autoLoad; } set { autoLoad = value; } }
// Tell the darn thing to load
public void RefreshDataAsync() {
lock(loadingLock) { if ( loading ) { return; } loading = true; }
// Schedule Async Loading
}
// Finish the data load
private void RefreshDataAsyncCallback() {
lock(loadingLock) {
dataCollection = loadedData;
dataAvailable = true;
loading = false;
}
if ( DataLoaded != null )
DataLoaded(this, EventArgs.Empty);
}
}
I called it SimpleDataLayer, but it is actually slightly more complex, because we've now written the event subscription code by hand. Basically, we allow the user to simply add an event, and we'll immediately call that event if data is ready. In addition, a special setting on the data layer enables automatic fetching of data whenever someone subscribes making it great for automatic delay-loading. If the data layer is asynchronous then the UI gets asynchronous event callbacks for free. Just make sure you do your marshalling (also required in the original scenario).
private void Control_Loaded(object sender, EventArgs e) {
// DataLayer.AutoLoad = true; // This should already be handled
DataLayer.DataLoaded += new EventHandler(this.UpdateList);
}
private void UpdateList(object sender, EventArgs e) {
if ( InvokeRequired ) { ... }
ArrayList foo = DataLayer.DataCollection;
if ( foo != null ) { ... }
}
So we make a slight trade-off between data layer complexity and UI complexity. I'd recommend putting the complexity into a single reusable location. The data layer can now be used to implement multiple user interfaces or in a console application with relative ease. This method of code-path reduction relies on the concepts of moving all data layer services out of the UI altogether. UI is often a hodge-podge of handlers that contain a mixture of UI state changing code and running functional code (behavior). For each behavior you can either properly encapsulate the code in a helper method, or make the mistake of rewriting it several times in several different places. In addition, you can make some heinous asynchronous mistakes.
This is only one of many models depending on your requirements. I don't consider this a final revision, and welcome any feedback users might have.