当考虑到C++是怎么做的时候,Java是干了件好事,它简化了参数如何传递的问题。在C++中,方法【译注:C++中没有方法一说,应该称为“函数”或“成员函数”】的参数和方法调用通过传值、引用、指针【译注:例如int、int*、int&】,使得代码变得不必要的复杂。C#显式传递引用,不管是方法声明时还是调用时。它大大地减少了混乱【译注:这句话应该这么理解:由于C++的语法问题,有时你并不知道你是在使用一个对象还是一个对象引用,本节后有示例】,并达到了和Java同样的目标,但是C#的方式更有表达力。显然这是C#的主旨—它不把程序员圈在一个圈里,使他们必须绕一个大弯子才能做成某件事。还记得Java吗?Java指南里,建议如何解决传引用的问题,你应该传递一个1个元素的数组去保存你的值,或另做一个类以保存这个值。
【译注:
#include "stdafx.h"
class ParentCls
{
public:
virtual void f(){printf("ParentCls\t");}
};
class ChildCls : public ParentCls
{
public:
virtual void f(){printf("ChildCls\t");}
};
void Test1(ParentCls pc) {pc.f();}
void Test2(ParentCls& pc) {pc.f();}
int main(int argc, char* argv[])
{
ChildCls cc;
Test1(cc);//输出ParentCls
Test2(cc);//输出ChildCls
//只看调用处,是不知道你使用的引用还是对象的,但运行结果迥异!
return 0;
}
】
15.特性 C#和Java的编译代码里都包括类似于字段访问级别的信息。C#扩展了这个能力,对类中的任何元素,比如类、方法、字段甚至是独立参数,你都可以编译自定义的信息,并可以于运行时获取这些信息。这儿有一个非常简单的使用特性的类的例子:
[AuthorAttribute ("Ben Albahari")]
class A
{
[Localizable(true)]
public String Text //【译注:应为public string Text或public System.String Text,如果前面没有using System的话】
{
get {return text;}
//...
}
}
Java使用一对/** */和@标签注释以包含类和方法的附加信息,但这些信息(除了@deprecated【译注:Java1.1版本及以后】)并未build到字节码中。C#使用预定义的特性Obsolete特性,编译器可以警告你,排除废代码(就象@deprecated),并用Conditional特性使得可以条件编译。微软新的XML库使用特性来表达字段如何序列化到XML中,这就意味着你可以很容易地把一个类序列化到XML中,并可以再次重建它。另外一个对特性的恰当的应用是创建真正有威力的类浏览工具。C#语言规范详尽第解释了怎样创建和使用特性。
16.switch语句 C#中的switch语句可以使用整型、字符、枚举或(不象C++或Java)字符串。在Java和C++中,如果你在任何一个case语句里忽略了一个break语句,你就有其它case语句被执行的危险。我想不通为什么这个很少需要的并容易出错的行为在Java和C++中都成了缺省行为,我也很高兴地看到C#不会是这个样子。
【译注: 因为C#不支持从一个case标签贯穿到另一个case标签。如果需要的话,可以使用goto case或goto default实现】
17.预定义类型 C#基本类型基本上和Java的差不多,除了前者还加入了无符号的类型。C#中有sbyte、byte、short、ushort、int、uint、long、ulong、char、float和double。唯一令人感到惊奇的地方是这儿有一个16个字节【译注:原文误写为12个字节】的浮点型数值类型decimal,它可以充分利用最新的处理器。
【译注:补充一下,尽管decimal占用128位,但它的取值范围比float(32位)、Double(64位)远远小得多,但它的精度比后二者的要高得多,可以满足精度要求极高的财务计算等】
18.字段修饰符 C#中字段修饰符基本上Java相同。为了表示不可被修改的字段,C#使用const和readonly修饰符。const字段修饰符就象Java的final字段修饰符,该字段的实际值被编译成IL代码的一部分。只读字段在运行时计算值。对标准C#库来说,这就可以在不会破坏你的已经部署的代码的前提下升级。
19.跳转语句 这儿没有更多的令人惊讶的地方,可能除了臭名卓著的goto语句。然而,这和我们记得的带来麻烦的20年前的basic的goto语句大不相同。一个goto语句必须指向一个标签【译注:goto语句必须必须在该标签的作用域内,或者换句话说,只允许使用goto语句将控制权传递出一个嵌套的作用域,而不能将控制权传递进一个嵌套域】或是switch语句里的一个选择支【译注:即所谓的goto case语句】。指向标签的用法和continue差不多。Java里的标签,自由度大一些【译注:Java中的break和continue语句后可跟标签】。C#中,goto语句可以指向其作用域的任意一个地方,这个作用域是指同一个方法或finally程序块【译注:如果goto语句出现在finally语句块内,则goto语句的目的地也必须在同一个finally语句块内】。C#中的continue语句和Java中的基本等价,但C#中不可以指向一个标签。
【译注:Java把goto作为保留字,但并未实现它】
20.组合体、名字空间和访问级别 在C#中,你可以把你源代码中的组件(类、结构、委托、枚举等)组织到文件、名字空间和组合体中。
名字空间不过是长类名的语法上的甜言蜜语而已。例如,用不着这么写Genamics.WinForms.Grid,你可以如此声明类Grid并将其包裹起来:
namespace Genamics.WinForms
{
public class Grid
{
//....
}
}
对于使用Grid的类,你可以用using关键字导入【译注:即using Genamics.WinForms】,而不必用其完整类名Genamics.WinForms.Grid。
组合体是从项目文件编译出来的exe或dll。.NET运行时使用可配置的特性和版本法则,把它们创建到组合体,这大大简化了部署—不需要写注册表,只要把组合体拷到相关目录中去即可。组合体还可以形成一个类型边界,从而解决类名冲突问题。同一组合体的多个版本可以共存于同一进程。每一个文件都可以包含多个类、多个名字空间。一个名字空间可以横跨若干个组合体。如此以来,系统将可获得更大的自由度。
C#中有五种访问级别:private、internal、protected、internal protected和public【译注:internal protected当然也可以是protected internal,此外再无其它组合】。private和public和Java中意思一样。C#中,没有标明访问级别的就是private,而不是包范围的。internal访问被局限在组合体中而不是名字空间(这和Java更相似)中。Internal protected等价于Java的protected。protected等价于Java的private protected,而它已被Java废弃。
21.指针运算 在C#中,指针运算可以被使用在被标为unsafe修饰符的方法里。当指针指向一个可被垃圾收集的对象的时候,编译器强迫使用fixed关键字去固定对象。这是因为垃圾收集器是靠移动对象来回收内存的。但是如果当你使用原始指针时,它所指的对象被移动了,那你的指针将指向垃圾。我认为这儿用unsafe这个关键字是个好的选择—它不鼓励开发人员使用指针除非他们真的想这么做。
22.多维数组 C#可以创建交错数组【译注:交错数组是元素为数组的数组。交错数组元素的维度和大小可以不同】和多维数组。交错数组和Java的数组非常类似。多维数组使得可以更有效、更准确地表达特定问题。以下是这种数组的一个例子:
int [,,] array = new int [3, 4, 5]; // 创建一个数组
int [1,1,1] = 5;//【译注:此行代码有误:应为array[1,1,1] = 5;】
使用交错数组:
int [][][] array = new int [3][4][5]; // 【译注:此行代码有误,应为:int [][][] array = new int[3][][];】
int [1][1][1] = 5; 【译注:此行代码有误:应为array[1][1][1] = 5;】【译注:小心使用交错数组】
若和结构联合使用,C#提供的高效率使得数组成为图形和数学领域的一个好的选择。
23.构造器和析构器 你可以指定可选的构造器参数:
class Test
{
public Test () : this (0, null) {}
public Test (int x, object o) {}
}
你也可以指定静态构造器:
class Test
{
static int[] ascendingArray = new int [100];
static Test ()
{
for (int i = 0; i < ascendingArray.Length; i++)
ascendingArray [i] = i;
}
}
析构器的命名采用C++的命名约定,使用~符号。析构器只能应用于引用类型,值类型不可以,并且不可被重载。析构器不可被显式调用,这是因为对象的生命期被垃圾收集器所管制。在对象所占用的内存被回收前,对象继承层次里的每一个析构器都会被调用。
尽管和C++的命名相似,C#中的析构器更象Java中的finalize方法。这是因为它们都是被垃圾收集器调用而不是显式地被程序员调用。而且,就象Java的finalize,它们不能保证在各种情况下都肯定被调用(这常常使第一次发现这一点的每一个人都感到震惊)。如果你已习惯于采用确定性的析构编程模式(你知道什么时候对象的析构器被调用),当你转移到Java或C#时,你必须适应这个不同的编程模型。微软推荐的和实现的、贯穿于整个.NET框架的是dipose模式。你要为那些需要管理的外部资源(如图形句柄或数据库连接)的类定义一个dispose()方法。对于分布式编程,.NET框架提供一个约定的基本模型,以改进DCOM的引用计数问题。
24. 受控执行环境对[C#/IL码/CLR]和[Java/字节码/JVM]进行比较是不可避免的也是正当的。我想,最好的办法是首先搞清楚为什么会创造出这些技术来。
用C和C++写程序,一般是把源代码编译成汇编语言代码,它只能运行在特定的处理器和特定的操作系统上。编译器需要知道目标处理器,因为不同的处理器指令集不同。编译器也要知道目标操作系统,因为不同的操作系统对诸如如何执行工作以及怎样实现象内存分配这些基本的C/C++的概念不同。C/C++这种模型获得了巨大的成功(你所使用的大多数软件可能都是这样编译的),但也有其局限性:
l程序无丰富的接口以和其它程序进行交互(微软的COM就是为了克服这个限制而创建的)
l程序不能以跨平台的形式分发
l不能把程序限制执行在一个安全操作的沙箱里
为了解决这些问题,Java采用了Smalltalk采用过的方式,即编译成字节码,运行在虚拟机里。在被编译前,字节码维持程序的基本结构。这就使得Java程序和其它程序进行各种交互成为可能。字节码也是机器中立的,这也意味着同样的class文件可以运行于不同的平台。最后,Java语言没有显式的内存操作(通过指针)的事实使得它很适合于编写“沙箱程序”。
最初的虚拟机利用解释器来把字节码指令流转换为机器码。但是这个过程慢得可怕以致于对于那些关注性能的程序员来说,从来都没有吸引力。如今,绝大多数JVM都利用JIT编译器,基本编译成机器码—在进入类框架的范围之前和方法体执行之前。在它运行前,还有可能将Java程序转换为汇编语言,可以避免启动时间和即时编译的内存负担。和编译Visual C++程序相比,这个过程并不需要移去程序对运行时的依赖。Java运行时(这个术语隐藏在术语Java虚拟机下之下)将处理程序执行的很多至关重要的方面,比如垃圾收集和安全管理。运行时也被认为是受控执行环境。
尽管术语有点含糊不清,尽管从不用解释器,但.NET基本模型也是使用如上所述方式。.NET的重要的改进将来自于IL自身的设计的改进。Java可以匹敌的唯一方式是修改字节码规范以达到严格的兼容。我不想讨论这些改进的细节,这应该留给那些极个别的既了解字节码也了解IL码的开发人员去讨论。99%的象我这样的开发人员不打算去研究IL代码规范,这儿列出了一些意欲改进字节码的IL设计决策:
l提供更好的类型中立(有助于实现模板);
l提供更好的语言中立;
l执行前永远都编译成汇编语言,从不解释;
l能够向类、方法等加入附加的声明性信息。参见
15.特性;目前,CLR还提供多操作系统支持,而且在其它领域还提供了对JVM的更好的互用性的支持。参见
26.互用性。
25.库语言如果没有库那它是没什么用的。C#以没有核心库著称,但它利用了.NET框架的库(它们中的一些就是用C#创建的)。本文着重于讲述C#语言的特别之处,而不是.NET的,那应该另文说明。简单地说,.NET库包括丰富的线程、集合、XML、ADO+、ASP+、GDI+以及WinForm库【译注:现在这些+们多已变成了.NETJ】。有些库是跨平台的,有些则是依赖于Windows的,请阅读下一段关于平台支持的讨论。
26.互用性我认为把互用性分成三个部份论述是比较合适的:de,,并且对那些追求语言互用性、平台互用性和标准互用性。Java长于平台互用性,C#长于语言互用性。而在标准互用性方面,二者都各有长短。
(1)语言互用性和其它语言集成的能力只存在集成度和难易程度的区别。JVM和CLR都允许你用多种语言写代码,只要它们编译成字节码或IL码即可。然而,.NET平台做了大量的工作—不仅仅是能够把其它语言写的代码编译成IL码,它还使得多种语言可以自由共享和扩展彼此的库。例如,Eiffel或Visual Basic程序员可以导入C#类,重载其虚方法;C#对象也可以使用Visual Basic方法(多态)。如果你怀疑的话,VB.NET已经被大幅升级,它已具有现代面向对象特性(付出了和VB6兼容性的损失)。
为.NET写的语言一般插入Visual Studio.NET环境中,如果需要的话,可以使用同样的RAD框架。这就克服了使用其它语言是“二等公民”的印象。
C#提供了P/Invoke【译注:Platform Invocation Service,平台调用服务】,这比Java的JNI和C代码交互起来要简单得多(不需要dll)。这个特性很象J/direct,后者是微软Visual J++的一个特性。
(2)平台互用性一般而言,这意味着操作系统互用性。但是在过去的几年里,internet浏览器自身已经越来越象个平台了。
C#代码运行在一个受控执行环境里。这是使C#能够运行在不同操作系统上的技术重要的一步。然而,一些.NET库是基于Windows的,特别是WinForms库,它依赖于多如牛毛的Windows API。有个从Windows API移植到Unix系统项目,但目前还没有启动,而且微软也没有明确的暗示要这么做。
然而,微软并没有忽视平台互用性。.NET库提供了编写HTML/DHTML解决方案的扩展能力。对于可以用HTML/DHTML来实现的客户端来说,C#/.NET是个不错的选择。对于跨平台的需要更为复杂的客户界面的项目,Java是个好的选择。Kylix—Delphi的一个版本,允许同样的代码既可以在Windows上也可以在Linux上编译,或许将来也会成为跨平台解决方案的一个好的选择。
(3)标准互用性几乎所有标准,例如数据库系统、图形库、internet协议和对象通讯标准如COM和CORBA,C#都可以访问。由于微软在制订这些大多数标准上拥有权利或发挥了很大的作用,他们对这些标准的支持就处于一个很有利的位置。他们当然会因为商业上的动机(我没有说他们是否公正)而提供较少的标准支持—对于和他们竞争的东西—比如CORBA(COM的竞争对手)和OpenGL(DirectX的竞争对手)。类似地,Sun的商业动机(再一次,我没有说他们是否公正)意味着Java不会尽其所能地支持微软的标准。
由于C#对象被实现为.NET对象,因此它自动暴露为COM对象。C#因此就既可以暴露COM对象也可以使用COM对象。这样,就可以集成COM代码和C#项目。.NET是一个有能力最终替代COM的框架—但是,已经有那么多已部署的COM组件,我相信,不等.NET取代掉COM,它已经被下一波技术所取代了。但无论如何,希望.NET能有一个长久而有趣的历史!J
27.结论 到此为止,我希望已给了你一个C#与Java、C++在概念上的比较。总的来说,比起Java,我相信C#提供了更好的表达力并且更适合编写对性能有严格要求的代码,它也同样具有Java的优雅和简单,这也是它们都比C++更具吸引力之处。
……