AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1261&pId=-1
Using Generics and Inheritance to Simplify Development
page
by Brian Mains
Feedback
Average Rating: 
Views (Total / Last 10 Days): 35632/ 50

Introduction

Inheritance is a common Object Oriented Programming (OOP) concept that can be both well used and overused in application development.  In combination with Generics, a new feature in .NET 2.0 that supports strong typing, using both inheritance and generics, can be a powerful combination helping to reduce the total amount of coding effort you do in application development.

Development Styles

Each developer has a different style of developing applications.  Given a single software problem, many people will come up with many different ways to develop the solution.  However, analyzing your trends in software development can help to create a base of reusable code that will minimize your total development efforts.

In addition, some of the standard tasks that you see examples on the MSDN can use a combination of inheritance and generics to help you not have to rewrite any of that code at all, as we will see in a future example.

Beware the Pitfalls

Before looking into the benefits, beware the potential pitfalls with inheritance and generics in combination with inheritance.  Inheritance can and sometimes does get out of hand, where a derived class may derive from five or more base classes.  While it may pay off for some people, it can quickly become a maintenance nightmare.  What if a property in the third base class changes, but should not change for the two-three other derived classes?  The solution may be to ignore the property or to shadow it and make it private.  What happens if a method changes its algorithm?  Will it break the inheritance chain or cause a problem for the other classes?

Sometimes, a different development structure (maybe the state/strategy pattern or simplifying the inheritance structure) is a better solution.  I know from personal experience the problems that can occur from deep chains of inheritance, as I have developed programs with the very problem I am talking about.  Note, however, I am not stating that inheritance is bad, as we shall soon see; I am just warning you to be careful how you plan your inherited structures and recommend keeping your inheritance chains short.

Generic base classes can cause a problem because the generic type of a base class makes the base class specific and you cannot take advantage of generic switching in some situations.  For instance, a class Entity that inherits from BusinessObject<EntityBase> does not inherit directly from BusinessObject, but BusinessObject<EntityBase> and, therefore, you may not be able to reference it dynamically in your application.

The Benefits of Inheritance

Inheritance is not bad as I have stated before.  There are plenty of good designs that involve inheritance.  Everything inherits something anyway.  If there is no direct inheritance, then it inherits from System.Object.

There are many benefits to inheritance. For instance, it makes it easy to implement list or dictionary collections, using the List<T> or Dictionary<K,V> classes.  In addition, inheritance makes it easier to add custom functionality to the base object.  For instance, one can inherit from the GridView or DetailsView class and add additional functionality to it.

Example: Generic Event Arguments

To make it easy to add a generic single parameter argument for an application, the following event argument was created.

Listing 1

namespace Nucleo.EventArguments
{
  public class DataEventArgs < T > : EventArgs
  {
    private T _data;
 
    public T Data
    {
      get
      {
        return _data;
      }
    }
 
    public DataEventArgs(T data)
    {
      _data = data;
    }
  }
 
  public delegate void DataEventHandler < T > (object sender, DataEventArgs < T
    > e);
}

This event argument makes it simplistic to expose a single strongly-typed event argument property in an application.  Below are some of the examples of the consumption of the delegate.

Listing 2

public static event DataEventHandler<Document> DocumentClosed;
public static event DataEventHandler<Document> DocumentCreated;
public static event DataEventHandler<Document> DocumentSelected;

These delegates are defined in a static class and any class that references these events will have a DataEventArgs object that has a Document type.

Example: Custom String Collection

The built-in StringCollection, defined in the .NET Framework's System.Collections.Specialized namespace, is easy to work with strings, but I wanted the ability to convert a list to/from a string.  Take for instance a comma-separated value string.  I wanted the list to have the ability to convert the comma-separated list to/from a string.  I also wanted to work with other separators as well, such as the vertical bar/pipe (|) or tab (\t), which you normally see data separated by.  Below, an inherited structure adds these features.

Listing 3

namespace Nucleo.Collections
{
  public class StringCollection: StringCollection
  {
    public void FromCommaSeparatedList(string text)
    {
      this.FromSeparatedList(text, ",");
    }
 
    public void FromSeparatedList(string text, string separator)
    {
      string[]items = text.Split(separator.ToCharArray());
      foreach (string item in items)
      {
        if (!string.IsNullOrEmpty(item))
          this.Add(item);
      }
    }
 
    public string ToCommaSeparatedList()
    {
      return ToSeparatedList(",");
    }
 
    public string ToSeparatedList(string separator)
    {
      string list = string.Empty;
 
      for (int i = 0; i < this.Count; i++)
      {
        if (i != 0)
          list += separator;
        list += this[i];
      }
 
      return list;
    }
 
    public static StringCollection FromList(string text, string separator)
    {
      StringCollection collection = new StringCollection();
      collection.FromSeparatedList(text, separator);
      return collection;
    }
  }
}

Note that the class also uses a static method to create a collection from a list string, using a separator defined by the user.  This class now has the ability to convert a string from/to CSV or other formatted data.

Example: Custom Configuration Collections

I first read the MSDN example on custom configuration collections and saw what was involved in creating a custom collection from the example.  Maybe there is an easier way, but that is where generics and inheritance come into play.  Instead of implementing that approach every time, I created this element class:

Listing 4

namespace Nucleo.Configuration
{
  public abstract class ConfigurationElementBase:
    System.Configuration.ConfigurationElement
  {
    protected internal abstract string Key
    {
      get;
    }
  }
}

The key property is something that must be overridden and returns a unique value that identifies an element in a collection.  The key is important because the base collection for configuration collections uses a unique value.  The key property ensures this.  Because the collection knows of the Key property in the element base interface, the collection can automatically use the element without having to create a custom collection on your own.  The following class is the base collection class, which defines a generic property that references the element base class above.

Listing 5

namespace Nucleo.Configuration
{
  public abstract class ConfigurationCollectionBase < T > :
    ConfigurationElementCollection where T: ConfigurationElementBase
  {
    public override ConfigurationElementCollectionType CollectionType
    {
      get
      {
        return ConfigurationElementCollectionType.AddRemoveClearMap;
      }
    }
 
    public T this[int index]
    {
      get
      {
        return (T)this.BaseGet(index);
      }
      set
      {
        if (this.BaseGet(index) != null)
          this.BaseRemoveAt(index);
        this.BaseAdd(index, value);
      }
    }
 
    new public T this[string key]
    {
      get
      {
        return (T)this.BaseGet(key);
      }
    }
 
    public void Add(T element)
    {
      this.BaseAdd(element);
    }
 
    public void Clear()
    {
      this.BaseClear();
    }
 
    public bool Contains(string key)
    {
      return (this.BaseGet(key) != null);
    }
 
    protected override ConfigurationElement CreateNewElement()
    {
      return Activator.CreateInstance < T > ();
    }
 
    protected override object GetElementKey(ConfigurationElement element)
    {
      return ((T)element).Key;
    }
 
    public void Remove(T element)
    {
      this.Remove(element.Key);
    }
 
    public void Remove(string key)
    {
      this.BaseRemove(key);
    }
 
    public void RemoveAt(int index)
    {
      this.BaseRemoveAt(index);
    }
  }
}

Now, because the work is done in the base collections class, inheritance from this is a snap and reduces the amount of code needed.  For instance, below is a derived implementation of the element.

Listing 6

namespace Nucleo.Web.Configuration
{
  public class IndividualPageElement: ConfigurationElementBase
  {
    protected override string Key
    {
      get
      {
        return this.PageName;
      }
    }
  }
}

The collection definition is even easier.

Listing 7

public class IndividualPageElementCollection :  
ConfigurationCollectionBase<IndividualPageElement> 
{ 
}

That is all it takes to implement a collection with all that functionality and with strong typing.  This is a more useful example because it really can cut down on the total amount of coding that is required for a collection.  However, this usage is not limited to collections, but can be used in many scenarios.

Example: Custom Data Control Fields

The DataControlField base class is a class used for the columns in a GridView and fields in a DetailsView control.  These fields provide the interface for a data bound field provided from a data source control or other type of data source.

The first level of data field base controls is the BaseDataField class.

Listing 8

namespace Nucleo.Web.Controls
{
  public abstract class BaseDataField: DataControlField
  {
    private bool _insertMode = false;
 
    public string DataField
    {
      get
      {
        object o = ViewState["DataField"];
        return (o == null) ? string.Empty : o.ToString();
      }
      set
      {
        ViewState["DataField"= value;
      }
    }
 
    protected bool InsertMode
    {
      get
      {
        return _insertMode;
      }
    }
 
    public bool ReadOnly
    {
      get
      {
        object o = ViewState["ReadOnly"];
        return (o == null) ? false : (bool)o;
      }
      set
      {
        ViewState["ReadOnly"= value;
      }
    }
 
    protected T ExtractValueFromControl < T > (TableCell cell, int index)where
      T: Control
    {
      if (index < 0 || index >= cell.Controls.Count)
        throw new ArgumentOutOfRangeException("index", Errors.OUT_OF_RANGE);
 
      Control control = cell.Controls[index];
      if (control == null)
        throw new InvalidOperationException(Errors.CANT_EXTRACT_CONTROL_IN_CELL)
          ;
      return (T)control;
    }
 
    public override void ExtractValuesFromCell(IOrderedDictionary dictionary,
      DataControlFieldCell cell, DataControlRowState rowState, bool
      includeReadOnly)
    {
      _insertMode = (rowState == DataControlRowState.Insert);
    }
 
    protected T GetDataItemValue < T > (object container, string property)
    {
      if (container == null || this.DesignMode)
        return default(T);
 
      object dataItem = DataBinder.GetDataItem(container);
      if (dataItem == null)
        return default(T);
      object value = DataBinder.GetPropertyValue(dataItem, property);
 
      if (value != null)
        return (T)value;
      else
        return default(T);
    }
 
    protected bool IsReadMode(DataControlRowState rowState)
    {
      return (this.ReadOnly || rowState == DataControlRowState.Normal ||
        rowState == DataControlRowState.Alternate || rowState ==
        DataControlRowState.Selected);
    }
  }
}

There are several standard fields when implementing a data field.  The DataField, InsertMode, and ReadOnly properties all have a specific function when rendering a data control field.  InsertMode is based off of the DataControlRowState setting of Insert specified from the ExtractValuesFromCell method.  DataField is commonly used because only one field is specified; there are alternatives if the data field uses multiple data fields.

To further minimize the amount of work to do, I created another base class.

Listing 9

namespace Nucleo.Web.Controls
{
  public abstract class BaseCustomDataField: BaseDataField
  {
    public abstract void BindEditControl(object control, bool insertMode);
 
    public override void ExtractValuesFromCell(IOrderedDictionary dictionary,
      DataControlFieldCell cell, DataControlRowState rowState, bool
      includeReadOnly)
    {
      base.ExtractValuesFromCell(dictionary, cell, rowState, includeReadOnly);
      string value = null;
 
      if (cell.Controls.Count > 0)
        value = this.GetEditControlValue(cell);
 
     //If the dictionary contains the data field, overwrite the value
      if (dictionary.Contains(this.DataField))
        dictionary[this.DataField] = value;
     //Create a new dictionary entry
      else
        dictionary.Add(this.DataField, value);
    }
 
    public abstract string GetEditControlValue(TableCell cell);
 
    public override void InitializeCell(DataControlFieldCell cell,
      DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
    {
      base.InitializeCell(cell, cellType, rowState, rowIndex);
      Control control = null;
 
      if (cellType == DataControlCellType.DataCell)
      {
        if (this.IsReadMode(rowState))
          control = cell;
        else
        {
          cell.Controls.Add(this.SetupEditControl());
 
          if (!string.IsNullOrEmpty(this.DataField))
            control = cell.Controls[0];
        }
 
        if (control != null && this.Visible)
          control.DataBinding += new EventHandler(control_DataBinding);
      }
    }
 
    public abstract Control SetupEditControl();
 
    void control_DataBinding(object sender, EventArgs e)
    {
      if (sender is TableCell)
      {
        TableCell cell = sender as TableCell;
        cell.Text = this.GetDataItemValue < string > (cell.NamingContainer);
      }
      else
        this.BindEditControl(sender, this.InsertMode);
    }
  }
}

This class makes use of the Template Method pattern, meaning that any derived class only has to override the BindEditControl, GetEditControlValue, and SetupEditControl methods.  SetupEditControl handles creating the control, BindEditControl handles the binding, and GetEditControlValue handles getting the value specified when in edit mode.  From this, it makes it even simpler to use the base controls.  The InitializeCell and ExtractValuesFromCell methods all have a standardly-used definition that makes use of BindEditControl, GetEditControlValue, and SetupEditControl methods.  And if that does not meet your needs, you can choose a lesser implementation from BaseDataField class.  Below are the implementations of those methods in a TextField class.  Note that the properties are omitted.

Listing 10

public class TextField: BaseCustomDataField
{
  protected override DataControlField CreateField()
  {
    return new TextField();
  }
  public override void BindEditControl(object control, bool insertMode)
  {
    TextBox box = control as TextBox;
//If not in insert mode, set the text property
    if (!insertMode)
      box.Text = this.GetDataItemValue < string > (box.NamingContainer);
  }
 
  public override string GetEditControlValue(TableCell cell)
  {
    return this.ExtractValueFromControl < TextBox > (cell).Text;
  }
 
  public override Control SetupEditControl()
  {
    TextBox box = new TextBox();
    box.TextMode = this.TextMode;
    box.Rows = this.Rows;
    box.Columns = this.Columns;
    box.MaxLength = this.MaximumLength;
    box.ToolTip = this.HeaderText;
    return box;
  }
}
Conclusion

You have seen several examples of inheritance and generics and how you can use it in custom development, whether it is business systems or web/windows applications.  These examples were taken from the Nucleo Framework I am developing.



©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-19 3:42:46 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search