今天读到了《CLR via C#》中动态基元类型的章节,恰好刚刚在候选区看到了一篇《为什么可以说Java语言是准动态语言?》的文章,其文中说Java依赖反射可以称为‘准动态语言’,而C#是静态语言。
我先不说结论,先来看一下什么是动态语言。
引用互动百科的词条:
动态语言,准确地说,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如众所周知的ECMAScript(JavaScript)便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态语言。
比如我们在JavaScript里面可以这样写:
1 var temp = {}; 2 temp.FuncA = function(){ 3 console.log(‘FuncA Invoked‘) 4 } 5 temp.Name = ‘JavaScript‘;
在以上代码中,对象temp是一个匿名对象,不具有类型结构。然后我们为temp动态追加了一个函数 FuncA和一个属性Name 。之所以可以这样写,是因为JavaScript是一种解释型语言,所有的代码只有在运行时才会被解释器编译。而C语言,Java语言以及.Net 3.5及以前版本的C#语言,是运行前已经被编译为机器语言,因此所有的对象在编译时就必须有一个类型定义,编译器没有办法推断动态调度的成员,也就没有办法编译代码了。这一类语言一般被称为静态语言。
很长一段时间以来,这两类语言也都相安无事,自己管着自己的一亩三分地。然而,动态编程对程序员的诱惑实在是太大了,很多工程师就会想,我们能不能让编译器通过某种方式编译动态调度的对象呢?答案是肯定的。在.net CLR还不支持动态调度的时候,就已经有工程师通过CodeDom动态编译技术和反射模拟了动态调度类型。
虽然这种方式性能十分差劲,用起来也十分的繁琐,但也为后来的动态基元类型的实现提供了理论基础。
我们前面说了,动态语言是运行时编译的,所以才能够动态调度。所以我们可以确定以下几点:
1. 动态调度对象需要在运行时才能确定其结构
2. 动态调度对象需要动态编译,不能够预编译
3. 通过动态编译和反射技术,我们可以以一种丑陋的方式模拟动态调度的过程
所以,,如果在JITCompiler中,对需要动态调度的对象进行动态编译不就能够在预编译语言中实现动态调度了吗?
在.net 4.0及之后的版本中,我们可以这样写:
1 public void Main() 2 { 3 dynamic temp = new System.Dynamic.ExpandObject(); 4 temp.FuncA = () =>{ 5 Console.WriteLine("FuncA Involved"); 6 }; 7 temp.Name = "CSharp Dynamic Dispatch"; 8 }
当编译器编译到这段代码的时候,不会将其编译为机器码,而是在程序集中加入payload,并在运行到这里时调用.NET 编译器动态编译,得到当前的动态对象的实例,然后通过IL的内存操作访问其中的成员,使性能比反射高出很多,实现起来也更加优雅了。
说到这里我们就能够得出结论,CLR版本4.0之后的C#已经符合所有动态语言的定义,可以被称为一种动态语言了。甚至如果你喜欢,可以完全把C#当做动态语言来用,虽然可能在某些场景下会有一定的性能损失,但跟反射比起来,还是会优秀很多的。