艾伟_转载:二十行C#代码打造Ruby Markup Builder

  从.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类只能算是冰山一角而已。

时间: 2024-10-12 06:13:15

艾伟_转载:二十行C#代码打造Ruby Markup Builder的相关文章

二十行C#代码打造Ruby Markup Builder

从.NET诞生之日起就有了XML类库,但是从使用上来说非常不方便.例如我们需要构造一个XML文档时,使用DOM API就要这样搞: var xmlDoc = new XmlDocument();var rootEle = xmlDoc.CreateElement("persons");xmlDoc.AppendChild(rootEle);var person1 = xmlDoc.CreateElement("person");person1.InnerText =

艾伟_转载:.NET设计模式:建造者模式(Builder Pattern)

概述 在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成:由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法确相对稳定.如何应对这种变化?如何提供一种"封装机制"来隔离出"复杂对象的各个部分"的变化,从而保持系统中的 "稳定构建算法"不随着需求改变而改变?这就是要说的建造者模式. 本文通过现实生活中的买KFC的例子,用图解的方式来诠释建造者模式.

艾伟_转载:.NET Discovery 系列之二--string从入门到精通(勘误版下)

本系列文章导航 .NET Discovery 系列之一--string从入门到精通(上) .NET Discovery 系列之二--string从入门到精通(勘误版下) .NET Discovery 系列之三--深入理解.NET垃圾收集机制(上) .NET Discovery 系列之四--深入理解.NET垃圾收集机制(下) .Net Discovery 系列之五--Me JIT(上) .NET Discovery 系列之六--Me JIT(下) .NET Discovery 系列之七--深入理解

艾伟_转载:基于.NET平台的Windows编程实战(二)—— 需求分析与数据库设计

本系列文章导航 基于.NET平台的Windows编程实战(一)--前言 基于.NET平台的Windows编程实战(二)-- 需求分析与数据库设计 基于.NET平台的Windows编程实战(四)-- 数据库操作类的编写 基于.NET平台的Windows编程实战(五)-- 问卷管理功能的实现 基于.NET平台的Windows编程实战(六)-- 题目管理功能的实现 大家都知道一个系统的成败与否关键在于其所做的需求分析是否到位,数据库的设计是否合理.因为本系列文章的目的是在于提高大家对.NET Wind

艾伟_转载:ASP.NET Forms验证的安全性问题研究——为什么加密代码需要配置为服务

申明:这个帖子不是要你去干啥坏事,就是提醒一下你可能会遇到的安全性问题. ASP.Net提供了内置的登录验证,最为常用的就是Forms验证.讲解如何配置的文章非常多,这里就不再讲如何配置使用这个验证的方式了.下面讲讲其在安全性上存在的一些被忽视的问题.其实它本身没有问题,而使用的方式上会附带出来一些问题. 本文将分三部分讲实际应用中将会遇到的安全性问题,并且加以研究,并尝试提出解决方案. 一.简单的Forms被破解危机二.垂直划分站点的Forms被破解危机三.危机将带来什么后果    一.简单的

艾伟_转载:WinForm二三事(一)

在进入正文之前,想请大家先欣赏下面两段代码: 1: //这是一个控制台程序,请先添加System.Windows.Form.dll的引用 2: using System.Windows.Form; 3:  4: public class ConsoleApplicationShowDialog 5: { 6: static void Main() 7: { 8: Form frm = new Form(); 9: frm.ShowDialog(); 10: } 11: } 1: //这是一个控制

艾伟_转载:趣味问题:你能用Reflection.Emit生成这段代码吗?

众所周知,Reflection.Emit是非常强大的工具,可以在运行时动态生成各种程序集.类型和方法的IL代码,几乎无所不能.原先我也是这样认为的,但是看了某个人的博客之后我发现想要用Reflection.Emit做一些特殊的事情还是很需要技巧性的.假设你还没有看过那个人的博客(暂时先不公开--)可以尝试一下这个问题.下面的代码可以用vbc.exe正确编译(当然等价C#程序也可以经试验C#编译器无法处理该逻辑,各位参照VB的行为吧)... Class AImplements B.IEnd Cla

艾伟_转载:LINQ to SQL、NHibernate比较(二)-- LINQ to SQL实例

    用ADO.NET操作数据库大家一定再熟悉不过了,select.insert.update等等SQL语句大家也都必然滚瓜烂熟.我将自己在学习LINQ to SQL过程中的动手经历记录下来,作为今后学习的参考,也希望对刚刚接触的人有一点帮助.     我在本文涉及到一个很简单的系统,利用DataGridView实现数据库数据的批量增.删.改,不是什么强大的功能.     如果有人感兴趣,可以在看完我的这篇文章之后用ADO.NET实现同样的功能,看看到底会比使用LINQ to SQL多多少时间

艾伟_转载:编写自文档化的代码

文所以载道也.  -- 宋·周敦颐<通书·文辞> 对于我们程序员来说,我们的工作也是写作--几乎每天都要写代码:而且还要载"道",不仅仅要满足客户的需求,还要让代码具有高度的可读性,这样其他的程序员可以更容易地对代码进行修改和扩展. 按这样的要求,我们需要为代码编写足够的文档,也就是将代码"文档化".常见的做法有两种,外部文档和注释. 外部文档 外部文档指的是在代码文件之外编写的附加文档,比如在Word文档中采用大量的篇幅(如UML图.表格)来设计或记录