(长文预警)
本人极少在知乎上回答程序设计和编程语言方面的问题,回答化学问题倒是多一点,今天看到这个问题,作为一名十几年前的老MCSD,以及MCT,外加也是SCJP,觉得可以粗略回答一下:
.NET Framework设计之初,微软根本就没准备让它完全跨平台,在没有使用.NET Framework这个产品名称之前,.NET Framework在微软内部有两种内部称呼。
一种叫做NGWS(Next Generation Windows Services,下一代Windows服务),从这个名称看来,.NET的设计目标之一,就是要取代传统的Win32 API,成为微软的下一代API,最终统一Win32 API、Win64 API和WinCE API(Windows Mobile、Windows Phone等都可以认为是WinCE的一个分支)。
另一种叫做COM+ 2.0,这个称呼在今天的.NET中仍然存在痕迹。.NET Framework的核心,即托管执行(早期国内曾译作受控执行,Managed Execution)环境,或者说也就是虚拟机,称为.NET CLR(公共语言运行时),但早期称为COM+ 2.0 Runtime或者COM+ Runtime,简称COR 2.0,用文本编辑器打开.NET Framework SDK或者新版Windows SDK中的CORHDR.H头文件,可以清楚地看到COM+ 2.0这一称呼的残留痕迹,甚至连托管代码EXE文件的文件头数据结构,仍然叫做IMAGE_COR20_HEADER类型:
typedef struct IMAGE_COR20_HEADER { // Header versioning DWORD cb; WORD MajorRuntimeVersion; WORD MinorRuntimeVersion; // Symbol table and startup information IMAGE_DATA_DIRECTORY MetaData; DWORD Flags; // The main program if it is an EXE (not used if a DLL?) // If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is not set, EntryPointToken represents a managed entrypoint. // If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is set, EntryPointRVA represents an RVA to a native entrypoint // (depricated for DLLs, use modules constructors intead). union { DWORD EntryPointToken; DWORD EntryPointRVA; }; // This is the blob of managed resources. Fetched using code:AssemblyNative.GetResource and // code:PEFile.GetResource and accessible from managed code from // System.Assembly.GetManifestResourceStream. The meta data has a table that maps names to offsets into // this blob, so logically the blob is a set of resources. IMAGE_DATA_DIRECTORY Resources; // IL assemblies can be signed with a public-private key to validate who created it. The signature goes // here if this feature is used. IMAGE_DATA_DIRECTORY StrongNameSignature; IMAGE_DATA_DIRECTORY CodeManagerTable; // Depricated, not used // Used for manged codee that has unmaanaged code inside it (or exports methods as unmanaged entry points) IMAGE_DATA_DIRECTORY VTableFixups; IMAGE_DATA_DIRECTORY ExportAddressTableJumps; // null for ordinary IL images. NGEN images it points at a code:CORCOMPILE_HEADER structure IMAGE_DATA_DIRECTORY ManagedNativeHeader; } IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER; #else // !__IMAGE_COR20_HEADER_DEFINED__ // <TODO>@TODO: This is required because we pull in the COM+ 2.0 PE header // definition from WinNT.h, and these constants have not yet propogated to there.</TODO>
也就是说,微软在设计.NET Framework之初的两个主要目标,一个是统一下一代Windows系统的API;另一个就是取代繁杂的COM/COM+ 1.0组件技术,成为下一代组件技术COM+ 2.0。跨平台根本不是微软设计.NET Framework的主要目标,尽管从基本原理上讲,.NET CLR(或者COM+ 2.0 Runtime)和Java VM一样都是虚拟机,也有一套自己的虚拟机指令,即MSIL代码指令或者CIL代码指令,理论上是可以跨平台的。
微软虽然也开放了.NET CLI(公共语言基础结构)标准,即ECMA-335标准,在.NET CLI中公开了MSIL代码指令、元数据格式等设计虚拟机、编译器等所必需的信息,理论上可以设计出一个非Windows平台的.NET CLR甚至.NET Framework子集,例如Mono,但实际上这样设计出的.NET Framework根本不能完全取代微软官方提供的.NET Framework,仍然有相当部分的.NET应用程序不能执行,原因主要有两个:
1、大量的.NET相关服务(或者类库、API),实际上只是Win32(或者Windows)相关服务的一种包装,实现起来根本无法离开Windows操作系统,如果要独立于Windows操作系统重新实现这些API是相当复杂甚至不现实的,典型例子就是实现分布式事务等功能的企业组件服务(Enterprise Services),可以认为相当于Java J2EE中的EJB,它实际上是COM+企业组件服务(1.0或者1.5版本)的一种包装,而COM+企业组件服务是Windows相关服务的一部分,可以独立于.NET Framework使用,实际上与.NET Framework并无关系。
2、.NET CLR允许托管代码,也就是虚拟机(MSIL或者CIL)代码与本机代码混合执行。例如在使用Visual C++开发.NET应用程序时,编译时加上/clr开关则可以编译成托管代码,但如果在程序中使用#pragma unmanaged编译指示,则可以将一部分源程序编译为本机代码,而.NET CLR支持这两种代码共存于同一个EXE文件中,并在EXE文件执行时,在.NET CLR支持下混合执行,这种混合代码EXE文件显然是不能跨平台执行的。.NET CLR的这一特性,使之更接近一个能运行托管代码,但也支持本机代码运行的运行时环境(Runtime),而不是一个纯粹只运行虚拟机指令代码的虚拟机(VM),因此微软并不把.NET CLR或者COM+ Runtime像JVM一样称为“.NET VM”。
从以上两点也可以看出,微软设计.NET Framework的目的主要是向下一代Windows平滑过渡,跨平台并非首要目标,这一点微软显然还是做到了,从Windows Vista/7开始,即Windows NT 6.0/6.1开始,.NET Framework已经是Windows的一部分了。至于从Windows 8开始,Windows Runtime、Metro以及UWP的暂时失利,反而与.NET Framework关系不大,如果一开始Windows Runtime就完全构建在.NET Framework(甚至.NET Core)上,可能反而容易成功,微软坚持要将Windows Runtime构建在传统Win32和COM组件技术上,导致了Windows Runtime繁杂难于轻量化。
顺便说说,同样一种技术要同时适用于重量化和轻量化平台,往往会就高难就低,或者就低难就高,可能导致无法真正适用于其中一种平台(甚至在两种平台上都不适应),Windows 8/8.1/10和Windows Runtime目前看来就是一个不好的例子,微软以前也有过先例,试图用WPF统一桌面应用程序和浏览器RIA应用,结果并不成功,不得不为浏览器RIA应用专门推出WPF的轻量化版本,即WPF/E,也就是Silverlight。
既然Java和.NET两种平台的设计目标本来就不是完全相同的,那么比较Java编程语言和C#编程语言的特性,就不能脱离开相应平台进行比较。从两种语言的基本语法来看,C#与Java大约有60%左右的相似程度,但这一相似并不应该完全归结为C#抄袭Java,实际上,从编程语言的细节,类库的设计乃至于整个.NET Framework的设计,C#源自Delphi的痕迹反而更明显,这一原因是显然的——Delphi之父Anders Hejlsberg,也就是Visual J++(微软版本的Java,也就是今天的J#)之父,最后也就成了C#以及.NET Framework之父——同一个人,三者同源痕迹极其明显。
以可视化程序设计所必需的事件处理为例,事件处理,通常需要在引发事件的控件中设置事件监听者(Event Listener)或者事件连接点(Event Connection Point),使之指向控件容器提供的事件处理者(Event Handler),而在事件监听者和事件处理者的设计上,Java和C#完全不同,而C#与Delphi很相似:
Java(例如Swing,甚至包括Android):事件监听者通过事件监听者接口引用实现,而事件处理者通常是匿名内部类对象。
C#和Delphi:事件监听者(事件连接点)是指向对象方法的指针,C#中称为Delegate,Delphi则是专门定义的一种指向方法的指针,而事件处理者则通常是控件容器类中实现的方法。
顺便说说,C++既不支持匿名内部类,又不支持指向不确定类方法的指针,因此C++实现事件处理有一定的困难。Java的匿名内部类重写方法,可以直接访问方法之外环境中的局部变量,具有闭包(Closure)特性;C#的Delegate可以直接指向不确定类对象的某一方法,只要方法参数和返回值相符即可通过编译,与对象的类可以无关,即Delegate可以同时指向对象以及对象的方法,我们称Delegate是一种闭包指针(Closure Pointer);而C++对闭包和闭包指针的支持都有限,仅Lambda表达式等功能有限支持闭包。为了克服C++在事件处理中的这个弱点,MFC实现了消息映射;Qt做了编译预处理,通过记录对象元数据,以及信号—槽等复杂机制(类似于Java或者C# Reflection的原理)实现事件处理;Delphi的“孪生兄弟”C++ Builder干脆给C++扩展了一个__closure关键字,让C++也支持闭包指针。
可以看出,C#在推出之初,可以认为结合了Java和Delphi的优点,还吸收了一部分C++的优点,因此C# 1.0一推出就显示出很多优于当时Java版本的语言特性,例如对运算符重载的支持,尽管是很有限的,但也使得String类和字符串的使用等,C#比Java简单直接得多;再例如对自动装箱拆箱的支持、Delegate的支持、非安全代码的支持等,C# 1.0就显得方便得多。.NET Framework 2.0开始在.NET CLR中加入了有限的泛型(参数化类型)支持,因此C# 2.0也能有限地支持泛型,这一点又走在了Java的前面。但Java也不是止步不前的,Java也反过来向C#学习,到了JDK 1.5,即Java 5,Java也引入了自动装箱拆箱、有限泛型支持等类似C#的新特性。C# 4.0以上,以及JDK 7以上,二者都引入了动态语言特性。可以说,目前C#和Java基本是不相上下,语法糖果可能C#更多更方便一些,但Java也有简练易学的优势。
但如果将比较目标从编程语言延伸到二者所在平台上,二者设计之初目标差异导致的结果就非常明显了,.NET Framework设计时就没有把跨平台作为主要目标,因此今天C#基本也只能用于Windows平台,虽然有Mono等开源平台,微软也有.NET Core等跨平台补救措施,但它们并不能完全取代完整版基于Windows的.NET Framework,相比Java在非Windows以及Windows平台上多年积累的开源和非开源生态环境,包括Android,.NET和C#肯定是迟了一步,前景需要观望。至少目前Windows Runtime、Metro和UWP不能说成功,在传统PC客户端Windows应用程序开发这一领域,为了保证Win7甚至WinXP的兼容性,谁也不敢轻易放弃Win32,微软早就想淘汰的Windows Forms,甚至已经有25年以上历史的MFC仍然在Win32开发一线挑大梁,这是事实。
在目前和未来一段时间内,Windows开发用C#,互联网和移动平台更多使用Java,这将是一个客观存在的事实,未来如何,只能拭目以待了。毕竟百足之虫,死而不僵,作为像微软这样有40年以上历史的老牌IT公司,又有相当的实力,是不会在互联网和移动时代,以及Java的冲击面前坐以待毙的,必然有其反制措施,老企业大企业思维僵化,内部利益集团牵扯多,决策颟顸,执行低效是全世界的通病,但体量和实力在那里摆着,不排除有中兴,或者说凤凰涅磐的可能性,不必过早下定论。编程语言终究是一通百通,兼容并蓄也不失为一种积极的态度。