This something is always a large switch block. Sometimes in the base message class (it creates a circular coupling in this case) or in another factory/helper class that must know the world.
Using LINQ expression to emit IL code at runtime and Reflection to discover all subclasses, this code generates the switch block at runtime. As such, the base message class can only know itself and the API it exposes, but still provide a factory method to create the proper message object based on the type.
Code:
using System; using System.Diagnostics.Contracts; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.InteropServices; namespace COServer.Protocol { public abstract class Msg { private static readonly Func<int, Msg> factoryFct; static Msg() { var assembly = Assembly.GetAssembly(typeof(Msg)); var msgTypes = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Msg)) && !t.IsGenericType).ToArray(); { var typeParam = Expression.Parameter(typeof(int), "type"); var msgVar = Expression.Variable(typeof(Msg), "msg"); var switchCases = new SwitchCase[msgTypes.Length]; var defaultCase = Expression.Assign(msgVar, Expression.Constant(null, typeof(Msg))); for (int i = 0; i < msgTypes.Length; ++i) { var createMethod = msgTypes[i].BaseType.GetMethod("Create", BindingFlags.Public | BindingFlags.Static); var testValue = msgTypes[i].GetField("Type", BindingFlags.Public | BindingFlags.Static).GetValue(null); switchCases[i] = Expression.SwitchCase( Expression.Assign(msgVar, Expression.Call(null, createMethod)), Expression.Constant(testValue)); } var switchExpr = Expression.Switch(typeParam, defaultCase, switchCases); var returnExpr = Expression.Assign(msgVar, msgVar); // not truly a return, but equivalent var expr = Expression.Block(new[] { msgVar }, switchExpr, returnExpr); factoryFct = Expression.Lambda<Func<int, Msg>>(expr, typeParam).Compile(); } } public static Msg Create(int type) { return factoryFct(type); } public abstract void Serialize(byte[] buf, int offset, int len); public abstract void Deserialize(byte[] buf, int offset, int len); public abstract void Recycle(); } public abstract class Msg<T> : Msg where T : Msg<T> { private static ObjectPool<T> messages; static Msg() { messages = new ObjectPool<T>(GetGenerator()); } private static Func<T> GetGenerator() { var newExp = Expression.New(typeof(T)); return Expression.Lambda<Func<T>>(newExp).Compile(); } public static T Create() { Contract.Ensures(Contract.Result<T>() != null); return messages.Get(); } public override void Recycle() { messages.Put((T)this); } }
Code:
public sealed unsafe class MsgAccount : Msg<MsgAccount> { public static readonly int Type = 1051; public static readonly int MinSize = sizeof(NetworkMessage); public static readonly int MaxSize = MinSize; // ... }