如果你不控制所涉及的类型,你如何重构"打开类型"是多态的?

Mik*_*oss 8 c# oop generics reflection

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

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

现在,我有一个班级,完成MicroformatsParser所有工作.它有一个重载的构造函数,允许你传递System.Uristring包含一个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);
}
Run Code Online (Sandbox Code Playgroud)

这里使用泛型是有意的.我从Firefox 3中的Microformats库(以及Ruby mofogem)的方式中获得灵感.这里的想法是解析器执行繁重的工作(在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()
    { 
    }
}
Run Code Online (Sandbox Code Playgroud)

解析器使用此处的属性来确定如何使用HTML文档中的数据填充类的实例.调用时,解析器执行以下操作GetAll<T>():

  • 检查类型T是否具有ContainerName属性(并且它不是空白)
  • 在HTML文档中搜索具有与之class匹配的属性的所有节点ContainerName.将这些称为"容器节点".
  • 对于每个容器节点:
    • 使用反射创建类型的对象T.
    • 通过反射获取公共字段(a MemberInfo[])的类型T
    • 对于每个领域 MemberInfo
      • 如果该字段具有PropertyName属性
        • 从HTML获取相应微格式属性的值
        • 将HTML中找到的值注入字段(即T在第一步中创建的类型对象上设置字段的值)
        • 将类型的对象添加T到aList<T>
    • 返回List<T>,现在包含一堆微格式对象

我正试图找出一种更好的方法来实现粗体步骤.问题是,Type微格式类中的给定字段不仅决定了HTML中要查找的节点,还决定了如何解释数据.

例如,回到HCard上面定义的类,"email"属性绑定到EmailAddresses字段,即a List<string>.解析器在HTML中找到"email""vcard"节点的所有子节点后,必须将它们放在一个List<string>.

更重要的是,如果我希望我HCard能够返回电话号码信息,我可能希望能够声明一个新的类型字段List<HCard.TelephoneNumber>(具有自己的ContainerName("tel")属性)来保存该信息,因为可以有多个"tel"元素HTML,"tel"格式有自己的子属性.但现在解析器需要知道如何将电话数据放入List<HCard.TelephoneNumber>.

同样的问题适用于FloatS,DateTimeS,List<Float>S,List<Integer>S等.

显而易见的答案是让解析器切换到字段类型,并为每种情况做适当的转换,但我想避免一个巨大的switch声明.请注意,我并不打算让解析器支持所有可能Type存在的,但我希望它能处理大多数标量类型及其List<T>版本,以及识别其他微格式类的能力(以便微格式类可以由其他微格式类组成).

关于如何最好地处理这个的任何建议?

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

我首先想到的是使用方法重载,所以我想有一个系列的GetPropValue像重载GetPropValue(HtmlNode node, ref string retrievedValue),GetPropValue(HtmlNode, ref List<Float> retrievedValue)等等.但我不知道是否有解决这个问题的更好方法.

Jon*_*eet 5

Mehrdad的方法基本上是我建议的方法,但作为可能更多的第一步.

您可以使用一个简单的IDictionary<Type,Delegate>(其中每个条目实际上是从TFunc<ParseContext,T>-但不能用泛型来表示),单类型(字符串,基元等),但是你还需要检查列表,地图等你赢"T能够做到这一点使用的地图,因为你必须为每个类型列表中(即单独的条目的条目List<string>,List<int>等等).泛型使这非常棘手 - 如果你很乐意将自己局限于某些具体类型,比如List<T>你会让自己更容易(但不那么灵活).例如,检测List<T>很简单:

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

检测类型是否IList<T>为某些T(然后发现T)实现可能是一种痛苦,特别是因为可能存在多个实现,并且具体类型本身可能是也可能不是通用的.如果您真的需要成千上万开发人员使用的非常灵活的库,那么这项工作可能是值得的 - 但我会保持简单.