質問

I have a base Message class, and around 100 different subtype classes of Message that represent each type of message that can be processed. What I am currently considering doing is using a giant switch statement to create the message object. For example:

switch (MsgType)
{
   case MessageType.ChatMsg:
      Msg = new MsgChat(Buf);
      break;
   case MessageType.ResultMsg:
      Msg = new MsgResult(Buf);
      break;
   ... // 98 more case statements
}
Msg.ProcessMsg(); // Use a polymorphic call to process the message.

Is there a better way to do this? If so, can you show an easy code example.

EDIT

So, I tried doing this:

public class Test
{
   public Test()
   {
      IEnumerable<Type> myEnumerable = GetTypesWith<MyAttribute>(true);
   }

   IEnumerable<Type> GetTypesWith<TAttribute>(bool inherit)
      where TAttribute : System.Attribute
   {
      return from a in AppDomain.CurrentDomain.GetAssemblies()
             from t in a.GetTypes()
             where t.IsDefined(typeof(TAttribute), inherit)
             select t;
   }
}

This appears to work in that myEnumerable now contains all 100 of the message subtypes, plus the base Message type as well. However, while I don't mind using reflection at the beginning of the program to load the types, using it to access the proper object in real time might be too slow. So, I would like to try out using a delegate.

The example in the comment below from @Mark Hildreth:

"So, you'd have a dictionary of >. Then, your mappings would be mappings[MessageType.ChatMsg] = x => new MsgChat(x);"

There are a couple of ways to interpret this code. One idea is to remove all 100 subclasses and just use one massive class with 100 delegate methods. That is a distant 2nd choice. The other idea and my first choice is for the above code to somehow create a message subclass object. But, I don't quite understand how it would do this. Also, it would be nice to keep the above technique in my Test class of getting all the types or delegates without having to write all 100 of them. Can you or anyone else explain how this can be done?

役に立ちましたか?

解決

Instead using a giant switch statement, you can define a Dictionary to map each MessageType value to its defined Message derived class and creates an instance using this mapping data.

Dictionary definition:

Dictionary<int, Type> mappings = new Dictionary<int, Type>();
mappings.Add(MessageType.ChatMsg, typeof(MsgChat));
mappings.Add(MessageType.ResultMsg, typeof(MsgResult));

...

Dictionary consumption:

ConstructorInfo ctor = mappings[MessageType.ChatMsg].GetConstructor(new[] { typeof(Buf) });
Message message = (Message)ctor.Invoke(new object[] { Buf });

Note that I don't compiled this code to verify if is correct or not. I only want to show you the idea.

EDIT

There is my new answer to improve the first one. I'm thinking on your edited question, using given ideas from @MikeSW and @Mark Hildreth.

public class FactoryMethodDelegateAttribute : Attribute
{
    public FactoryMethodDelegateAttribute(Type type, string factoryMethodField, Message.MessageType typeId)
    {
        this.TypeId = typeId;
        var field = type.GetField(factoryMethodField);
        if (field != null)
        {
            this.FactoryMethod = (Func<byte[], Message>)field.GetValue(null);
        }
    }

    public Func<byte[], Message> FactoryMethod { get; private set; }
    public Message.MessageType TypeId { get; private set; }
}

public class Message
{
    public enum MessageType
    {
        ChatMsg,
    }
}

[FactoryMethodDelegate(typeof(ChatMsg), "FactoryMethodDelegate", Message.MessageType.ChatMsg)]
public class ChatMsg : Message
{
    public static readonly MessageType MessageTypeId = MessageType.ChatMsg;
    public static readonly Func<byte[], Message> FactoryMethodDelegate = buffer => new ChatMsg(buffer);
    public ChatMsg(byte[] buffer)
    {
        this.Buffer = buffer;
    }

    private byte[] Buffer { get; set; }
 }

public class TestClass
{
    private IEnumerable<Type> GetTypesWith<TAttribute>(bool inherit) where TAttribute : Attribute
    {
        return from a in AppDomain.CurrentDomain.GetAssemblies()
               from t in a.GetTypes()
               where t.IsDefined(typeof(TAttribute), inherit)
               select t;
    }

    [Test]
    public void Test()
    {
        var buffer = new byte[1];
        var mappings = new Dictionary<Message.MessageType, Func<byte[], Message>>();
        IEnumerable<Type> types = this.GetTypesWith<FactoryMethodDelegateAttribute>(true);
        foreach (var type in types)
        {
            var attribute =
                (FactoryMethodDelegateAttribute)
                type.GetCustomAttributes(typeof(FactoryMethodDelegateAttribute), true).First();

            mappings.Add(attribute.TypeId, attribute.FactoryMethod);
        }

        var message = mappings[Message.MessageType.ChatMsg](buffer);
    }
}

他のヒント

You're on a right track and using a dictionary is a good idea. If reflection is too slow you can use expressions, like this (I'm assuming you decorate the Messages classes with a MessageTypeAttribute).

public class Test
{
 public Test()
  {
     var dict=new Dictionary<MessageType,Func<Buffer,Mesage>>();
     var types=from a in AppDomain.CurrentDomain.GetAssemblies()
         from t in a.GetTypes()
         where t.IsDefined(MessageTypeAttribute, inherit)
         select t;
    foreach(var t in types) {
      var attr = t.GetCustomAttributes(typeof (MessageTypeAttribute), false).First();
       dict[attr.MessageType] = CreateFactory(t);
       }

      var msg=dict[MessageType.Chat](Buf);
  }

 Func<Buffer,Message> CreateFactory(Type t)
 {
      var arg = Expression.Parameter(typeof (Buffer));
        var newMsg = Expression.New(t.GetConstructor(new[] {typeof (Buffer)}),arg);
        return Expression.Lambda<Func<Buffer, Message>>(newMsg, arg).Compile();
}

}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top