最近看《CLR via C#(第4版)》

读到第五章末尾dynamic基元类型时,看了下作者的一个利用dynamic动态调用string类型的Contains方法(静态方法)的实现,突然发现这个不就是Ruby的method missing么!虽然当时已经夜深,仍忍不住起来试试,写了个利用Dynamic构建Xml的小Demo,非常有趣。于是有了本文。

所以,我当时想处理什么问题呢?

Ruby的method missing机制,会ruby,用过ror框架(Ruby on Rails)的肯定见识过。

RoR框架中的数据层,可以仅从方法名称中就可以推导出所需的sql查询条件(这个方法甚至没有定义过!)。在没有任何方法实现的情况下,由ruby提供机制以在运行时处理当我找不到那个方法时,我应该做点什么?,即method missing,在其中仅通过解析方法名,就可以构造sql语句并返回正确的查询结果。

忘了是几年前,当时应该ruby才1.9版左右,看到这个特性,感觉特别惊艳。

而,CSharp在拥有dynamic后,也可以做到类似的事情,处理sql可能略繁琐,这里以构建Xml为例。

我想要的效果是,让类似以下的代码(控制台项目)能运行,并且返回一个Xml结构:

var xmlWrapper =
(某个dynamic对象)
.A
.B
.C
.D
.E;//ABCDE可以是任何字符串,当然没有任何预先的方法或者属性定义

期望的结果是:

<root>
<A>
<B>
<C>
<D>
<E />
</D>
</C>
</B>
</A>
</root>

这个好处是什么呢?构建Xml的时候,将多余字符减少到最少!恩,程序员都是懒人。

Step1 首先我们需要一个dynamic

上面的变量名xmlWrapper揭示了,我们需要一个Wrapper,这个Wrapper提供了dynamic的运行时绑定机制。

而且,我们观察一下期望的代码书写方式(某个dynamic对象).A.B.C而非.A().B().C(),所以我们要针对动态调用属性进行处理。

给Wrapper起个名字叫XmlDynamicConstructor,于是我们先有最初的版本:

public sealed class XmlDynamicConstructor : DynamicObject
{
public XElement Element { get; } public XmlDynamicConstructor(XElement element)
{
//初始化时构建一个XElement对象,如上面期望结果中的root节点
Element = element;
} public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name;//获取调用时的名称,如上面的A
var child = new XElement(name);
child.Add(Element);
//为了能继续“链式”调用,必须将返回结果设为一个Wrapper,
//这里以节点A作为下一步处理的“根节点”
result = new XmlDynamicConstructor(child);
return true;
}
}

原理很简单,dynamic调用时,我们知道A这个属性或是A()方法并未定义过,所以在Wrapper中进行处理,将A构造为Xml的一个节点,然后继续返回包含A的Wrapper做下一步处理,又能在A节点中以同样的方式加入B节点,如此,一直继续下去。。。

最后,我们尝试输出xmlWrapper的Element对象:

<E>
<D>
<C>
<B>
<A>
<root />
</A>
</B>
</C>
</D>
</E>

额,为啥反了。。。

原来是疏忽了,应该将child.Add(Element);修正为Element.Add(child),再运行:

<E />

勒个去,剩一个节点了?好吧,xmlWrapper这个变量,在每次调用时都被替换为子节点的引用了。

由于调用的执行是按栈的方式进行的,貌似这个问题不好处理。我们暂且保留下root那个XElement对象的引用,输出root节点看看:

修改Main()如下:

var root = new XElement("root");
var xmlWrapper =
((dynamic)new XmlDynamicConstructor(root))
.A
.B
.C
.D
.E; Console.WriteLine(root);
Console.ReadLine();

输出:

<root>
<A>
<B>
<C>
<D>
<E />
</D>
</C>
</B>
</A>
</root>

OK,对了!但是每次都要保留根节点,比较心塞,怎么解决这个问题?Wrapper内再加点东西?

Step2 改良,传递根节点引用,重写xmlWrapper的ToString

首先,从上面的结果来看,整个Xml的构建是没问题的,就是输出方式有点难看,我们分析分析。

从调用方式上看,xmlWrapper虽然事实上每次调用后都会被替换为子节点的Wrapper,但是我更期望能直接Console.Write输出从root节点开始的整个结构。所以我们必须能在任何一个节点上都能找到根节点。

改进下XmlDynamicConstructor,如下:

public sealed class XmlDynamicConstructor : DynamicObject
{
public XElement Element { get; }
//调用任一节点Wapper的RootElement时,实际返回的是root的Element
public XElement RootElement { get { return _root.Element; } } private XmlDynamicConstructor _root;//保存root节点的Wrapper public XmlDynamicConstructor(XElement element)
{
//初始化时构建一个XElement对象,如上面期望结果中的root节点
Element = element;
_root = this;
} public XmlDynamicConstructor(XElement element, XmlDynamicConstructor root)
{
Element = element;
_root = root;
} public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name;//获取调用时的名称,如上面的A
var child = new XElement(name);
Element.Add(child);
//为了能继续“链式”调用,必须将返回结果设为一个Wrapper,
//这里以节点A作为下一步处理的“根节点”
result = new XmlDynamicConstructor(child, _root);//传递 _root
return true;
} public override string ToString()
{
return RootElement.ToString();//可直接用Console.Write输出xmlWrapper对象
}
}

改下调用代码,如下:

var xmlWrapper =
((dynamic)new XmlDynamicConstructor(new XElement("root")))
.A
.B
.C
.D
.E; Console.WriteLine(xmlWrapper);

输出,符合预期!调用方式也比前面要人性化很多!

Step3 不能光会增加子节点,兄弟节点呢?

观察上面的调用方式,CSharp中的方法调用、属性调用的点符号已经被使用于增加子节点了,那么我们要换个符号用于增加兄弟节点,就用加号吧。

XmlDynamicConstructor敲个override看看,看来看去只有TryBinaryOperation比较像。

public override bool TryBinaryOperation(
BinaryOperationBinder binder, object arg, out object result)
{
if (!(arg is XmlDynamicConstructor))
{
throw new ArgumentException(
"operatiing object should be type of XmlDynamicConstructor!");
} dynamic brother = arg; //AddChecked是指执行溢出检测的加法运算
if (binder.Operation == ExpressionType.Add
|| binder.Operation == ExpressionType.AddChecked)
{
Element.AddAfterSelf(brother.RootElement);
}
else
{
throw new NotImplementedException();
} result = this;
return true;
}

依然很简单的代码,试试看:

static void Main(string[] args)
{
var xmlWrapper =
((dynamic)
new XmlDynamicConstructor(new XElement("root")))
.A
.B
.C
.D
.E
+
((dynamic)
new XmlDynamicConstructor(
new XElement("F")))
.G
.H
.I; Console.WriteLine(xmlWrapper);
Console.ReadLine();
}

输出:

<root>
<A>
<B>
<C>
<D>
<E />
<F>
<G>
<H>
<I />
</H>
</G>
</F>
</D>
</C>
</B>
</A>
</root>

我们可以看到节点E和节点F成兄弟节点了,并且F还带着子节点。

Step4 继续改良,要考虑下为Xml节点添加属性了

前面我们overrideTryGetMemberTryBinaryOperation,我们可以看到他们分别对应dynamic调用属性和运算符时的处理。那么方法呢?

再来个override,发现TryInvokeMember比较像(不再单独演示结果):

public override bool TryInvokeMember(
InvokeMemberBinder binder, object[] args, out object result)
{
var name = GetName(binder.Name);
var child = new XElement(name); if (args.Length > 0)
{
var arg = args[0];
//一个辅助方法设置XElemnt的xml属性
XDC.SetAttributes(child, arg);
} Element.Add(child);
result = new XmlDynamicConstructor(child, _root);
return true;
}

还有之前((dynamic) new XmlDynamicConstructor(new XElement("root")))这行比较丑,换个短点的,比如XDC.New("root")

篇幅已经挺长,不再单独演示了。

总结

通过继承DynamicObject,我们可以自定义如何处理dynamic类型的运行时动态绑定。当然这肯定有一定的性能开销,但是对于某些任务,这个机制是极其方便的,甚至可以说是非常潇洒的,正因如此,Ruby的method missing也被誉为Ruby程序员的梦中情人

希望本文能给大家一定收获。

完整代码请见TinyDSL.Xml

完整版XmlDynamicConstructor大概就80多行代码,XDC也才20几行,文章标题上说的100行写个DSL基本达成。

[2017-08-25]100行CSharp代码利用dynamic写个DSL(特定领域语言)的更多相关文章

  1. 转: 通过不到100行Go代码打造你自己的容器

    备注:这个文章讲容器,讲的比较的浅显易懂.推荐,前期入行者看. 转: http://www.infoq.com/cn/articles/build-a-container-golang?utm_sou ...

  2. 100行Python代码实现一款高精度免费OCR工具

    近期Github开源了一款基于Python开发.名为 Textshot 的截图工具,刚开源不到半个月已经500+Star. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语 ...

  3. 哪吒票房超复联4,100行python代码抓取豆瓣短评,看看网友怎么说

    <哪吒之魔童降世>这部国产动画巅峰之作,上映快一个月时间,票房口碑双丰收. 迄今已有超一亿人次观看,票房达到42.39亿元,超过复联4,跻身中国票房纪录第三名,仅次于<战狼2> ...

  4. apue编程之参考du代码利用递归写的一个简单的du命令的源代码

    #include <stdio.h> #include <stdlib.h> #include <glob.h> #include <string.h> ...

  5. 100 行代码实现的 JavaScript MVC 样式框架

    介绍 使用过 JavaScript框架(如 AngularJS, Backbone 或者Ember)的人都很熟悉在UI(用户界面,前端)中mvc的工作机理.这些框架实现了MVC,使得在一个单页面中实现 ...

  6. 用JavaCV改写“100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ”

    FFMPEG的文档少,JavaCV的文档就更少了.从网上找到这篇100行代码实现最简单的基于FFMPEG+SDL的视频播放器.地址是http://blog.csdn.net/leixiaohua102 ...

  7. 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】

    转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] ...

  8. 【编程教室】PONG - 100行代码写一个弹球游戏

    大家好,欢迎来到 Crossin的编程教室 ! 今天跟大家讲一讲:如何做游戏 游戏的主题是弹球游戏<PONG>,它是史上第一款街机游戏.因此选它作为我这个游戏开发系列的第一期主题. 游戏引 ...

  9. 【转】100行代码实现最简单的基于FFMPEG+SDL的视频播放器

    FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我把自己做项目过程中实现的一个非常简单的视频播放器 ...

随机推荐

  1. DL4NLP——词表示模型(一)表示学习;syntagmatic与paradigmatic两类模型;基于矩阵的LSA和GloVe

    本文简述了以下内容: 什么是词表示,什么是表示学习,什么是分布式表示 one-hot representation与distributed representation(分布式表示) 基于distri ...

  2. (转)Servlet初始化、运行、销毁全部过程

    Servlet初始化.运行.销毁全部过程 (2012-07-05 10:41:26) 标签: 杂谈 分类: java基础面试知识 Servlet的生命周期是由servlet的容器来控制的.分为3个阶段 ...

  3. XHTML 相对路径与绝对路径

    文件路径 文件路径就是文件在电脑(服务器)中的位置,表示文件路径的方式有两种:相对路径和绝对路径. 路径标识: 标识符号 说明 / 路径标识 . 当前目录 .. 上一层目录 "." ...

  4. CubieBoard开发板不用ttl线也不用hdmi线的安装方法

    本文重点在于CubieBoard开发板系统的初始化安装,并且不用ttl和hdmi线,开机就可以远程ssh进系统.本文适合没有配线的同学参考操作.事实上,无论有没有ttl线,按照本文的方法安装效率都是一 ...

  5. Codeforces Round #427 (Div. 2)

    B. The number on the board 题意: 有一个数字,它的每个数位上的数字的和不小于等于k.现在他改变了若干位,变成了一个新的数n,问现在的数和原来的数最多有多少位不同. 思路: ...

  6. iOS多款源码分享

    iOS精选源码 列表联动,Linkage 电商商品详情 AxcUIKit-控件整合框架,快速简单的使用高级控件 GKNavigationBarViewController-导航栏联动 仿京东的加入购物 ...

  7. [补档]暑假集训D2总结

    %dalao https://hzoi-mafia.github.io/2017/07/26/17/ (纪念我已死去的github) 大佬AntiLeaf来讲概率&期望,然后--成功变为 不可 ...

  8. POJ2236 Wireless Network 并查集简单应用

    Description An earthquake takes place in Southeast Asia. The ACM (Asia Cooperated Medical team) have ...

  9. UIButton防止被重复点击

    一.避免屏幕内多个 UIButton 被重复点击 1.在 AppDelegate 中添加[[UIButton appearance] setExclusiveTouch:YES]; 2.button. ...

  10. 前端到后台ThinkPHP开发整站(7)

    今晚我继续这个项目的前台开发,把前台的做出来了,现在项目进行一个收尾工作了,还有栏目页和一个文章页的开发,做完这两个算是完成了.说到这里感觉有点松懈了,把剩下两个功能页面做完在吹吧,先看看今天弄的代码 ...