Introduction
This article explains the use of the System.Reflection
namespace classes to enumerate what classes have been derived from a selected base class at run-time. Instances of the derived classes are then created and stored in an array. The array can then be enumerated for a suitable class instance by an alternative implementation.
This is useful if you wish to derive classes from a selected base and not have to tie them directly into code. Ensuring that they are derived from a single base class allows a minimum implementation to exist.
Using this concept allows generic code to be implemented and modified without having to update any tedious cross references. Being able to derive from a generic base class and then use it generically provides powerful flexibility.
The Theory
The System.Reflection
namespace allows the code to be enumerated at run-time. Basically, the code knows about itself, and understands what implementation details exist for the application.
The enumeration of the base class is performed using a simple enumeration of the assembly:
Assembly myAssembly = Assembly.GetEntryAssembly();
Type[] myTypes = myAssembly.GetTypes();
foreach(Type currentType in myTypes)
{
...
}
This firstly determines what assembly is calling the enumeration method, and then determines what types exist in that assembly. Then, each type is determined. An array of types then exist, and the next stage can be performed, which is determing what class they are derived from and creating an instance of it to store in an array:
if ( currentType.BaseType.Name == "State" )
{
System.Reflection.ConstructorInfo creator =
currentType.GetConstructor( new Type[] { } );
State newState = (State)creator.Invoke( new object[] { } );
states.Add(newState);
}
The code above firstly determines the base class type of the class (in this case "State
"), and then creates an instance of it using the ConstructorInfo
class. Finally, the newly created class instance is stored in an array for recalling later.
An Example
To explain this concept, we really need to work on an example to see how useful this might be. For the following example, we're going to use the concept of some system being in selected 'States'. When entering these states, we need to do some processing, and when we leave, we need to free some resources.
A simple base class could be defined as:
using System;
namespace TransitionDemo.States
{
public abstract class State
{
private String name = String.Empty;
public String Name { get { return name; } }
public State(String theName)
{
this.name = theName;
}
public abstract void OnEnter();
public abstract void OnLeave();
}
}
From this, we will derive a sample class, for now called State1
:
using System;
namespace TransactionSample
{
public class State1 : TransitionDemo.States.State
{
public override void OnEnter()
{
System.Windows.Forms.MessageBox.Show("State 1 enetred");
}
public override void OnLeave()
{
System.Windows.Forms.MessageBox.Show("State 1 left");
}
public State1() : base("State 1")
{
}
}
}
Now, let's assume that we want a generic solution that doesn't really understand the derived states, but offers the user the ability to change states. We don't want to hard-code the class type somewhere, what if we want to add more? Things might get messy.
We use a State Manager class - our derived class manager. The code for such a class is shown below:
using System;
using System.Reflection;
namespace TransitionDemo.States
{
public class DynamicStateManager
{
public static StateManager Manager = new StateManager();
private ArrayList states = new ArrayList();
private State currentState = null;
public String State
{
get
{
String selectedState = String.Empty;
if ( currentState != null )
selectedState = currentState.Name;
return selectedState;
}
set
{
int selectedIndex = GetState(value);
if ( selectedIndex >= 0 )
{
if ( currentState != null )
currentState.OnLeave;
currentState = states[selectedIndex];
currentState.OnEnter();
}
else
{
throw new InvalidOperationException();
}
}
}
private StateManager()
{
Assembly myAssembly = Assembly.GetEntryAssembly();
Type[] myTypes = myAssembly.GetTypes();
foreach(Type currentType in myTypes)
{
if ( currentType.BaseType.Name == "State" )
{
ConstructorInfo creator = currentType.GetConstructor(
new Type[] { } );
State newState = (State)creator.Invoke( new object[] { } );
states.Add(newState);
}
}
}
public State this[int index]
{
get { return (State)states[index]; }
}
public int Count { get { return states.Count; } }
public int GetState( String theState)
{
int entryIndex = -1;
for( int i=0; i < states.Count; i++ )
{
State state = (State)states[i];
if ( state.Name == theState )
{
entryIndex = i;
}
}
return entryIndex;
}
public bool Contains( String theState)
{
return ( GetState(theState) != 0 );
}
}
}
The state definition is name-driven, meaning that the user could be selecting the state from a list, and it simply gets implemented.
The Demo
The demo expands on this concept, and introduces a TransitionManager
that defines what states can be moved between and performs an operation during the transition. This is a good concept when working with state machines.
New states can be added by being derived from a base class, and a transition between the states can be derived from a base class and defines the rules of the transition.
Conclusion
The concept of a derived class manager has only become simple enough to implement efficiently with the introduction of .NET and the System.Reflection
namespace. The ability to analyse the code at run-time allows new possibilities in inheritience that would otherwise have to be hard coded.