【问题标题】:Best way to handle creation of large number of subtype objects处理创建大量子类型对象的最佳方法
【发布时间】:2013-01-20 23:34:01
【问题描述】:

我有一个基本的 Message 类,以及大约 100 个不同的 Message 子类型类,它们代表可以处理的每种类型的消息。我目前正在考虑做的是使用一个巨大的 switch 语句来创建消息对象。例如:

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.

有没有更好的方法来做到这一点?如果是这样,你能展示一个简单的代码示例吗?

编辑

所以,我尝试这样做:

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;
   }
}

这似乎有效,因为 myEnumerable 现在包含所有 100 个消息子类型,以及基本消息类型。然而,虽然我不介意在程序开始时使用反射来加载类型,但使用它来实时访问正确的对象可能太慢了。所以,我想尝试使用委托。

@Mark Hildreth 下面评论中的示例:

“所以,你会有一个 > 的字典。然后,你的映射将是 mappings[MessageType.ChatMsg] = x => new MsgChat(x);”

有几种方法可以解释此代码。一种想法是删除所有 100 个子类,只使用一个具有 100 个委托方法的大型类。这是一个遥远的第二选择。另一个想法和我的第一选择是让上面的代码以某种方式创建一个消息子类对象。但是,我不太明白它会如何做到这一点。此外,最好将上述技术保留在我的 Test 类中,以获取所有类型或委托,而不必编写全部 100 个。您或其他人能解释一下如何做到这一点吗?

【问题讨论】:

  • 一个具有类型属性的类可能就足够了,为什么还需要 100 个类?
  • 利用多态性,横向传播系统的智能。

标签: c# oop creation subtype


【解决方案1】:

您可以定义一个Dictionary 来将每个MessageType 值映射到其定义的Message 派生类,而不是使用巨大的switch 语句,并使用此映射数据创建一个实例。

字典定义:

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

...

字典消费:

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

请注意,我没有编译此代码来验证是否正确。我只是想向你展示这个想法。

编辑

有我的新答案来改进第一个。我正在考虑您编辑的问题,使用 @MikeSW@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);
    }
}

【讨论】:

  • 这是我会使用的一般想法。但是,我建议通过使用函数作为字典的值来避免反射。所以,你会有一本&lt;MessageType, Func&lt;Buffer, Message&gt;&gt; 的字典。然后,您的映射将是 mappings[MessageType.ChatMsg] = x =&gt; new MsgChat(x);
  • 另外,请记住,通过将属性添加到您的 Message 类,并使用代码查找所有这些类并自动构建字典,可以更轻松地自动化此类事情。请参阅this question 了解如何搜索属性。
  • 非常有趣。因此,听起来我应该能够在基类中指定一个属性以及 Inherited = True 开关,以便所有子类都可以使用它。然后可能在基类中指定第二个属性,这样基类就不会被链接中定义的 GetTypesWith 方法拾取。不过,一个问题是 Func 定义了一个带有 2 个参数的通用委托方法,还是只有一个?我所有的子类构造函数都只需要一个参数——Buffer,它被定义为一个字节数组。我不确定第二个参数的用途。
  • Func&lt;Buffer, Message&gt; 上定义的最后一个类型是Func&lt;T, TResult&gt; 委托的返回类型。 MSDN
  • 谢谢。刚刚尝试通过您的代码进行调试,它运行良好。
【解决方案2】:

您走在正确的轨道上,使用字典是个好主意。如果反射太慢,你可以使用表达式,像这样(我假设你用 MessageTypeAttribute 装饰 Messages 类)。

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();
}

}

【讨论】:

  • 有趣的想法,但我看到您的代码中有一个 Compile 方法。不知道这是否一定比反射更好。我现在正在看的是简单地摆脱我编写的 100 个类并用方法替换它们。 Message 然后会变成某种超类,但它会支持静态链接,并且将是处理消息的最有效方式。
  • 它比反射更好,因为它是光代码生成。它几乎与手动编写新消息(buf)一样快。 compile() 只被调用一次来创建 Func。一旦创建,它就像任何其他 Func 一样。我正在使用类似的方法为 SqlFu(我的 microOrm)生成映射代码,而且速度很快。恕我直言,将这些类组合成一个大类并不是明智的方法。多类方法是可扩展的,上面的代码适用于 1 个或 1000 个类而无需更改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-26
  • 2013-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-17
  • 1970-01-01
相关资源
最近更新 更多