用C#实现仿Ruby的XML Builder
Ruby中有个很好用的XML API,在页面实现XML输出很方便。比如有个联系人Contacts的XML输出,Ruby里面是这样写的。
... xml.contacts do xml.contact(:type => "work") do xml.name("Joe Smith") end end ...
借用.Net 4.0的dynamic特性,用C#实现一个XmlBuilder。测试代码如下:

[TestFixture]
public class XmlBuilderTest
{
[Test]
public void CreatorTest()
{
dynamic xmlBuilder = new XmlBuilder();
var result = xmlBuilder.Contacts(
xmlBuilder.Contact("type", "team-member", "id", "1",
xmlBuilder.Name("Joe Smith"),
xmlBuilder.Phone("type", "work", "(123) 456-7890"),
xmlBuilder.爱好("看电影"),
xmlBuilder.Address("type", "home",
xmlBuilder.Street("123 Main St."),
xmlBuilder.City("SpringField"))));
Console.WriteLine("XML: \n" + result.ToString());
}
}
为实现上述功能,我们创建类XmlBuilder,继承DynamicObject,并override其TryInvokeMember方法。

public class XmlBuilder : DynamicObject
{
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
var arguments = ParseArguments(args);
var rootElement = new XElement(binder.Name, arguments.Text, arguments.Attributes);
foreach (XDocument xDocument in arguments.Documents)
rootElement.Add(xDocument.FirstNode);
result = new XDocument(rootElement);
return true;
}
private static dynamic ParseArguments(object[] arguments)
{
var documents = new List<XDocument>();
var strings = new List<string>();
var attributes = new List<XAttribute>();
string text = null;
//分析参数类型,区分string和XDocument
foreach (var arg in arguments)
{
XDocument xDocArgument;
string stringArgument;
if (CastAs(arg, out xDocArgument))
documents.Add(xDocArgument);
else if (CastAs(arg, out stringArgument))
strings.Add(stringArgument);
}
//如果strings总数为奇数,定义最末一个字符串为InnerText。
if (strings.Count % 2 == 1)
{
text = strings.Last();
strings.RemoveAt(strings.Count - 1);
}
//将strings成对封装为XAttribute。
for (var i = 0; i < strings.Count; i=i+2)
{
attributes.Add(new XAttribute(strings[i], strings[i+1]));
}
//返回动态类型。
return new { Documents = documents, Attributes = attributes, Text = text };
}
/// <summary>
/// 类型判断
/// </summary>
private static bool CastAs<T>(object value, out T castedValue)
{
if (value is T)
{
castedValue = (T)value;
return true;
}
castedValue = default(T);
return false;
}
}
运行XmlBuilderTest,得到的结果如下:
XML:
<Contacts>
<Contact type="team-member" id="1">
<Name>Joe Smith</Name>
<Phone type="work">(123) 456-7890</Phone>
<爱好>看电影</爱好>
<Address type="home">
<Street>123 Main St.</Street>
<City>SpringField</City>
</Address>
</Contact>
</Contacts>
-- 声明 --
1、没太动脑筋,主要的实现思路(包括部分代码),来源于In a Perfect World。(带梯子可访问)原作者给的代码有问题,我在上面做了修改。
2、运行dynamic,需要在.Net 4.0环境下,引用程序集Microsoft.CSharp.dll。
-- 下载 --
所有代码在这儿下载。XmlBuilderTest.zip