After some months of intensive C# learning and development, I've run into few limitations - things that Anders and others must have intentionally chosen not to do. For example, "Virtual Static Methods" - sounds all academic right ? How about this :
I am writing an object messaging library, and I want to have the objects themselves recognise their own binary stream, so that when deserializing an object from binary, I can run through a list of registered message classes, call a virtual function, and if it returns true, that is the correct class to use, and so I would call its Constructor to create an instance, and then read in its properties.
C# has two barriers to this that Delphi handles fine.
1) In C#, static functions cannot be virtual. In Delphi you would declare the message base class with a static virtual method
Recognise(TStream aStream): boolean
then override it with each specific type of message. In C# a method can be static or virtual, but not both. To use a virtual instance (non-static) method I must have already created the object, but I want to use the Recognise method to determine whether to instantiate that class in the first place ! You could I suppose implement the "prototype" design pattern, where you keep a list of objects (rather than classes) available for cloning.
2) In C#, constructors cannot be virtual either, so when I know what class to create, I can't just create it polymorphically.
Thankfully C# does have a very powerful equivalent to Delphi's RTTI (Run Time Type Information) known as Reflection and we can solve these problems with it.I solved the first issue with a static property called MessageID that must be declared in every message class (non-virtually). The message creating code keeps a list of registered classes (more on this later), reads a certain byte from the incoming stream and looks for a match with the values of MessageID in the list. It now has the class required and must create it (issue 2).
To do this, it again uses reflection in a method (see CreateFromClass() below) that will create an instance of any class having a constructor with no parameters.
AliasClassFactory contains the code for registering and creating the classes. Message classes can be specifically registered with their ID as a string (the string can of course be a number).
To get the MessageID property value from any class use this utility method :
public static object GetStaticProperty(Type aType, string aProperty) {
return aType.InvokeMember(aProperty, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy, null, null, null);
}
Whenever a method has a parameter of type Type like aType above (did you get that?), you can call it like this with a class :
int id = (int) GetStaticProperty(typeof(MyMessage), "MessageID")
or like this with an instance of a class
int id = (int) GetStaticProperty(myMessage.GetType(), "MessageID")
using System;
using System.Reflection;
using System.Collections;
using System.Diagnostics;
namespace myspace {
/// Register classes with aliases, then create them from the alias.
public class AliasClassFactory {
ArrayList list;
Type baseClass;
/// helper class used for each item in the list of classes
protected internal class AliasClassItem {
Type vClass;
string vAlias;
public Type Class {
get {
return vClass;
}
}
public string Alias {
get {
return vAlias;
}
}
public AliasClassItem(Type aClass, string aAlias) : base() {
this.vClass = aClass;
this.vAlias = aAlias;
}
}
///Create factory. Registered classes must descend from base class given here
///(can be object)
public AliasClassFactory(Type aBaseClass) : base () {
baseClass = aBaseClass;
list = new ArrayList();
}
///Classes must be registered with an alias before being created by the factory
public void Register(Type aClass, string aAlias) {
if (!aClass.IsSubclassOf(baseClass))
throw new Exception("Class must be a subclass of "+baseClass.FullName);
Type vClass = ClassFromAlias(aAlias);
if (vClass != null)
throw new Exception("Alias already registed with class "+vClass.FullName);
list.Add(new AliasClassItem(aClass,aAlias));
Trace.WriteLine("Class "+aClass.FullName+" was registered with alias :"+aAlias);
}
///Method for creating an object from a class. Requires constructor with no arguments.
///This limitation could be removed later.
public static object CreateFromClass(Type aClass) {
ConstructorInfo constructorInfo = aClass.GetConstructor(
BindingFlags.Instance | BindingFlags.Public,
null,
CallingConventions.HasThis,
new Type[0] {},//aPars,
null
);
if(constructorInfo != null)
return constructorInfo.Invoke(null);
else
throw new Exception("Constructor not found");
}
///Returns the class given an alias
public Type ClassFromAlias(string aAlias) {
foreach (object obj in list) {
if ( String.Compare( ((AliasClassItem) obj).Alias, aAlias, true)==0 ) {
return ((AliasClassItem) obj).Class;
}
}
return null;
}
///Creates a class given an alias.
public object CreateFromAlias(string aAlias) {
Type vClass = ClassFromAlias(aAlias);
if (vClass==null)
throw new Exception("Class not registered");
return CreateFromClass(vClass);
}
}
}