First of all, I would separate the packet parser from the data stream reader (so that I could write tests without accessing the stream). Then consider a base class that provides a method for reading in a package and one for writing a package.
In addition, I would create a dictionary (once, and then reuse it for future calls) as follows:
class Program { static void Main(string[] args) { var assembly = Assembly.GetExecutingAssembly(); IDictionary<byte, Func<Message>> messages = assembly .GetTypes() .Where(t => typeof(Message).IsAssignableFrom(t) && !t.IsAbstract) .Select(t => new { Keys = t.GetCustomAttributes(typeof(AcceptsAttribute), true) .Cast<AcceptsAttribute>().Select(attr => attr.MessageId), Value = (Func<Message>)Expression.Lambda( Expression.Convert(Expression.New(t), typeof(Message))) .Compile() }) .SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value })) .ToDictionary(o => o.Key, v => v.Value);
Edit: some explanation of what is going on here:
At first:
[Accepts(5)]
This line is a C # attribute (defined by AcceptsAttribute ), says the FooMessage class accepts message id 5.
Secondly:
Yes, a dictionary is built at runtime through reflection. You only need to do this once (I would put it in a singleton class that you can put a test case on it that you can run to ensure the dictionary is correctly built).
Third:
var m = messages[5]();
This line gets the following compiled lambda expression from the dictionary and executes it:
()=>(Message)new FooMessage();
(The cast is necessary in .NET 3.5, but not in 4.0 due to covariant changes in delagates, in 4.0 an object of type Func<FooMessage> can be assigned to an object of type Func<Message> .)
This lambda expression is built on the line of assignment of values ββwhen creating a dictionary:
Value = (Func<Message>)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile()
(The cast here requires the compiled lambda expression to be Func<Message> .)
I did it like this because I already have the type available to me at that moment. You can also use:
Value = ()=>(Message)Activator.CreateInstance(t)
But I think this will be slower (and the cast here needs to change Func<object> to Func<Message> ).
Fourth:
.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))
This was done because I felt that you might have a value when placing AcceptsAttribute more than once in a class (to accept more than one message identifier for each class). It also has a good effect on ignoring message classes that do not have a message identifier attribute (otherwise, the Where method would have to be difficult to determine if the attribute is present).