我正在用C#编写一个微格式解析器,我正在寻找一些重构建议.这可能是我在C#中尝试了一段时间的第一个"真正的"项目(我在日常工作中几乎完全用VB6编程),所以我觉得这个问题可能成为系列中的第一个;-)
让我提供一些关于我到目前为止的背景,以便我的问题(希望)有意义.
现在,我有一个班级,完成MicroformatsParser
所有工作.它有一个重载的构造函数,允许你传递System.Uri
或string
包含一个URI:在构造时,它会在给定的URI下载HTML文档并将其加载到一个HtmlAgilityPack.HtmlDocument
容易操作的类中.
基本API就像这样(或者,一旦我完成代码......):
MicroformatsParser mp = new MicroformatsParser("http://microformats.org"); Listhcards = mp.GetAll (); 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); }
这里使用泛型是有意的.我从Firefox 3中的Microformats库(以及Ruby mofo
gem)的方式中获得灵感.这里的想法是解析器执行繁重的工作(在HTML中查找实际的微格式内容),微格式类本身(HCard
在上面的示例中)基本上提供了告诉解析器如何处理它找到的数据的模式.
HCard
该类的代码应该更清楚(注意这不是一个完整的实现):
[ContainerName("vcard")] public class HCard { [PropertyName("fn")] public string FullName; [PropertyName("email")] public ListEmailAddresses; [PropertyName("adr")] public List Addresses; public HCard() { } }
解析器使用此处的属性来确定如何使用HTML文档中的数据填充类的实例.调用时,解析器执行以下操作GetAll
:
检查类型T
是否具有ContainerName
属性(并且它不是空白)
在HTML文档中搜索具有与之class
匹配的属性的所有节点ContainerName
.将这些称为"容器节点".
对于每个容器节点:
使用反射创建类型的对象T
.
通过反射获取公共字段(a MemberInfo[]
)的类型T
对于每个领域 MemberInfo
如果该字段具有PropertyName
属性
从HTML获取相应微格式属性的值
将HTML中找到的值注入字段(即T
在第一步中创建的类型对象上设置字段的值)
将类型的对象添加T
到aList
返回List
,现在包含一堆微格式对象
我正试图找出一种更好的方法来实现粗体步骤.问题是,Type
微格式类中的给定字段不仅决定了HTML中要查找的节点,还决定了如何解释数据.
例如,回到HCard
上面定义的类,"email"
属性绑定到EmailAddresses
字段,即a List
.解析器在HTML中找到"email"
父"vcard"
节点的所有子节点后,必须将它们放在一个List
.
更重要的是,如果我希望我HCard
能够返回电话号码信息,我可能希望能够声明一个新的类型字段List
(具有自己的ContainerName("tel")
属性)来保存该信息,因为可以有多个"tel"
元素HTML,"tel"
格式有自己的子属性.但现在解析器需要知道如何将电话数据放入List
.
同样的问题适用于Float
S,DateTime
S,List
S,List
S等.
显而易见的答案是让解析器切换到字段类型,并为每种情况做适当的转换,但我想避免一个巨大的switch
声明.请注意,我并不打算让解析器支持所有可能Type
存在的,但我希望它能处理大多数标量类型及其List
版本,以及识别其他微格式类的能力(以便微格式类可以由其他微格式类组成).
关于如何最好地处理这个的任何建议?
由于解析器必须处理原始数据类型,我认为我不能在类型级别添加多态性...
我首先想到的是使用方法重载,所以我想有一个系列的GetPropValue
像重载GetPropValue(HtmlNode node, ref string retrievedValue)
,GetPropValue(HtmlNode, ref List
等等.但我不知道是否有解决这个问题的更好方法.
Mehrdad的方法基本上是我建议的方法,但作为可能更多的第一步.
您可以使用一个简单的IDictionary
(其中每个条目实际上是从T
到Func
-但不能用泛型来表示),单类型(字符串,基元等),但是你还需要检查列表,地图等你赢"T能够做到这一点使用的地图,因为你必须为每个类型列表中(即单独的条目的条目List
,List
等等).泛型使这非常棘手 - 如果你很乐意将自己局限于某些具体类型,比如List
你会让自己更容易(但不那么灵活).例如,检测List
很简单:
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) { // Handle lists // use type.GetGenericArguments() to work out the element type }
检测类型是否IList
为某些T
(然后发现T
)实现可能是一种痛苦,特别是因为可能存在多个实现,并且具体类型本身可能是也可能不是通用的.如果您真的需要成千上万开发人员使用的非常灵活的库,那么这项工作可能是值得的 - 但我会保持简单.