views:

164

answers:

4

I have a number of classes that reflect tables in a database. I would like to have a base class that has some basic functionality (say, it would have a "isDirty" flag), and a static array of strings with the column names as they appear in the database. The following code doesn't work but illustrates what I would like to do:

public class BaseRecord {  
  public bool isDirty;  
  public object [] itemArray;  
  public static string [] columnNames;    
}

public class PeopleRec : BaseRecord {  
}

public class OrderRec : BaseRecord {  
}

public static void Main() {
  PeopleRec.columnNames = new string[2];
  PeopleRec.columnNames[0]="FIRST_NAME";
  PeopleRec.columnNames[1]="LAST_NAME";

  OrderRec.columnNames = new string[4];
  OrderRec.columnNames[0] = "ORDER_ID";
  OrderRec.columnNames[1] = "LINE";
  OrderRec.columnNames[2] = "PART_NO";
  OrderRec.columnNames[3] = "QTY";
}

public class DoWork<T> where T : BaseRecord {
  public void DisplayColumnNames() {
    foreach(string s in T.columnNames)
      Console.Write("{0}", s);
    }
  public void DisplayItem(T t) {
    for (int i=0; i<itemValues.Length; i++) {
      Console.Write("{0}: {1}",t.columnNames[i],t.itemValues[i])
    }
  }
}

I would like each derived class to have it's own static array of strings of database column names, and I would like the generic class to access this static member without the need for an instance.

But it doesn't work:
(A) columnNames is the identical array in BaseRec, PeopleRec and OrderRec. I cannot have columnNames be different. BaseRec.columnNames.Length would be 3 because the columnNames in OrderRec is initialized last.
(B) The notation T.columnNames does not compile.

Any ideas on how to fix this?

+1  A: 

Why don't you just create separate classes of "Global Names," whose members are simply static constant strings containing the text you need to access.

public static class OrderRecNames{
    public static const string OrderId =  "ORDER_ID";
    public static const string Line = "LINE";
    public static const string PartNo = "PART_NO";
    public static const string Qty = "QTY";
}

Edit:

Better yet, make each static class of Global Names inner classes of the classes they describe, so instead of OrderRecNames.OrderId, you could type OrderRec.Names.OrderId, so the class's purpose is very obvious. Hope that helps!

Edit 2: (to be very specific)

public class BaseRecord {  
    public bool isDirty;  
    public object [] itemArray;  
}

public class PeopleRec : BaseRecord {  
    class Columns {
        public static const string FirstName =  "FIRST_NAME";
        public static const string LastName = "LAST_NAME";
    }
}

public class OrderRec : BaseRecord {  
    class Columns {
        public static const string OrderId =  "ORDER_ID";
        public static const string Line = "LINE";
        public static const string PartNo = "PART_NO";
        public static const string Qty = "QTY";
    }
}
Pwninstein
Because I wanted a generic class to work on any number of children of BaseRecord, I needed to have an array or List or something of the fields, and not named fields (as in, I could not have fields called FirstName, LastName, etc.). Also, could I access the "Columns" class like T.Columns.OrderID in a generic class?
Marc Meketon
A: 

Arrays are reference types. Just include a non-static property in the base, and implement it in your derived classes by creating a static member for all the instances to refer to. The overhead is really pretty small. I promise you won't notice.

Oh, and a ReadOnlyCollection<string> would be better than an array for those names. Something like this:

public class BaseRecord {  
  public bool isDirty;  
  public IEnumerable<Object> items;  
  public ReadOnlyCollection<string> columnNames;    
}

or

public class BaseRecord {  
  public bool isDirty;  
  public IList<Object> items;  

  public Object this[int index]
  {
     get { return items[index];}
     set { items[index] = value;}
  }

  public ReadOnlyCollection<string> columnNames;    
}

or with C# 4 (assuming my understanding of dynamic is correct):

public class BaseRecord {  
  public bool isDirty;  
  public IList<dynamic> items;  

  public dynamic this[int index]
  {
     get { return items[index];}
     set { items[index] = value;}
  }

  public ReadOnlyCollection<string> columnNames;    
}

Really, though, I question the benefits of this class. The interface it provides only gives you un-typed access to the actual data, and that isn't very useful. You might try to fix it with generics, but then you end up with something like this:

public Record<T>
{
    public bool isDirty;
    public ReadOnlyCollection<string> columnNames;
    public T data;
}

where each "T" is a completely different class with the fields from a single table. It doesn't give you good syntax, and by the time you implement those other classes you may just as well tack the column names and isDirty member in directly.

It seems to me that a better solution is when I previously mentioned an "interface". What you really want looks something more like this:

public IRecord
{
    bool isDirty;
    ReadOnlyCollection<string> columnNames;
}

or maybe:

public IRecord
{
    bool isDirty;
    ReadOnlyCollection<string> columnNames;

    dynamic this[int index]
    {
        get;
        set;
    }
}
Joel Coehoorn
I appreciate your answer. However, I'm concerned about doubling the storage needed. If each IRecord or BaseRecord or whatever has both it's data and the columnNames array, it's doubling (or more) the size. I'm already hitting the limits of memory for my application.
Marc Meketon
@Marc - You missed an important point: I'm **not** suggesting you keep a copy of the array for each IRecord! Arrays are reference types. I'm suggesting you build a separate static collection for your IRecord types and implement your columnNames property with a reference to that static collection. That's only 16 bytes per instance. I promise your app can handle that.
Joel Coehoorn
Joel: I certainly did miss the point. You're saying create the string of column names and store it somewhere, and have each instance of IRecord point (refer to) that array. I missed that when I first read it. Indeed, 16 bytes per instance will not be missed. Thanks!
Marc Meketon
+2  A: 

The issue is you that you want to associate some data with the types, not with instances of the types. I'm not sure that there's a neat way of doing this in C#, but one possibility is using a static Dictionary<Type, string[]> on BaseRecord. An example is below, you could neaten this up by adding some generic static members on BaseRecord for initializing/accessing the record names (and add some error checking...):

using System;
using System.Collections.Generic;

namespace Records
{
    public class BaseRecord
    {
        public bool isDirty;
        public object[] itemArray;

        public static Dictionary<Type, string[]> columnNames = new Dictionary<Type, string[]>();
    }

    public class PeopleRec : BaseRecord
    {
        static PeopleRec()
        {
            string[] names = new string[2];
            names[0] = "FIRST_NAME";
            names[1] = "LAST_NAME";
            BaseRecord.columnNames[typeof(PeopleRec)] = names;
        }
    }

    public class DoWork<T> where T : BaseRecord
    {
        public void DisplayColumnNames()
        {
            foreach (string s in BaseRecord.columnNames[typeof(T)])
                Console.WriteLine("{0}", s);
        }

        public void DisplayItem(T t)
        {
            for (int i = 0; i < t.itemArray.Length; i++)
            {
                Console.WriteLine("{0}: {1}", BaseRecord.columnNames[typeof(T)][i], t.itemArray[i]);
            }
        }
    }

    class Program
    {
        public static void Main()
        {
            PeopleRec p = new PeopleRec
            {
                itemArray = new object[] { "Joe", "Random" }
            };

            DoWork<PeopleRec> w = new DoWork<PeopleRec>();
            w.DisplayColumnNames();
            w.DisplayItem(p);
        }
    }
}
Dave
Thanks Dave. I considered using a Dictionary as a last resort (simply due to the little extra performance overhead), but I didn't think about using the typeof(T) as the key field. Your solution certainly is elegant.
Marc Meketon
A: 

If you had to have the static variable I would do this:

  public class BaseRecord
  {
     public bool isDirty;
     public object[] itemArray;
     public static string[] columnNames;

     public void DisplayColumnNames()
     {
        foreach (string s in columnNames)
           Console.Write("{0}\n", s);
     }

     public void DisplayItem()
     {
        for (int i = 0; i < itemArray.Length; i++)
        {
           Console.Write("{0}: {1}\n", columnNames[i], itemArray[i]);
        }
     }
  }

  public class PeopleRec : BaseRecord
  {
     public PeopleRec()
     {
     }
  }

  public class OrderRec : BaseRecord
  {
     public OrderRec()
     {
     }
  }

  public static void Main()
  {
     PeopleRec.columnNames = new string[2];
     PeopleRec.columnNames[0] = "FIRST_NAME";
     PeopleRec.columnNames[1] = "LAST_NAME";

     OrderRec.columnNames = new string[4];
     OrderRec.columnNames[0] = "ORDER_ID";
     OrderRec.columnNames[1] = "LINE";
     OrderRec.columnNames[2] = "PART_NO";
     OrderRec.columnNames[3] = "QTY";

     BaseRecord p = new PeopleRec();
     p.DisplayColumnNames();
     p.itemArray = new object[2];
     p.itemArray[0] = "James";
     p.itemArray[1] = "Smith";
     p.DisplayItem();

     BaseRecord o = new OrderRec();
     o.DisplayColumnNames();
     o.itemArray = new object[4];
     o.itemArray[0] = 1234;
     o.itemArray[1] = 1;
     o.itemArray[2] = 39874;
     o.itemArray[3] = 70;
     o.DisplayItem();
  }

Otherwise, I would change them to public properties (none static) and just add them to the created derived class array (remove the static call with a property call).

SwDevMan81
This won't work at all - the static array of column names lives in the base class, and will be overwritten when any of the subclasses are instantiated (create a PeopleRec, then an OrderRec, and inspect each of the created instances' static properties). Also, you probably shouldn't have that logic to setup these names in the constructor, since 99.9% of the time, column names don't change during execution.
Pwninstein