#Google Analytic Tracker

Pages

May 21, 2008

Customizing a Base Class Static Members thought its Derived Class

In my programming experience, it usually doesn't make sense to customize a static member. In short, implementing a static member is not part of the object orientated programing technique.

In C#, at least as far as I know, you cannot override a static member. For example:

   public class MyClass
   {
        //Compiling error: Overridable method cannot be static
        public static virtual void Do(){// Do something}
   }
   public class DerivedClass : MyClass
   {
        public override void Do(){// Do something else}
   }

A static method is like a global method that can be called anywhere in your code without instantiate an object. Therefore, it doesn't make sense you can override a base static method.

A Situation when Customizing a Static Method is Needed.

Last year, I needed to create a brunch of data access classes. A data access class's primary purpose is to retrieve and transfer data between an application and a database. In design, you would expect you to see something like this:

MyVehicleData vehicle = new MyVehicleData();
//...
//... Do something with vehicle object
//...

//Save to database
vehicle.Save();
However, what if you want to write Select()? Does it make sense that you need to instantiate an object and then call the Select()?
MyVehicleData vehicle = new MyVehicleData(); 
vehicle.SelectByKey("somekey");
Instead, you probably want to do something like:
MyVehicleData vehicle = MyVehicleData.SelectBykey("somekey");

The above SelectByKey() method is a static method. In the code, most likely you can hardcode your select SQL.

What if you want to 100 data access classes?

As a smart lazy programmer, you don't want to hardcode your SQL statements for every business objects. You want to write a base class to save you time from repetitive coding. Even better, you can write a code generator to create your data access class. However, code generation is outside of my discussion.

This is what we would like:

// We want to do something like this:
// NOTE: This is just an idea, you will get compiling error
public abstract class BaseDataAccess<TDataAccess>{
    protected static string selectSQL;

    // Returns an instance of Data Access object of type TDataAccess
    public static TDataAccess SelectByKey(string key)
    {
        // note: RunSQL get column data based on the select SQL and the parameter
        string[] retrievedData = RunSQL(selectSQL, key);
        
        T result = TDataAccess.CreateData(retrievedData);
        return result;
    }
    
    // Creates a new instance of TDataAccess based on database values
    // Note: Compiling error: you can't have static method in with abstract declaration
    public abstract static TDataAccess CreateData(string[] values);
}

Solution

In the above example, it won't compile because abstract static is not allowed.  From the start, it doesn't make sense you can decorate the base class though its derived classes. This is time when Generic come into rescue. The solution is to create an instance of the derived class within the generic base class static constructor.  Please take a look at the following example:

// Don't get confused by the following declaration. What it said is that when you deriving // from this base class, you have to pass in a type that is also a // based from BaseDataAccess<TDataAccess>.public abstract class BaseDataAccess<TDataAccess> : where TDataAccess : BaseDataAccess<TDataAccess>, new()
{
    protected static string selectSQL;
    protected static BaseDataAccess<T> instance;
    
    // This is a static, it only run once!
    static BaseDataAccess()
    {
        instance = new T();
        selectSQL = instance.GetSelectSQL(); // virtual method call
    }

    // Return a instance of TDataAcess from database
    public static TDataAccess SelectByKey(string key)
    {
        // note: DataManager.RunSQL get data based on the select SQL and the parameter
        string[] retrievedData = DataManager.RunSQL(selectSQL, key);
        
        T result = instance.CreateData(retrievedData);
        return result;
    }
    
    // Create a new instance of TDataAccess based on database values
    // Note: Compiling error: you can't have static method in with abstract declaration
    protected abstract TDataAccess CreateData(string[] values);
    
    protected abstract string GetSelectSQL();
}

public class DerivedDataAccess : BaseDataAccess<DerivedDataAccess>
{
    // Parameterless constructor, this allow the base class to create an instance of its derived class.
    public DerivedDataAccess()
    {
    }
    
    public DerivedDataAccess(string[] values)
    {
        //Initialize your object;
    }
    
    // override the CreateData, this is how you customize the behaviour of your base class
    protected override DerivedDataAccess CreateData(string[] values)
    {
        DerivedDataAccess result = new DerivedDataAccess(values);
        return result;
    }
    
    protected override GetSelecteSQL()
    {
        // This virtual method will be called by base constructor, it should only use constant data
        return "select x, y, z from someTable";
    }
}

By using generic, the compiler create many type of base class for you. In addition, you instantiate a derived object so that you can get the information from the derided class.

Note that your base class static constructor only execute once, it won't re-initialize the base static constructor when you call "instance = new T()", hence, there is no infinite loop.

Also, you need to specify the where TDataAccess : BaseDataAccess<TDataAccess>, new() so that you can call the base class method. By specifying the generic type to be the BaseDataAccess, you can call the derived class virtual methods.  Just a side note, be careful when you call a virtual method in the constructor, because your object is not initialized, therefore any dynamic data will not be setup for your implemented virtual method to use.  The new() tells the compiler that your class support parameter-less constructor. Therefore you can instantiate a generic type though new T();

As a result, you can no do the following:

DerviedDataAccess myDataAccess = DerviedDataAccess.SelectByKey("someKey");

Final Thought

When I did this program, I was still using C# 2.0. However, in C# 3.0, the above idea can also be achieve by partial method.  In any case, it is still a neat technique.


 

No comments: