Thursday, February 8, 2007

Reflection factory method

I have a class BaseFoo and several classes that inherite from it.
I needed to create instances of the descendants classes based on an integer value loaded from a database row.
The typical solution for this is a factory method pattern, but this pattern requires updating of the factory method every time I create a new descendant ('Foo') class.
I wanted a way around this.

I needed a method which will extract all available classes from the assembly and will be able to locate the correct class according to the database row information and create a new instance.
At first I added a contant integer to each Foo class, but this meant using reflection to retrieve this constant, searching for the constant's name and comparing it to a hard-coded string - meaning total meltdown the minute I changed the constant name or used obfuscation.
Instead I created a new Enum containing a range of integer values, this meaning I will search for a type, not a string. During run-time, first I retrieve all Foo classes to a memory dictionary containing also the unique Enum value of each class.
Then for each database row I lookup the integer value (from the database) in the dictionary and use reflection to call the class's constructor.

private Dictionary FooClasses = new Dictionary();

//Create the memory table
private void BuildClassesMemoryTable()
{
Type[] types = Assembly.GetExecutingAssembly().GetTypes();
for (int i=0; i < types.Length; i++)
{
if (types[i].BaseType == typeof(BaseFoo))
{
foreach (FieldInfo fi in types[i].GetFields())
{
if (fi.FieldType == typeof(FooEnum))
{
FooClasses.Add((int)fi.GetValue(null), types[i]);
break;
}
}
}
}
}

//Create a new instance of a Foo class
private BaseFoo CreateKnownClass()
{
//Create an array of parameter types according to the constructor's signature to retrieve it
Type[] parameterTypes = new Type[2];
parameterTypes[0] = typeof(String);
parameterTypes[1] = typeof(int);

//Constructor's parameters
Object[] parameters = new Object[parameterTypes.Length];
parameters[0] = "Hello";
parameters[1] = 42;

return (BaseFoo)((Type)FooClasses[FooClass.ClassID]).GetConstructor(parameterTypes).Invoke(parameters);
}

private void LoadFromDatabase()
{
for (int i = 0; i < tables.fooDataSet.Rows.Count; i++)
{
//Some code to find the Foo class id from the database row.....


if (FooClasses.ContainsKey(fooClassID))
{
CreateKnownClass();
}
}
}

No comments: