[2017-08-25]100行CSharp代码利用dynamic写个DSL(特定领域语言)
最近看《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节点添加属性了
前面我们override
了TryGetMember
和TryBinaryOperation
,我们可以看到他们分别对应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(特定领域语言)的更多相关文章
- 转: 通过不到100行Go代码打造你自己的容器
备注:这个文章讲容器,讲的比较的浅显易懂.推荐,前期入行者看. 转: http://www.infoq.com/cn/articles/build-a-container-golang?utm_sou ...
- 100行Python代码实现一款高精度免费OCR工具
近期Github开源了一款基于Python开发.名为 Textshot 的截图工具,刚开源不到半个月已经500+Star. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语 ...
- 哪吒票房超复联4,100行python代码抓取豆瓣短评,看看网友怎么说
<哪吒之魔童降世>这部国产动画巅峰之作,上映快一个月时间,票房口碑双丰收. 迄今已有超一亿人次观看,票房达到42.39亿元,超过复联4,跻身中国票房纪录第三名,仅次于<战狼2> ...
- apue编程之参考du代码利用递归写的一个简单的du命令的源代码
#include <stdio.h> #include <stdlib.h> #include <glob.h> #include <string.h> ...
- 100 行代码实现的 JavaScript MVC 样式框架
介绍 使用过 JavaScript框架(如 AngularJS, Backbone 或者Ember)的人都很熟悉在UI(用户界面,前端)中mvc的工作机理.这些框架实现了MVC,使得在一个单页面中实现 ...
- 用JavaCV改写“100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ”
FFMPEG的文档少,JavaCV的文档就更少了.从网上找到这篇100行代码实现最简单的基于FFMPEG+SDL的视频播放器.地址是http://blog.csdn.net/leixiaohua102 ...
- 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】
转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] ...
- 【编程教室】PONG - 100行代码写一个弹球游戏
大家好,欢迎来到 Crossin的编程教室 ! 今天跟大家讲一讲:如何做游戏 游戏的主题是弹球游戏<PONG>,它是史上第一款街机游戏.因此选它作为我这个游戏开发系列的第一期主题. 游戏引 ...
- 【转】100行代码实现最简单的基于FFMPEG+SDL的视频播放器
FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我把自己做项目过程中实现的一个非常简单的视频播放器 ...
随机推荐
- c# 多线程 创建对象实例
本次的标题是我在写单例模式的博客时遇到的问题,所以今天专门写了的demo让自己记住怎么简单的使用多线程. 一直纠结的是怎么在for循环中多次实例化对象,好复现单例模式在没有加锁的情况下出现多个实例对象 ...
- 【ztree】ztree例子
<script language="javascript" type="text/javascript" src="js/jquery.js&q ...
- linux(centos)下安装PHP的PDO扩展
PHP 数据对象PDO扩展为PHP访问数据库定义了一个轻量级的一致接口.PDO 提供了一个数据访问抽象层,这意味着,不管使用哪种数据库,都可以用相同的函数(方法)来查询和获取数据.最近在我们的建站和O ...
- EF 中 Code First 的数据迁移以及创建视图
写在前面: EF 中 Code First 的数据迁移网上有很多资料,我这份并没什么特别.Code First 创建视图网上也有很多资料,但好像很麻烦,而且亲测好像是无效的方法(可能是我太笨,没搞成功 ...
- OOP in Javascript
写了几篇Vue入门的内容了,今天写点其它的放松一下,简单讲讲javascript中的面相对象. 在面向对象的语言中,都有类的概念,当然es6中开始javascript中也有类的概念了,这里以es5为基 ...
- 最小截断[AHOI2009]
[题目描述] 宇宙旅行总是出现一些意想不到的问题,这次小可可所驾驶的宇宙飞船所停的空间站发生了故障,这个宇宙空间站非常大,它由N个子站组成,子站之间有M条单向通道,假设其中第i(1<=i< ...
- Spring Boot整合Dubbo使用及开发笔记
一.概述: Spring Dubbo是我开发的一个基于spring-boot和dubbo,目的是使用Spring boot的风格来使用dubbo.(即可以了解Spring boot的启动过程又可以学习 ...
- vue-router的使用
关于vue-router的基本使用方法 首先,需要下载vue-router npm install vue-router --save vue-router在html或组件中的展现 ``` &l ...
- linux下安装log4cplus
wget http://sourceforge.net/projects/log4cplus/files/log4cplus-stable/1.1.2/log4cplus-x.x.x.tar.gz t ...
- Unity 坐标 转换 详解 World世界坐标 Screen屏幕坐标 View视口坐标 GUI坐标 NGUI坐标 localPosition相对父级坐标
在制作游戏中我们经常会遇到这样一个需求: 在人物模型的上面显示 名字.称号 一类的文字或者图片 如下图 人物模型属于是Camera1 UI Title信息属于NGUI Camera2 如下图 这时 ...