Building dynamically look up table of classes with LINQ and Reflection

Here’s the problem: Having some ID, get a new instance of some class corresponding to this ID. All classes implement the same common interface (by which we will use them).

So we need to be able to write something like:

public IMessage DoSomethingWithMessage(Id id)

{

    IMessage message = MessageFactory.CreateMessage(id);

    message.DoSomething();

}

Now, how to actually implement MessageFactory, knowing that it needs to be really fast, and without having to explicitly change its implementation when new messages are added, Ids change and so on? And how to bind IDs to Messages?

Here’s my idea for the solution, and comments and improvements are more than welcome.

Each Message has its ID known beforehand, so it seems logical that the only place where this knowledge is kept should be the class itself. Now, I could define this ID as const, or static readonly field in each class, but this seems a little bit awkward to me.

First, I don’t gain anything by it, I can’t enforce a class to have a static field for once, and it doesn’t seem to belong there. It’s not used anywhere else than as a way to identify a class that should be instantiated in this one place, so it’s more like metadata on a class, rather than actual class’ data. That’s why I declared IdAttribute class that I decorate each Message class with.

Next thing: actual registration. How to have the cake (have all the types registered properly) and eat it (not having to change MessageFactory’ implementation each time I change a message). Since I’m already using reflection to get to each message’ Id, it seems reasonable to hit two birds with one stone, and get the types via reflection as well. Finally I came up with this LINQ query:

private static readonly IDictionary<int, MessageCreator> messages =

    (from t in Assembly.GetExecutingAssembly().GetTypes()

     where t.IsClass && !t.IsAbstract && t.Implements<IMessage>() && t != typeof (EmptyMessage)

     select t).ToDictionary(t => t.Attribute<IdAttribute>().Id, c => c.GetCreator());

Please ignore MessageCreator and other custom extension methods. We’ll get to them in a second.

Another problem is the actual instantiation. As I said, it needs to be very fast, so based on Oren‘s recent study, I decided to go where the dragons live and use DynamicMethod. It adds little overhead when the program starts to build methods, but it happens before the program gets under heavy fire of requests, so it’s fine.

I created delegate declaration for my creation methods

public delegate IMessage MessageCreator();

And then the actual method for creating certain type of Message (GetCreator extension method from above LINQ query).

public static MessageCreator GetCreator(this Type type)

{

    var method = new DynamicMethod("Create" + type.Name, type, new Type[0]);

    ILGenerator gen = method.GetILGenerator();

    ConstructorInfo ctor = type.GetConstructor();

    gen.Emit(OpCodes.Newobj, ctor);

    gen.Emit(OpCodes.Ret);

    return (MessageCreator)method.CreateDelegate(typeof (MessageCreator));

}

GetConstructor is extension method that gets default constructor.

Here’s the entire code:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Reflection;

using System.Reflection.Emit;

 

 

namespace Creation

{

    class Program

    {

        static void Main(string[] args)

        {

            IMessage message = MessageFactory.CreateMessage(1);

            Console.WriteLine(message.Write());

            IMessage empty = MessageFactory.CreateMessage(-1);

            Console.WriteLine(empty.Write());

        }

    }

 

    public class MessageFactory

    {

        private static readonly IDictionary<int, MessageCreator> messages =

            (from t in Assembly.GetExecutingAssembly().GetTypes()

             where t.IsClass && !t.IsAbstract && t.Implements<IMessage>() && t != typeof(EmptyMessage)

             select t).ToDictionary(t => t.Attribute<IdAttribute>().Id, c => c.GetCreator());

 

        private static readonly IMessage empty = new EmptyMessage();

 

        public static IMessage CreateMessage(int id)

        {

            if (messages.ContainsKey(id))

                return messages[id]();

            return empty;

        }

    }

 

    public delegate IMessage MessageCreator();

 

    public interface IMessage

    {

        string Write();

    }

    

    [Id(1)]

    public class Message1:IMessage

    {

        public string Write()

        {

            return ToString();

        }

    }

 

    [Id(2)]

    public class Message2:IMessage

    {

        public string Write()

        {

            return ToString();

        }

    }

 

 

    public class EmptyMessage:IMessage

    {

        public string Write()

        {

            return ToString();

        }

    }

 

    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]

    public sealed class IdAttribute : Attribute

    {

        private readonly int _id;

        public IdAttribute(int id) {

            _id = id;

        }

 

        public int Id

        {

            get { return _id; }

        }

    }

 

    public static class TypeExtensions

    {

        public static bool Implements<IInterface>(this Type type)

        {

            return type.GetInterfaces().Contains(typeof (IInterface));

        }

        public static TAttribute Attribute<TAttribute>(this Type type) where TAttribute:Attribute

        {

            return (TAttribute)type.GetCustomAttributes(typeof (TAttribute), false).Single();

        }

        public static ConstructorInfo GetConstructor(this Type type)

        {

            return type.GetConstructors().Single(c => c.IsPublic && c.GetParameters().Count() == 0);

        }

 

        public static MessageCreator GetCreator(this Type type)

        {

            var method = new DynamicMethod("Create" + type.Name, type, new Type[0]);

            ILGenerator gen = method.GetILGenerator();

            ConstructorInfo ctor = type.GetConstructor();

            gen.Emit(OpCodes.Newobj, ctor);

            gen.Emit(OpCodes.Ret);

            return (MessageCreator)method.CreateDelegate(typeof (MessageCreator));

        }

    }

}

There are few things I don’t like about this solution however.

Conventions. It’s a convention that each Message must have Id attribute, with unique value, and it’s a convention that each must have default public constructor. In the actual code GetConstructor and Attribute<TAttribute> methods throw descriptive exceptions when those conventions aren’t met, but it happens at runtime, not during compilation.

Reflection. It seems to be reasonable choice, but if there was some other solution…

Anyway, I’m open for suggestions on how to improve/change that.

Technorati Tags: , , ,