日期:2012-07-19  浏览次数:20420 次

     我认为,我认为我们使用的IL的方式对此感兴趣:我们给你一个选择—如果你愿意—你可以控制把IL编译或翻译为本地代码的时机。实际上,使用受管制的 C++,你可以直接从源程序生成本地代码。受管制的 C++还可以生成IL,就象C#和VB那样。当你安装你的代码时,我们给你一个编译选项,可把IL编译成本地代码。因此,当你运行它们时,就不会有即时编译负担。我们还给你提供了一个动态运行和编译代码的选项—即时编译。有了IL,就给你带来了很多便利,比如它提供了这些能力:移植到不同的CPU结构并引入真正的类型安全并在此之上创建安全的系统。

我认为我们IL的设计和Java字节码关键的不同在于,我们做出了超前的决定—不用解释器。我们的代码永远本地运行。因此,即使产生IL,你也不会运行解释器。我们还有不同风格的JIT。对于精简框架,我们有EconnoJIT,就象我们称呼它的一样,它是一个非常简单的JIT[编注:精简.NET是.NET框架的一个子集,是为移植到其它设备和平台设计的]。对于桌面版,我们有完全功能的JIT。我们甚至有可和我们的C++编译器共用一个后端的JIT。不过,这都会比较耗时,因此你只应该在安装时使用它们。

     一旦你做出偏向于执行本地代码而不是解释码的决定,它就会深深地影响IL设计。它改变了应该包括那些指令,应该包括哪些类型信息,以及它应该如何传输。如果你仔细看看两个IL[译注:即.NET IL和Java字节码],你就会发现它们非常不同。从某种意义上讲,我们的IL是类型中立的。指令里没有指定参数类型的信息。进一步说,它是靠已经压栈的东西推断出来的。这种方式使IL更为精简。无论如何,一个JIT编译器需要知道哪些信息,因此没有理由在指令里携带它们。所以,最终我们做出了不同的设计决定,而这使得容易把IL翻译成本地代码。

Osborn:

     解释方式和你描述的方式有何不同?

Hejlsberg:

     解释器的核心是一个循环—从p-code流取得一些字节,然后进入一个大大的switch[译注:类似于程序语言里的switch...case]声明:“哦,这是ADD指令,因此它到这儿来,但是这不是…”等等。

     解释器模拟CPU。我们反其道而行之,我们只走一条道,我们一直都走一条道,我们把指令翻译为机器码。现在,在EconoJIT的情况下,机器码实际上非常简单,它只创建一个调用和压栈指令的列表,并且调用运行时帮助器,然后运行时帮助器替换这个列表。当然,这个代码比解释器代码执行得快。

Osborn:

     让我用一句话来总结一下:你们完全编译代码。因此当你编译完时,字节已经完全准备好运行了,尽管从IL翻译成机器码的时机可能不同。

Hejlsberg:

     是的。但是,如果它是在一个内存受限的小设备的环境里,有可能当运行完就被扔掉了。

Osborn:

     让我们进入语法细节。我在想,C#是否包括对正则表达式的内建支持。我没有在语言参考里看到它,或许它可能在别的什么地方吧。

Hejlsberg:

     首先,在基类库里有一个正则表达式类。我们并没有在语言里加入对正则表达式的任何直接支持,但是,实际上我们有些非常类似的特性。并不值得对它们做重大的处理。但是,比如当你需要指定一个时候—我们给你这个能力去写一个逐字字符串而不需要你每次写两个后斜杠。当你写正则表达式时并且当你的正则表达式里的引号还套引号时,它实际上有极大的帮助。虽然这个帮助不足挂齿,但显然其核心在.NET框架里,而这个框架可以被任何编程语言共享。

Osborn:

     C#和Java名字空间看起来不同。它们是否概念相同而实现上不同?

Hejlsberg:

     概念上是的,但是在实现上非常不同。在Java里,包名也是物理的东西,它指定了你的源代码文件的目录结构。在C#中,物理包和逻辑名称完全独立,无论你如何称呼你的名字空间,它都和你的实际代码的物理包不相关。这就给你更多的弹性—把物理上分布的单元包装在一起,并且不强迫你建很多的目录。在语言自身,有很多很明显的区别。在Java里,包也是你的物理结构,因此,Java源文件必须在正确的路径里,并且只能包含一个公开类型或者一个公开的类。因为C#没有那种物理和逻辑上的绑定,所以你可以任意命名你的源文件。每一个源文件都可以被多个名字空间使用并且可以带有多个公开类。进一步讲,你可以把所有的源码写在一个大文件里,或你可以把它们分散到交叉的小文件中去。概念上讲,C#编译时发生了什么—你给编译器提供了所有构成你的工程的源文件,然后它只管前进并指出该干什么。

Osborn:

     我有一个关于泛型编程的问题:你认为它是个重要的概念吗?它应该成为面向对象语言的一部分吗?如果是的话,你们把泛型编程加为C#的一部分的计划如何?

Goodhew:

     好的。在第一个版本里包括泛型编程的愿望受到了限制。并不象每一个人以为的那样,微软并没有无限制的资源。在这第一个版本里,我们不得不做一些困难的决定。

Osborn:

     有多少人参与开发C#?

Hejlsberg:

     语言设计组由4个人构成,编译器组由另外五个开发人员构成。

Petrusha:

     框架呢?

Hejlsberg:

     那就多了,整个公司都被卷入了。

Goodhew:

     就整个Visual Studio和.NET平台组而言,我们的部门大概有千人左右。包括程序管理人员、开发人员、测试人员,包括所有创建例程、框架、运行时、ASP编程模型的人员以及其它所有的人比如我,管理层的。

Hejlsberg:

     就你刚才所说的泛型方面,我明确地认为它是个非常有用的概念,并且你当然可以列举出发生在学术界和业界所有的泛型研究。模板是该问题的一个解决方法。在我们内部讨论中,我们决定要在新平台里做这个事情。但我们真正喜欢做的是让底层的运行时理解泛型。这和如何创建泛型原型是不同的。用Java的“擦除”概念系统里没有真正的泛型知识。如果公共语言运行时理解泛型的概念,多种语言就可以共享这个功能。你可以在一个地方用C#写一个泛型类,别的人用别的语言也可以使用。

     使泛型成为运行时的一部分还可以使你更有效率的做某些事情。泛型初始化的最理想的时间是在运行时。如果用C++,模板的初始化发生在编译时,你有两个选择:听任你的代码膨胀或试图在链接时去除掉一些膨胀代码。但是,如果你有多个应用,你可能会忘记这一点,你将只能得到膨胀的代码。

     如果把泛型的知识纳入公共语言运行时,则运行时可以理解—当一个应用或组件请求一个“Foo”列表时,它首先会问:“我已经有了一个初始化的“Foo”列表了吗?”如果是,就用那一个。实际上,如果Foo是一个引用类型,并且我们设计正确的话,我们可以让所有引用类型共享初始化。对于值类型,比如整型和浮点型,我们可以为每一个值类型创建一个初始化,但这应该在应用请求时才做。为了把泛型加入运行时,我们已经做了大量的设计工作和必要的基础性工作。

     你先前提到的关于IL的东西是有意思的,因为加入泛型的决定影响了IL的设计。如果IL指令嵌入类型信息,如果,例如,一个“加”指令不再是个“加”了,而是一个整数“加”或是浮点数“加”或是一个双精度数“加”,你就把类型信息硬加入到了指令流里,并且在这一点上来说IL不是泛型的。我们的IL格式实际上是真正的类型