二十行C#程式碼打造Ruby Markup Builder

iDotNetSpace發表於2009-10-27

從.NET誕生之日起就有了XML類庫,但是從使用上來說非常不方便。例如我們需要構造一個XML文件時,使用DOM API就要這樣搞:

var xmlDoc = new XmlDocument();
var rootEle = xmlDoc.CreateElement("persons");
xmlDoc.AppendChild(rootEle);

var person1 = xmlDoc.CreateElement("person");
person1.InnerText = "Tom";
var person1Age = xmlDoc.CreateAttribute("age");
person1Age.Value = "10";
person1.Attributes.Append(person1Age);
rootEle.AppendChild(person1);

var person2 = xmlDoc.CreateElement("person");
person2.InnerText = "Jerry";
var person2Age = xmlDoc.CreateAttribute("age");
person2Age.Value = "8";
person2.Attributes.Append(person2Age);
rootEle.AppendChild(person2);

別看這麼多行程式碼,但實際上它只構造了這麼簡單的一個XML:

<persons>
  <person age="10">Tomperson>
  <person age="8">Jerryperson>
persons>

我承認,DOM API的確非常嚴謹(如XmlDocument和XmlElement的歸屬關係),非常符合定義,也非常的物件導向,但是這易用性也實在太差了。記得在03還是04年的時候,我為在為專案做一個編輯XML文件的WinForm應用程式,當時也不像現在那麼容易想到“偷懶”的法門,而VS 2003也不像VS 2005/2008那麼好用,因此可謂做的勞心費神。這個情況在.NET 2.0中也沒有得到改變,直到有一天,LINQ to XML隨.NET 3.5橫空出世,於是乎XML的生活一下子變得美好了很多。例如上面的功能只需寥寥數行便可以實現:

var xmlDoc = new XElement("persons",
    new XElement("person",
        "Tom",
        new XAttribute("age", 10)),
    new XElement("person",
        "Jerry",
        new XAttribute("age", 8))); 

雖然LINQ to XML一直是所謂C# 3.0中LINQ特性的一部分,與LINQ to SQL,LINQ to Object及LINQ to……某個別的並列,但我始終認為LINQ to XML實則還是LINQ to Object的一種特殊形式,只是它用於操作XML而已。它的一切都是System.Xml.Linq名稱空間下相關類庫(如XElement)在起作用,不關LINQ什麼事情。XElement等相關型別大大簡化了我們的開發,與DOM API相比,無論是XML的構造還是讀取都容易了許多。不過俗話說得好:“不怕不識貨,就怕貨比貨”,這樣的API與Ruby Markup Builder相比還是有明顯差距。請看:

builder = Builder::XmlMarkup.new
xml = builder.persons { |b|
    b.person("Tom", :age => "10")
    b.person("Jerry", :age => "8")
}

請看上面這段程式碼,它自然沒有使用Ruby語言的標準著色方式。我著色的目的是體現這個構造方式中的“噪音”——也就是與XML內容無關的部分。從中可以發現,Ruby不愧是一種噪音較少的語言,如果您嘗試使用這個方式來觀察C#中LINQ to XML的做法,就會發現兩者之間的確有明顯的差距。當然,如果使用VB.NET的XML Literal可能噪音也很少,但是在我看來,XML Literal在XML構造方面的表現有些羅嗦,例如它需要開發人員同時提供元素的開始標籤和閉合標籤,可能在IDE的幫助下此類程式碼輸入較為簡單,但是程式碼還是略顯冗餘。

但是我們這些可憐的C#程式設計師難道只有在一邊眼饞的份嗎?不見得,我們也可以來“享受”一把:

dynamic b = new XmlMarkupBuilder();
XElement xml =
    b.persons(
        b.person("Tom", age: 10),
        b.person("Jerry", age: 8));

哇,這是什麼,怎麼程式碼那麼簡單。很明顯,從dynamic關鍵字上可以看出,這是C# 4.0中新增的功能。您可能會想“原來.NET 4.0對XML又有增強了”……其實並非如此,這是我們自己擴充套件的功能。不過這應該算是更好的訊息,因為這說明我們已經有能力自行擴充套件,自行設計這樣的API了——這可是“漁”,比“魚”可要值錢多了。而實現這樣的功能也只需要短短二十幾行C#程式碼:

public class XmlMarkupBuilder : DynamicObject
{
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        XElement xml = new XElement(binder.Name);

        var attrCount = binder.CallInfo.ArgumentNames.Count;
        var elementCount = args.Length - attrCount;

        for (int i = 0; i < elementCount; i++)
        {
            xml.Add(args[i]);
        }

        for (var i = 0; i < attrCount; i++)
        {
            var attrName = binder.CallInfo.ArgumentNames[i];
            if (attrName[0] == '@') attrName = attrName.Substring(1);

            xml.Add(new XAttribute(attrName, args[i + elementCount]));
        }

        result = xml;
        return true;
    }
}

DynamicObject是個特殊的物件,簡單地說它的行為可以被“擴充套件”——是如動態語言般真正的擴充套件,而非靜態的多型。當我們使用dynamic修飾變數後,在它之上的方法呼叫會由編譯器和DLR配合出不一樣的行為。例如,我們在呼叫一個方法的時候,DLR會先檢查這個動態物件上是否存在符合這個簽名的方法,存在則最好,否則便會呼叫TryInvokeMember來“執行”一個動態方法,而它的引數便是此次呼叫的全部資訊。這樣的做法被稱為“Method Missing”操作,事實上Ruby Markup Builder也是使用Ruby物件中的這個特性來實現“呼叫什麼方法,便生成什麼元素”的功能。此外,我們還可以這麼用:

var persons = new [] { new Person("Tom", 10), new Person("Jerry", 8) };
XElement xml2 = 
    b.persons(
        from p in persons
        select b.person(p.Name, age: p.Age));

XmlMarkupBuilder對LINQ的直接支援得益於XElement無與倫比的“包容性”(因此我認為LINQ to XML其實只是LINQ to Object + 類庫)。至於age: 10這樣的程式碼,其實是使用了C# 4.0的新特性:命名引數(Named Parameters)——C#還真把什麼都為我們準備好了。

即便是大部分DynamicObject的示例都喜歡拿XML操作開涮(但還是沒有出現我這篇的用法,所以我還是“原創”),但事實上這個功能可發揮的餘地非常之大。例如,陳貓同學提到他想用這個功能來簡化Silverlight中的JSON操作,剛“喜得貴女”的Phil Haack同學在上個月也提到一個設想,它在ASP.NET MVC中使用dynamic關鍵字來修飾View的Model,這樣在訪問Model的屬性時變可附加一些約定好的操作。例如,Model.Content表示讀取Content屬性的內容,而Model._Content則表示在讀取Content之後自動進行HTML編碼。這無疑簡化了我們的開發——當然,強型別的各種優勢就不復存在了。

而這個功能對我的意義在於,我又找到了一種設計API的方式,它可以使類庫變得簡單好用——就好比上面的XmlMarkupBuilder一樣。雖然,這個示例的功能非常簡單,但是這也足以證明C# 4.0中的dynamic特性並不僅僅是“方便Interop操作”或是“簡化反射”這麼簡單,如果我們可以發揮想象能力,加以充分利用同時又不濫用,我們的程式開發生活就會變得越來越美好。

最後……我還是承認了吧,這篇文章其實是標題黨,真正Ruby Markup Builder功能非常強大而複雜,我們的XmlMarkupBuilder類只能算是冰山一角而已。

原文地址:http://www.cnblogs.com/JeffreyZhao/archive/2009/10/27/1590321.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-617530/,如需轉載,請註明出處,否則將追究法律責任。

相關文章