===================================================php
注:
好久没有发文了,贴一篇新文吧。从Word直接贴过来的,没仔细排版,诸位海涵。
有关DLR和C# 4动态特性的详细介绍,请参看本人拙著《.NET 4.0面向对象编程揭秘(应用篇)》,目前该书正处于编辑出版流程中,估计12月上市。
与此书相关的技术资源,将陆续发布于博客园与CSDN的本人博客。html
=====================================================================================python
金旭亮jquery
近几年来,在TIOBE公司每月发布的编程语言排行榜[1]中,C#老是能挤进前10名,而在近10年的编程语言排行榜中,C#整体上呈现上升的趋势。C#能取得这样的成绩,有不少因素在起做用,其中,它在语言特性上的锐意进取让人印象深入(图 1)。程序员
图 1 C#各版本的创新点编程
2010年发布的C# 4,最大的创新点是拥有了动态编程语言的特性。缓存
动态编程语言并不是什么新鲜事物,早在面向对象编程语言成为主流以前,人们就已经使用动态编程语言来开发了。即便在Java、C#、C++等面向对象编程语言繁荣兴旺、大行于世的年代,动态编程语言也在“悄悄”地攻城掠地,占据了至关的开发领域,好比 JavaScript业已成为Web客户端事实上的主流语言。架构
最近这几年,动态编程语言变得日益流行,好比Python、Ruby都很是活跃,使用者众多。app
这里有一个问题,为何咱们须要在开发中应用动态编程语言?与C#和Java这类已经很是成熟且功能强大的静态类型编程语言相比,动态编程语言有何优点?框架
简单地说,使用动态编程语言开发拥有如下的特性:
(1)支持REPL(Read-evaluate-print Loop:“读入à执行à输出”循环迭代)的开发模式,整个过程简洁明了,直指问题的核心。
举个简单的例子,图 2所示为使用IronPython[2]编程计算“1+2+……+100”的屏幕截图,咱们能够快速地输入一段完成累加求和的代码,而后立刻就能够看到结果:
图 2 使用IronPython编程
若是使用C#开发就麻烦多了,您得先用Visual Studio建立一个项目,而后向其中添加一个类,在类中写一个方法完成求和的功能,再编写调用这一方法的代码,编译、排错,最后才能获得所需的结果……
很明显,对于那些短小的工做任务而言,动态编程语言所具有的这种REPL开发模式具备很大的吸引力。
(2)扩展方便。用户能够随时对代码进行调整,须要什么功能直接往动态对象上“加”就是了,不要时又能够移除它们。并且这种修改能够立刻生效,并不须要像C#那样必须先修改类型的定义和声明,编译以后新方法才可用。
换句话说:使用动态语言编程,不须要“重量级”的OOAD,整个开发过程迭代迅速而从不拖泥带水。
(3)动态编程语言的类型解析是在运行时完成的,能够省去许多没必要要的类型转换代码,所以,与静态编程语相比,动态编程语言写的代码每每更紧凑,量更少。
动态编程语言主要的弱点有两个:
(1)代码中的许多错误要等到运行时才能发现,并且须要特定的运行环境支持,对其进行测试不太方便,也不支持许多用于提高代码质量的各类软件工程工具,所以不太适合于开发规模较大的、包容复杂处理逻辑的应用系统。
(2)与静态编程语言相比,动态编程语言编写的程序性能较低。不过随着计算机软硬件技术的不断进步,好比多核CPU的普遍应用,动态编程语言引擎和运行环境不断地优化,动态编程语言编写的程序性能在不断地提高,在特定的应用场景下,甚至能够逼近静态语言编写的程序。
为了让C#、Visual Basic等.NET编程语言能具有动态编程语言的特性,.NET 4.0引入了一个“DLR(Dynamic Language Runtime:动态语言运行时)”(图 3)。
图 3 DLR:动态语言运行时
DLR运行于CLR之上,提供了一个动态语言的运行环境,从而容许Python、Ruby等动态语言编写的程序在.NET平台上运行,同时,现有的.NET静态类型编程语言,好比C#和Visual Basic,也能够利用DLR而拥有一些动态编程语言的特性。
C# 4新增了一个dynamic关键字,能够用它来编写“动态”的代码。
例如,如下代码建立了一个ExpandoObject对象(注意必须定义为dynamic):
dynamic dynamicObj = new ExpandoObject();
这一对象的奇特之处在于,咱们能够随时给它增长新成员:
dynamicObj.Value = 100; //添加字段
dynamicObj.Increment = new Action(() => dynamicObj.Value++); //添加方法
这些动态添加的成员与普通的类成员用法同样:
for (int i = 0; i < 10; i++)
dynamicObj.Increment();//调用方法
Console.WriteLine("dynamicObj.Value={0}",dynamicObj.Value);//访问字段
ExpandoObject对象实现了IDictionary<string, object>接口,可当作是一个字典对象,全部动态添加的成员都是这个字典对象中的元素,这意味咱们不只能够添加新成员,还能够随时移除再也不须要的成员:
//移除Increment方法
(dynamicObj as IDictionary<string, object>).Remove("Increment");
方法移除以后,再尝试访问此方法将引起RuntimeBinderException异常。
要在.NET这个“托管世界”里调用“非托管世界”中的COM组件,咱们必须经过 “互操做程序集(Interop Assembly)”做为桥梁,“互操做程序集”定义了CLR类型与COM类型之间的对应关系。
只要给.NET项目添加对“互操做程序集”的引用,就能够在.NET应用程序中建立这一程序集所包容的各类类型的实例(即COM包装器对象),对这些对象的方法调用(或对其属性的存取)将会被转发给COM组件。
以调用Word为例,在C# 4.0以前您可能常常须要编写这样的代码:
Object wordapp = new Word.Application(); //建立Word对象
Object fileName = “MyDoc.docx” ;//指定Word文档
Object argu = System.Reflection.Missing.Value;
Word.Document doc = wordapp.Documents.Open(ref fileName, ref argu,
ref argu, ref argu, ref argu, ref argu, ref argu, ref argu,
ref argu, ref argu, ref argu, ref argu, ref argu, ref argu,
ref argu, ref argu);
上述对Open()方法的调用语句只能用“恐怖”一词来形容,其缘由是Word组件中的Open()方法定义了太多的参数。
C#4使用dynamic关键字,配合从Visual Basic中学来的“命名参数与可选参数”这两个新语法特性,能够写出更简洁的代码:
dynamic wordapp = new Word.Application();
dynamic doc = wordapp.Documents.Open(FileName: “MyDoc.docx”);
上述代码中省去了用不着的参数,而且能够去掉参数前的ref关键字。
当上述代码运行时,DLR会使用反射技术将dynamic表达式“绑定(bind)”到COM互操做程序集中所包容的Word.Application代理对象。
C#4中所定义的dynamic变量能够引用如下类型的对象:
l 传统的“静态”的CLR对象。
l COM包装器对象。前面已经介绍了这方面的内容。
l 实现了IDynamicMetaObjectProvider接口的“动态对象”,ExpandoObject就是这种类型对象的实例。
l 基于DLR实现的动态语言(好比IronRuby和IronPython)所建立的对象。
从C#程序员角度来看,全部这四种对象都是同样的,均可用一个dynamic变量引用之,而DLR在程序运行时动态地将方法调用和字段存取请求“绑定”到真正的对象上。
dynamic的功能是由DLR所支撑的,是C#编译器与DLR分工合做的成果。
请看如下示例代码:
dynamic d = 100;
d++;
C#编译器在处理上述代码时,它并不去检查变量d是否能够支持自增操做,而是为其建立了一个CallSite<T>对象(<>p__Site1):
private static class <Main>o__SiteContainer0 {
public static CallSite<Func<CallSite, object, object>> <>p__Site1;
}
中文MSDN将CallSite<T>译为“动态(调用)站点”,它是DLR中的核心组件之一。
动态站点对象经过CallSite<T>.Create()方法建立, C#编译器会为其指定一个派生自CallSiteBinder的对象(称为“动态站点绑定对象”)做为其参数。
动态站点绑定对象是与具体语言相关的,好比IronPython和C#都有各自的动态站点绑定对象。
动态站点绑定对象的主要工做是将代码中的动态表达式(本例中为d++)转换为一棵“抽象语法树(AST:Abstract Syntax Tree)”,这棵语法树被称为“DLR Tree”,是在.NET 3.5所引入的LINQ表达式树的基础上扩充而来的,所以,有时又称其为“表达式树(Expression Tree)”
DLR在内部调用此表达式树的Compile()方法生成IL指令,获得一个能够被CLR所执行的委托(在本例中其类型就是Func<CallSite, object, object>)。
动态调用站点对象(本例中为<>p__Site1)有一个Target属性,它负责引用这一辈子成好的委托。
委托生成以后,动态表达式的执行就体现为委托的执行,其实参由C#编译器直接“写死”在IL代码中。
简化的代码示意以下(经过Reflector获得,为便于阅读,修改了变量名):
object d = 100;
object CS$0$0000 = d;
if (<>p__Site1 == null)
<>p__Site1 = CallSite<Func<CallSite, object, object>>.Create(……);
d = <>p__Site1.Target(<>p__Site1, CS$0$0000);
上述类型推断、方法绑定及IL代码生成的工做都是在程序运行时完成的。
动态编程语言易学易用,代码紧凑,开发灵活,但性能则一直是它的“软肋”。为了提高性能,DLR设计了一个三级缓存策略。
动态站点绑定对象会为动态调用表达式转换而成的语法树加上相应的测试条件(称为“test”),构成一个“规则(Rule)”,这个规则能够用于判断某个语法树是否可用于特定的动态调用表达式。
举个例子,请看如下这个动态表达式:
d1 + d2
若是在程序运行时d1和d2都是int类型的整数,则DLR生成的规则为:
if( d1 is int && d2 is int) //测试条件
return (int)d1+(int)d2; //语法树
DLR经过检查规则中的“测试条件”,就能够知道某个动态表达式是否可使用此规则所包容的语法树。
“规则”是DLR缓存的主要对象。
前面介绍过的动态站点对象Target属性所引用的委托是第一级缓存,它实现的处理逻辑是这样的:
//当前处理规则,属于第1级缓存
if( d1 is int && d2 is int) //测试条件
return (int)d1+(int)d2; //知足测试条件,直接返回一个表达式树
//未命中,则在第2级、第3级缓存中查找,若是找到了,用找到的结果更新第1级缓存
return site.Update(site,d1,d2);
若是3级缓存中都没有命中的规则,则此动态站点所关联的调用站点绑定对象会尝试建立一个新的规则。若是建立新规则失败,则由当前编程语言(好比C#)所提供的默认调用站点绑定对象决定如何处理,一般的做法是抛出一个异常。
当前版本的DLR第2级缓存了10条规则,第3级则缓存了100条规则。
因为DLR自身设计了一个“规则”缓存系统,又充分利用了CLR所提供的JIT缓存(由于全部动态调用代码最终都会转换为CLR能够执行的IL指令,而CLR能够缓存这些代码),使得动态代码仅仅在第一次执行时性能较差,后续的连续调用其性能能够逼近静态代码。
因为几乎全部的编程语言均可以使用抽象语法树来表达,所以,在理论上DLR支持无限多种编程语言间的互操做,在当前版本中,能够实现C#/Visual Basic与IronPython和IronRuby的互操做,相信很快会出现其余动态编程语言的DLR实现。
一个有趣的地方是当前基于DLR实现的动态编程语言都以“Iron”开头,好比IronRuby和IronPython。IronPython的设计者、DLR的架构设计师Jim Hugunin曾经在微软PDC 2008大会上解释说主要是为了不起一个“Python.NET”或“Python for .NET”之类“微软味十足”的名字,才有了“IronPython”。他强调:“Iron”系列动态语言将严格遵循动态语言自身的标准和规范,尊重这些动态语言已有的历史和积累,不会引入一些仅限于.NET平台的新语言特性,而且这些语言的.NET实现保持开源。与此同时,Jim Hugunin指出 “Iron”系列语言能很好地与.NET现有类库、编程语言和工具集成,而且能“嵌入”到.NET宿主程序中。
因为各类动态编程语言之间的特性相差极大,实现各语言间的互操做是个难题。为此DLR采起了一个聪明的策略,它不去尝试设计一个“通用的类型系统”(CLR就是这么干的),而是设计了一个“通用的对象通信协议”,规定全部须要互操做的动态对象必须实现IDynamicMetaObjectProvider接口,此接口定义了一个GetMetaObject()方法,接收一个语法树对象做为参数,向外界返回一个“动态元数据(DynamicMetaObject)”对象:
DynamicMetaObject GetMetaObject(Expression parameter);
DynamicMetaObject对象向外界提供了两个重要属性:Restrictions引用一组测试条件,Expression属性则引用一个语法树。这两个属性组合起来就是可供动态站点对象缓存的“规则(Rule)”。
DLR中的“动态站点绑定对象(CallSiteBinder)”获取了DynamicMetaObject对象以后,它调用此对象所提供的各个方法建立“规则”,让“动态站点对象(CallSite<T>)”的Target属性引用它,完成动态绑定的工做。
为了方便地实现静态编程语言与各类动态编程语言间的相互集成,DLR提供了一整套称为“通用寄宿(Common Hosting)”的组件,其中包容ScriptRuntime、ScriptScope等类型。
下面咱们以IronPython为例,介绍如何在C# 4开发的程序中集成动态编程语言代码。
首先须要建立一个ScriptRuntime对象,它是一个最顶层的对象,用于在一个.NET应用程序域中“嵌入”一个特定动态语言的运行环境:
ScriptRuntime pythonRuntime = Python.CreateRuntime();
接着须要建立一个ScriptEngine对象,它是动态语言代码的执行引擎:
ScriptEngine engine = pythonRuntime.GetEngine("py");
ScriptScope对象相似于C#中的命名空间,其中能够经过定义一些变量向动态代码传入数据,好比下述代码将一个C# 建立的ExpandoObject对象传给Python代码:
ScriptScope scope = pythonRuntime.CreateScope();
//C#建立动态对象
dynamic expando = new ExpandoObject();
expando.Name = "JinXuLiang"; //动态添加一个字段
//让IronPython接收C#建立的Expando对象
scope.SetVariable("ExpandoObject", expando);
string pythonCode = "print ExpandoObject.Name";
//IronPython引擎执行Python语句
engine.CreateScriptSourceFromString(pythonCode).Execute(scope);
上述示例代码是直接执行Python代码。在实际开发中,更常见的是直接执行Python文件中的代码,假设有一个Calculator.py文件,其中定义了一个Add函数:
def Add(a,b):
return a+b
则如下C#代码能够直接执行之:
ScriptRuntime pythonRuntime = Python.CreateRuntime();
dynamic pythonFile = pythonRuntime.UseFile("Calculator.py");
Console.WriteLine(pythonFile.Add(100, 200));
上述示例说明在DLR的支持之下,可让静态编程语言使用动态语言所开发的库,反过来,基于DLR实现的动态编程语言也能使用为静态语言所设计的库,好比标准的.NET基类库。
这意味着两点:
(1)咱们如今能够将“静态”和“动态”编程语言组合起来,开发出一些具备高度交互性的应用程序,使用静态编程语言搭建系统框架,使用动态编程语言实现交互性,这是一个很值得注意的应用领域。
(2)未来会出现一些“静态”“动态”编程语言同时适用的库,向实现“无所不在的复用”目标又前进了一步。
Visual Studio 2010为新的.NET编程语言F#提供了专门的项目模板,但没有为IronPython和IronRuby之类动态语言的开发提供支持,相信随着动态语言在.NET平台之上的应用日趋普遍,后继版本的Visual Studio会直接支持动态语言的开发。
从C# 1.0~4.0所走过的路,能够很清晰地看到它的发展轨迹,获得这样的一个结论:
将来的编程语言应该是多范式的,具备高度的可组合性,在一个项目或产品中组合多个编程语言、使用多种编程范式会变得愈来愈广泛。
咱们能够推断C#的后继版本将会在此条道路上越走越远……
[1] http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html
[2] IronPython是动态语言Python基于.NET的一个实现