代码之家  ›  专栏  ›  技术社区  ›  Mike Spross Alex Martelli

如果不控制所涉及的类型,您将如何将“打开类型”重构为多态性?

  •  8
  • Mike Spross Alex Martelli  · 技术社区  · 17 年前

    microformats 我正在寻找一些重构建议。这可能是我在C#中尝试了一段时间的第一个“真正”的项目(在我的日常工作中,我几乎完全用VB6编程),所以我觉得这个问题可能会成为一系列问题中的第一个;-)

    让我提供一些到目前为止的背景资料,以便我的问题(希望)有意义。

    MicroformatsParser ,做所有的工作。它有一个重载构造函数,允许您传递 System.Uri 或者 string 包含URI:构建时,它下载给定URI处的HTML文档并将其加载到 HtmlAgilityPack.HtmlDocument

    基本API的工作原理是这样的(或者,一旦我完成了代码…),它将:

    MicroformatsParser mp = new MicroformatsParser("http://microformats.org");
    List<HCard> hcards = mp.GetAll<HCard>();
    
    foreach(HCard hcard in hcards) 
    {
        Console.WriteLine("Full Name: {0}", hcard.FullName);
    
        foreach(string email in hcard.EmailAddresses)
            Console.WriteLine("E-Mail Address: {0}", email);
    }
    

    这里使用泛型是有意的。我的灵感来自Firefox3中的微格式库(以及Ruby)的工作方式 mofo 宝石)。这里的想法是解析器完成繁重的工作(在HTML中查找实际的微格式内容)以及微格式类本身( HCard 在上面的示例中)基本上提供一个模式,告诉解析器如何处理它找到的数据。

    代码为 HCard 类应该更清楚地说明这一点(注意,这不是一个完整的实现):

    [ContainerName("vcard")]
    public class HCard
    {
        [PropertyName("fn")]
        public string FullName;
    
        [PropertyName("email")]
        public List<string> EmailAddresses;
    
        [PropertyName("adr")]
        public List<Address> Addresses;
    
        public HCard()
        { 
        }
    }
    

    解析器使用这里的属性来确定如何使用HTML文档中的数据填充类的实例。当您调用 GetAll<T>() :

    • 检查类型是否正确 T ContainerName 属性(并且它不是空的)
    • class 属性,该属性与 容器名称 . 将这些称为“容器节点”。
    • 对于每个容器节点:
      • 使用反射创建类型为的对象 .
      • MemberInfo[] )对于类型 通过反射
      • 对于每个字段的 MemberInfo
        • 如果该字段具有 PropertyName
          • 从HTML中获取相应微格式属性的值
          • 将HTML中的值注入字段(即,在类型为的对象上设置字段的值 T (在第一步中创建)
          • T List<T>
      • 列表<T> ,它现在包含一组微格式对象

    我正试图找出一种更好的方法来实施这一步骤 大胆的 . 问题是 Type

    例如,回到 HCard "email" 属性绑定到 EmailAddresses 字段,这是一个 List<string> “电子邮件” 父节点的子节点 "vcard" 节点,它必须将它们放在 .

    更重要的是,如果我想要我的 HCard List<HCard.TelephoneNumber> (这将有自己的 ContainerName("tel") 属性)来保存该信息,因为可以有多个 "tel" “电话” 格式有自己的子属性。但是现在解析器需要知道如何将电话数据放入 列表<HCard.电话号码> .

    同样的问题也适用于 Float DateTime s List<Float> s List<Integer> S、 等等。

    显而易见的答案是让解析器打开字段的类型,并对每种情况进行适当的转换,但我希望避免出现巨大的错误 switch 陈述注意,我不打算让解析器支持所有可能的功能 存在,但我希望它能够处理大多数标量类型 列表<T>

    关于如何最好地处理这个问题,有什么建议吗?

    由于解析器必须处理原始数据类型,我认为我不能在类型级别添加多态性。。。

    GetPropValue 重载像 GetPropValue(HtmlNode node, ref string retrievedValue) GetPropValue(HtmlNode, ref List<Float> retrievedValue)

    3 回复  |  直到 17 年前
        1
  •  5
  •   Jon Skeet    17 年前

    您可以使用一个简单的 IDictionary<Type,Delegate> T Func<ParseContext,T> -但是,对于单一类型(字符串、原语等),这不能用泛型来表示,但是您还需要检查列表、映射等。您将无法使用映射来实现这一点,因为您必须为每种类型的列表设置一个条目(例如,为 List<string> List<int> 等等)。如果你愿意将自己限制在 只是 某些具体类型,例如 List<T> 这很简单:

    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
    {
        // Handle lists
        // use type.GetGenericArguments() to work out the element type
    }
    

    IList<T> 对一些人来说 T T

        2
  •  4
  •   Mehrdad Afshari    17 年前

    您可以构造一个字典,将字符串映射到委托,并在需要使用适当的方法进行解析时查找,而不是使用一个大的switch语句。

        3
  •  2
  •   Marc Gravell    17 年前

    这与我的序列化引擎所遇到的问题非常相似( protobuf-net IList<T> Foo<T> ; 当T是 HCard ,我预先计算一个模型(即,我为每个属性创建一个对象,该对象可以解析/呈现该属性,并存储它们),该模型允许我处理 HCard