Generics, Invalid Cast Exceptions, and Non-Generic Base Classes

Its hard even for me to believe that I’ve been coding for 25 years now.  I started writing code when I was 15 and I’ve been doing it pretty much non-stop ever since.  I took an upper division survey of programming languages class back in my college days at UT where I learned two things:  One, I never wanted to write Cobol again, and, two, ADA was really cool because it had generics.

I was really excited when c# 2.0 was announced and I heard that it would include generics.  Over the last couple of years I’ve had the opportunity to use generics in c# in the real world and I’ve generally been pleased with the results.  Most of what I’ve done has been simple, collection-related coding that made straight forward use of System.Collections.Generic classes.  I have also developed some relatively simple classes that either derived from built-in generic types or actually were generic themselves.

Given my quarter-century of development experience combined with my familiarity and practical use of generics in production-deployed code, I was surprised to find myself spinning my wheels for over a day on what seemed like a relatively simple task at the outset (How many times have I thought that :)).  So without further ado, let me share some valuable nuggets that I gathered over a couple of long days and late nights that didn’t produce what I expected.

I had an existing class, Series, that stored data for a single calendar day.  This is a very useful class in the power scheduling business domain that I work in because it provides:

  • varying the period of the data from 1 to 60 minutes
  • merging and splitting functionality to change the period of the data
  • mathematical operations with overloaded operators
  • daylight savings time transition support
  • loading and saving to and from datasets

If you’ve never worked with time-series data in a 24×7 sub-hourly scheduling environment, then you may not appreciate the complexities involved, particularly with respect to the daylight savings time transition days.  It’s incredible for me to think how much time we spend coding for those 2 hours out of 8,760 hours  in a year.  After years of experience, I’ve concluded that using GMT behind the scenes, and then computing the GMT start and and end times for a given calendar day is definitely the cleanest way to handle this.  One day has 23 hours, another has 25, and the rest have 24 but time remains continuous in GMT.  The previous hour is always one hour less and the next hour is always one hour more.  That sounds obvious, but its only true if you always stick with GMT.

I digress.  The Series class is great.  In fact, I also had a SeriesCollection class.  Series has a DateOf field and a Description field so stuffing a bunch of Series in a collection was very useful for displaying different types of data across time and even for displaying cross-tabbed data to the user in a grid.

There were several sub-classes but they weren’t sub-classes that added any real functionality.  They simply allowed you to store a different set fields/keys/tags with the series of numerical data for that day.  So when I needed to add yet another pair of Series, SeriesCollection sub-classes I decided enough was enough, time to make these generic.

My seemingly simple idea was to convert Series and SeriesCollection to Series<I> and SeriesCollection<I>, where I would be an interface that provided the “fields” that described the assocated Series.  I could really be any class you wanted as long as it implemented the ISeriesDescriptor interface.  Here’s a simplified, high-level overview of the interface and classes:

public interface ISeriesDescriptor {
 void GetColDefs(DataColumn[] cols);
 void GetValues(object[] vals);
 int Count { get; }
}

public class Series<I> where I : ISeriesDescriptor {
 double this[int] { get {...} set {...} }
 void AddInPlace(Series<I> other) {...}
 static DataSeries operator +(DataSeries d1, double val) {...}
 public static DataSeries operator +(DataSeries d1, double val) {...}
}

public class SeriesCollection<I> : IList<Series<I>>, ITypedList  where I : ISeriesDescriptor {...}

Those of you that have also been lured down this seemingly inviting path may recognize the dreaded nested <<>>.  I am leaving out several other classes that help provide the ITypedList implementation and some cool “virtual property” functionality for databinding.  The key here is that I blindly started replacing every occurence of Series with Series<I> and every occurence of what used to be “string Description” with “I Description.”  The <I> started to ripple outwards in a seemingly never-ending spiral.  Soon, I had 8 classes that where based on <I> in varying fashions. 

I (myself) was also using reflection in my SeriesCollection<I> class to add new instances of Series<I> to the collection.  So when I finally finished several hours of making changes, trying to compile, making more changes, ad infinitum, it was with great pleasure that I finally ran my newly compiling, super-generic code.  I then began modifying the code that would use this wonderful new construct.  I created subclasses of Series<I> and SeriesCollection<I> that didn’t add anything other than specific load from database methods.  Really all they did was invoke different methods on a data-access object and then populate the collection using a reflected constructor on Series<I>.  As I was doing this I thought shouldn’t I be using a factory method on a passed in object here?  Naw, the reflection keeps it more “generic.”

public class StockDescriptor : ISeriesDescriptor {...}
public class StockSeries : Series<StockDescriptor> {...}
public class StockSeriesCollection : SeriesCollection<StockDescripor> {...}

That was quick.  I run the code.  I am binding a StockSeriesCollection to a DataGridview.  Wow, that implementation of ITypedList is working beautifully.  I want to add some validation code so I need to take grd.Rows[e.RowIndex].DataBound and cast it to a StockSeries.  At run-time I get an invalid cast exception.  What?  You’re telling me I can’t cast a Series<StockDescriptor> to a StockSeries when StockSeries : Series<StockDescriptor>?  When I write this now it seems obvious that you can’t cast down the inheritance tree, only up. 

But I was so caught up in the generics aspect of it that I dug myself in even deeper.  I read the c# spec sections on generics and the type casting rules.  I try to solve the problem by adding a second type variable.  That takes a long time and yields exactly the same result. At 4am, I am really frustrated and dejected.  I go to bed.  The next morning I finally realize my simple mistake with the cast.  Then I started to look at what I’d done to my Series operators.  Before I changed to the generics, I could do:

VolumeSeries vs = new VolumeSeries();
// load it up
PriceSeries ps = new PriceSeries();
// load it up
PriceSeries result = (PriceSeries) ps * vs;

That wouldn’t be possible with my generic implementation because I hadn’t created a non-generic base or interface.  Something I hadn’t even thought about up front.  Unfortunately, this same limitation would cripple my SeriesCollection<I> class.  I could no longer put all of the different sub-classes of Series into a single SeriesCollection.  The bad news wasn’t over.  I also realized that my implementation of ITypedList which brought the ISeriesDescriptor properties “up one level” for data binding and turned the interval numeric values normally accessed via indexors into “virtual properties” for cross-tabbed display using System.Component.PropertyDescriptors wouldn’t handle a mixed collection anyway.

I hate that feeling that you just wasted a day or two of your life on what initially looked so simple and promising and turned out so ugly and inflexible.  Unfortunately, I didn’t have the time to properly refactor any of it.  I finally solved my immediate problem by replacing StockSeries with Series<StockDescriptor>.  I completed the UI component that depended on the Series and SeriesCollection. I could have just used the original set of classes and added just one more pair of subclasses an probably finished in 1/4th the time or even less.

The only silver lining of the whole affair is that I gained some valuable insight into non-trivial generic class design: generic type equivalence can be tricky and you will more than likely want a non-generic base or interface so that you can mingle different generic derivations in one collection or define operators on the base class.  I am still not sure whether using generics to achieve composition the way I tried is a good idea.  I also realized that my ITypedList implementation would need to be much more flexible to handle binding a mixed collection.  I relearned for the nth time the hard way that you really should stop and think hard about what your doing when you start changing the second or third class you weren’t expecting to touch.

—————————————–

P.S. (5/12/2007)

The classes described above have been working pretty well with some small refinements. Just because you have the <T> around, doesn’t mean you are always dealing directly with a C<T>. You may still need to reflect the actual type of the instance you are working on or one of it’s properties so that you can return properly typed object from operators.

For example:

public class SeriesCollection : List<Series<I>>, ITypedList {

public Series<I> GetTotal() {
    PropertyInfo pi = this.GetType().GetProperty(”Item”, new Type[] {typeof(int)});
    ConstructorInfo ci = pi.PropertyType.GetConstructor(new Type[] {typeof(DateTime), typeof(int)});
    DateTime dateOf = DateTime.Today;
    object obj = ci.Invoke(new object[] {dateOf, this._displayPeriod});
    Series<I> ret = (Series<I>) obj;
    foreach ( Series<I> ds in this ) {
       …total things up
    }
    return ret;
}
}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: