随着 Visual J++ 的隐退,Microsoft 引入 C# 来填补这一空白
Michael L. Perry
Mallard 软件设计师
2000 年 8 月
Microsoft 对因特网的新视野与当今在线内容的网络相去甚远。Microsoft 预见未来的因特网是一个由不同的人用不同的语言在不同的硬件平台上开发出的互相关联的服务,可以被运行在不同的因特网操作系统之上。
该公司为大胆设想的这个新世界所提供的就是 .Net 平台。实际上,.Net 并不是传统意义上的平台;它并不是一种公共的硬件或软件系统。.Net 实际上是一个协议集合,它允许因特网应用程序利用运行在不同机器上的不同服务。
Microsoft 新战略的结果就是下一代的 Microsoft Visual Studio 将有重新设计的语言,尤其是能支持 .Net 平台。Java 将不再此程序包中,Visual J++ 也不会再有新的版本。不要担心,Microsoft 将推出一个新的语言 C# 来填补这一空白。该公司动用了其最好的资源,包括星级语言专家 Anders Hejlsberg,来开发 C#。
非他莫属:Anders Hejlsberg,语言体系结构设计师
考虑一下 Hejlsberg 的经历,就不会奇怪 Microsoft 将此重任交给他了。毕竟,C# 将不是他对软件开发方式的首次大事改革。
作为 Borland 的首席体系结构设计师,Hejlsberg 秘密地将 Turbo Pascal 变成一种面向对象的、拥有真正可视化的环境和卓越的数据库访问特性的应用程序开发语言。曾经被吹捧为“VB 杀手”的 Delphi 依然是 Borland(现在为 Inprise/Borland)的拳头产品。
Microsoft 用丰厚的薪水、股票认购权和巨额的加盟奖金雇请了 Hejlsberg 之后,Borland 曾提出通过不正当手段吸收新成员的控告。Borland 还曾声称 — 提供了很少的细节来支持指控 — Hejlsberg 正在开发“Delphi for Java”。这场诉讼最终以 Borland 的胜利而收场;但是从那时起,Microsoft 已在让 Hejlsberg 工作了。
Hejlsberg 充实了 Microsoft 的 Java 产品;其中格外引人注意的是,为了跟上其他 Visual Studio 语言的现行版本号,他人为地在一个发行版中将其版本号从 1.1 提升到 6.0。实际上,这个版本号的跳跃并未夸大 6.0 与其前一版本之间的差别。Hejlsberg 添加的功能已使该语言变成一种强大的 Windows 应用程序开发平台。添加的功能包括对 Windows API 的访问;因此,利用这些新功能的程序不再具有可移植性。
Hejlsberg 对 Microsoft 的 Java 产品的更改促使 Sun 控告 Microsoft 违反了 Java 许可协议。Sun 声称,Visual J++ 语言扩展没有保持 Java 的平台中立性,而是将开发人员锁定在 Microsoft 平台上。尽管 Microsoft 的 Visual J++ 事实上是最好的 Java 编译器,并且该公司的虚拟器(执行 Java 字节码的运行期模块)是最快的一种虚拟机,但 Sun 的诉讼已有效地阻止了 Microsoft 在 Java 舞台中的竞争。
Microsoft 的反应是完全从 Java 中退出来,并用一种新的基础因特网平台 — .Net — 和一种新的旗帜语言 — C# 来取代它。Anders Hejlsberg 是完成后一工作的自然选择。
证明一种新编程语言的合理性
最近有关 C# 争论 的一些新闻声称开发人员还不会收受另一种编程语言。虽然现在确实已经有几种有用的语言可供开发人员使用,但仍有空间容纳另一种语言 — 假如它填补了仍未满足的需要。一种新的语言毕竟只是专业人员可用来创建软件解决方案的另一种工具。让我们分析一下 Microsoft 为 C# 制定的目标,以及其他语言到目前为止还没有满足这些目标的情况。
快速应用程序开发:Microsoft 为 C# 制定的最重要的目标之一就是支持 RAD。因特网应用程序必须以因特网节奏开发;一种新语言必须易于学习和调试,而且必须生成易于更新的代码。虽然 Delphi 和 VB 在这些方面很出色,但 C++ 却没有这么成功。语言本身复杂而难以掌握,而且很少有有用的 C++ 库提供简单的接口。此外,C++ 的手动内存管理和复杂的类型转换模型使它难以调试。
此外,正如我们将在下面看到的那样,C++ 本质上不防止由于版本不兼容而导致的潜在问题。尽管 Microsoft 和 Borland 对这种语言作了巨大的努力,但 C++ 仍然不适合快速应用程序开发。
跨平台部署:根据定义,因特网语言应该支持跨平台的部署。因为因特网是不同系统的一个网络,所以必须将服务部署到各式各样的硬件和软件上。 此外,客户端软件应该能够运行在多种类型的设备上,包括 PDA 和蜂窝式便携无线电话。 这种灵活性事实上是对除 Java 之外的所有语言的一种挑战。VB 尤其只能生成在基于 Intel 微处理器的机器上运行的 Windows 应用程序。Delphi 也受到同样的限制。Delphi for Linux 不久就会发布,但它也不支持因特网设备。VB 和 Delphi 都不符合跨平台部署的目标。
访问平台固有的资源:据 Microsoft 称,开发人员需要访问平台固有的资源。对于编写强大的目标应用程序而言,这种访问有时是必不可少的。Visual J++ 6.0 允许开发人员访问 Windows API,Java 通常不允许这种访问。Java 通过定义每种虚拟机实现的最小公分母标准来提供跨平台的部署。Java 开发人员依据这种削弱的标准编写代码,而不能利用只有某些平台才提供的更强大的服务。这样,Java 无法满足提供对平台固有资源的访问这一目标。
支持 COM 和 .Net 平台:Microsoft 已将支持 COM 和 .Net 平台确定为 C# 的最重要的目标。当然,目前还没有一种语言支持 .Net 平台,因为它仍在构建中。与 .Net 不同,COM 已出现相当一段时间了,但它仍然缺少强大的语言支持。大多数语言,包括 C++ 和 Delphi,都要求开发人员为他们所创建的每个 COM 对象创建一个额外的 IDL 声明、一个类工厂和专用的修饰。
某些语言厂商已提供了一些向导来自动完成许多常见的 COM 和 OLE 任务,但这些工具没有完全隐藏 COM 的复杂性。VB 确实成功隐藏了 COM 的复杂性,但这是以牺牲功能为代价换来的。VB 不仅缺乏面向对象语言的力量;它也无法支持 COM 的低级功能 — 例如,多个接口、聚集和自定义编排。简而言之,没有一种现有的语言像 C# 那样全面支持 COM。
虽然这四个目标对于一种新语言是相当高的,但 Hejlsberg 在工作中已拿出自己最好的经验来确保 C# 实现这些目标。结果是产生了一种语言,这种语言从 Java 和 Delphi 中借鉴东西与从 C 和 C++ 借鉴的东西一样多(如果没有超过的话)。请注意,下面的信息基于 C# 操作规程,可能不准确代表最终的产品发行版。
C# 是一种 RAD 环境
C# 提供 Java 风格的垃圾收集:C# 提供的最重要的 RAD 功能之一就是 Java 风格的垃圾收集。在运行时的任意时间内,所有没有引用的对象会自动删除。通过将开发人员从手动内存管理任务中解放出来,垃圾收集使程序开发变得容易且不易出错。但是,自动垃圾收集是耗时的,并且是不可预知的。因此,C# 允许开发人员在要求实时性能的情况下局部禁用垃圾收集 — 通过将代码标记为 unsafe。
C# 实现了 Java 和 Delphi 风格的值/引用类型系统:为了进一步支持 RAD,C# 始终以 Java 和 Delphi 风格的值/引用类型系统处理 C/C++ 指针模型。在这个系统中,内建类型(integer、real、string,等等)、枚举 (enumeration) 和结构 (structure) 都是值类型。赋值运算符和比较运算符复制并检查这些类型的变量的值。接口、类和授权(将在后面说明)都是引用类型。赋值运算符和比较运算符复制并检查这些类型的变量所引用的对象的同一性。
这种值/引用类型系统比 C++ 的指针模型要简单得多。它使对象处理更加容易,并消除了困扰 C 和 C++ 程序的许多内存错误。
C# 接口是独立于类来声明的:C# 也支持类似 Java 和 Delphi 的接口模型,接口是独立于类来声明的。这与 C++ 模型是对立的,在 C++ 中接口实际上就是抽象基类。接口和类都可以继承多个接口。而类可以继承一个基类,接口根本不能继承类。这种模型避免了 C++ 的多继承问题,C++ 中不同基类中的实现可能出现冲突。因此也不再需要诸如虚拟继承和显式作用域这类复杂机制。C# 的简化接口模型有助于加快应用程序的开发。
类方法的声明和定义被组合在一起:C# 简化开发的另一个方面是将类方法的声明和定义组合在一起,与 Java 的做法十分类似。 C++ 开发人员必须为声明(头文件)和定义(实现文件)维护单独的文件,结果使得软件开发过程变得复杂。C# 甚至能够自动找出源代码模块之间的关系,从而使开发人员从一项附加的杂务中解脱出来。尽管 C++ 需要使用 #include(Delphi 需要使用 uses)来定位相关的源文件,但 C# 不需要任何额外的语句。
C# 使用方法引用,称为授权,而不使用方法指针:C# 使用方法引用来快速连接对象和方法。称为授权的这些方法引用类似于 Delphi 的过程类型。Hejlsberg 也将同一机制引入 Visual J++ 6.0 中,不过它并不是标准 Java 规范的一部分。方法引用有点像 C++ 的方法指针,但它要优越、安全和有用得多。
授权是一种引用类型,它持有方法的签名。 应用程序可将符合这一签名的任何方法赋给授权变量。当调用这种授权变量时,相关的方法就会被调用。与 Delphi 的过程类型不同,C# 授权自动支持多点传送。应用程序可以将许多方法赋给一个授权变量;当调用此变量时,所有方法都会被调用。
C# 使用 Java 的简单线程同步机制。要在 C# 中实现线程同步,开发人员只须对关键的代码块作标志即可。 Java 开发人员使用 synchronized 关键字,而 C# 开发人员使用 lock。隐藏的互斥块将带有标志的代码块包装起来,在任一时刻只允许一个线程执行这段代码。除了关键字不同之外,这一机制在 C# 和 Java 中是完全相同的。因为线程所有编程任务最容易出错的一种,对同步进行简化的任何特性都有助于快速应用程序开发。
显式的重载声明:最后,C# 使用显式的重载声明来支持 RAD。显式的重载声明通过保护类的方法名称空间并指出意外的命名冲突来支持快速应用程序开发。
C# 导出类的开发人员必须显式地用关键字 override 标记方法重载,正像在 Delphi 中所做的那样。如果导出类包含一个与基类中的一个虚方法同名的未标记方法,则编译器无法明确辨别作者的意图。
另一方面,命名冲突可能是不可避免的;尤其是当基类和导出类由不同的程序员(可能为不同的公司工作)实现时更是不可避免。在这种情况下,编译器将发出一个警告,并将导出类的方法视为一个新声明,而不将其视为重载。
另一方面,如果程序员有意让导出类中的一个新方法与基类中的某个虚方法同名,则他(或她)可以使用 new 关键字来阻止编译器发出警告。
C# 的其他特性
考虑一下刚刚讨论过的的所有语言特性,对 RAD 的强大支持似乎是 Microsoft 为 C# 制定的一个最主要的目标。但是,C# 还必须致力于其他重要的开发需求。这些需求包括跨平台部署、访问平台固有的资源和对 COM 和 .Net 平台的支持。让我们分析一下旨在支持这些需求的 C# 语言特性。
跨平台部署:C# 以 Java 解决这一问题的相同方式解决了跨平台部署的问题。C# 编译器生成由 .Net 运行时解释的字节码流。运行时组件的工作方式与 Java 虚拟机十分类似;应用程序可以部署到已安装了 .Net 运行时的任何设备上。
应用程序对平台固有资源的访问:与 Java 虚拟机不同,.Net 运行时允许程序访问平台固有的资源。 例如,一个 C# 程序可以利用 Windows API 作为 Windows 2000 上全功能的应用程序运行。通过使用 Windows CE API 子集,同一个程序也可以在 PDA 上运行。 当然,并不是应用程序预期的所有服务都可在所有设备上获得。因此在所有目标平台上测试软件是开发人员的责任,如有必要,开发人员还可以编写特殊情况的代码,那将使系统在缺少所需的服务时仍能够工作。
支持 COM 和 .Net:为了支持 COM 和 .Net 平台,C# 包含一种称为属性的独特语言特性。一个属性实际上就是一个 C# 类,它通过修饰源代码来提供元信息。属性使 C# 能够支持特定的技术,如 COM 和 .Net,而不会干扰语言规范本身。
例如,C# 提供将 C# 接口转换为 COM 接口的属性类。另一些属性类将 C# 类转换为 COM 类。执行这些转换不需要任何 IDL 或类工厂。某些语言观察家声称所有 C# 类都是 COM 对象。这不正确 — 但通过合适的属性,任何 C# 类很容易转换为一个 COM 对象。
随编译器提供的另一个属性库将 C# 类和函数包装为 Web 服务。Web 服务是可在因特网上通过交互协议(如 SOAP)调用的托管软件模块。SOAP 将方法调用与它们的参数和返回值一起包装在 XML 数据包中。Web 服务可用许多语言编写,并可部署到许多硬件和软件平台上。而且,不同类型的 Web 服务可以协作组成整个 Web 应用程序。通过使用正确的属性,程序员很容易将任何 C# 类或函数转换为 Web 服务。
C# 不具备的特性
与 Anders Hejlsberg 的经历一致,C# 用来支持因特网应用程序的许多特性是从 Delphi 和 Java 挑选而来的。而 Microsoft 声称 C# 是从 C 和 C++ 转变而来的。因此让我们分析一下 C# 不具备的某些 C++ 特性。
作用域和非关联化操作符:C# 不使用 C++ 作用域操作符 (::);而是依赖于 Delphi 和 Java 中类似的点操作符 (.)。此外,因为它基于前面说明的 Java 和 Delphi 风格的值/引用类型系统,在大多数情况下 C# 不使用 C++ 的非关联化访问符 (->),而是再次实现了点操作符。虽然重载的点操作符可能使刚开始学习 C# 的 C++ 程序员感到迷惑,但这对 Java 和 Delphi 开发人员来说是相当熟悉的。
引用声明语法:因为类、接口和授权类型默认情况下都是引用,所以 C# 没有 C++ 的引用声明语法。这种新语言改为使用类似于 Delphi 声明按引用传递参数的语法:
无标记的参数是输入参数
ref 关键字指定一个输入/输出参数(类似于 Delphi 的 var)
out 关键字指定一个输出参数
params 关键字指定一个变长参数列表(类似于 Delphi 的 variant open-array 参数)
模板:与 Java 和 Delphi 一样,C# 没有模板。因此,C# 没有支持声明类型安全的集合的机制。每个类最终都是从共同的基类 object 导出的。一般集合类必须是一个 object 集合,需要将它的成员不安全地下溯类型转换为所需的类型。
尽管没有 C++ 的这些语言特性,但 C# 仍然是一种功能强大的编程语言,围绕类似 Java 和 Delphi 的特性进行设计来支持因特网应用程序。因为 C++ 对机器级的关注以及缺少 RAD 支持,所以它不适合用作因特网应用程序开发语言的基础。如果 C++ 开发人员需要针对 .Net 平台编写应用程序和服务,则在 C# 发布以后将会被极力建议学习这种语言 — 请记住,它与 C++ 的实际区别比它们的名称所表明的区别要大得多。
在这个过渡时期,最好学习 Java 或 Delphi。不仅因为这两种语言将使您更好地理解 C#,而且因为这两种语言本身也是非常有用的语言。作为软件专业人员,我们应该利用可获得的最好的工具。
作者简介
Michael L. Perry 六年多来一直从事专业 Windows 开发,并拥有 COM+、Java、XML 等方面的专家经验以及目前塑造编程前景的其他技术。他于 1998 年成立了 Mallard Software Designs 公司,在公司内他用数学严格证明软件设计 — 在实现之前首先建立解决方案的正确性。