JUST DO IT ~

我只想当个程序员

c# Anders Hejlsberg Bruce Eckel C#设计过程 Checked Exceptions 委托的概念

(转)Anders Hejlsberg谈C#设计过程
博客分类: 程序开发
C++CC#设计模式编程 
原文:http://msdn.microsoft.com/vcsharp/headlines/hejlsberg/default.aspx 
    Anders Hejlsberg为Borland工作13个春秋后,于1996年加盟微软,最初参与设计Visual J++和WFC(Windows Foundation Classes)。然后,担任C#首席设计师和Microsoft .NET Framework设计小组核心成员。目前,他还将继续领导C#语言后续版本的设计工作。 
  2003年7月30日,Hejlsberg在他微软的办公室会见了Bruce Eckel(《Thinking in C++》和《Thinking in Java》的作者)、Bill Venners(Artima.com主编)。谈话内容主要分为三个部分: 
  一、C#设计过程;语言可用性研究和语言美学。 
  二、Checked Exceptions特征的版本相关性和扩展性。 
  三、委托的概念;组件概念在C#中的至高地位。 
  一 
  1、C#设计过程 
  Bruce Eckel:我听说C#是一个工程师小组在一个屋子里设计出来的? 
  Anders Hejlsberg:是的。4年来,我们一直呆在这个屋子里。现在,每周一、三、五,我们仍然在这里会面。 
  Bruce Eckel:我很想了解一些关于C#设计过程的情况。我直接或间接参与过几种语言的设计工作,如Python。在Python设计过程中,Guido van Rossum被我们戏称为“仁慈的独裁者”。 
  Anders Hejlsberg:哦,Guido van Rossum就相当于我的位置。 
  Bruce Eckel:那么你是C#小组“仁慈的独裁者”么? 
  Anders Hejlsberg:我一般扮演最后拍板者的角色。比如,我们被一个问题困扰多时,到了非解决不可、只能作不二选择的时候,是由我来作最后决定的。当然大多数这样的情况下,正确的选择是显而易见的。 
  Bruce Eckel:C#的设计过程是不是和Turbo Pascal、Delphi十分相似? 
  Anders Hejlsberg:后面两者的设计过程不是那么规范的。因为Turbo Pascal主要由我一个人设计,而Delphi也是我和Chuck Jazdzewski、Gary Whizin等几个为数不多的人来完成,所以没有必要引入非常规范的设计过程。相反的,C#的设计过程则十分规范,每周一、三、五从1:00到3:00, 我们都会召开一个正式会议,会议议程也相当灵活,所有的问题都会拿到桌面上公开讨论、仔细推敲。我们还在互联网上建立了一个Wiki,这些问题及其解决方案,以及其他一些相关的东西都被发布在上面。 
  Bruce Eckel:那你们是如何发现这些的问题呢? 
  Anders Hejlsberg:呵呵,我们有一套行之有效的方法。我们可以通过很多途径来得到用户对语言设计的反馈意见——如软件设计咨询会、网络新闻组。这些反馈意见包括:疑问、软件Bugs、不一致、不规范问题等。这样我们就能有的放矢了。最后我们将这些问题整理成表,并一一重现它们。对于每个问题,我们都会认真对待,询问自己:“我们对这个问题有新的想法吗?真的没有吗?这个问题已经搁置好几个星期了,我们立即花30分钟集中精力研究一下,看这次是否能有所斩获。” 
  Bruce Eckel:那可能一个问题长期没有解决,都臭不可闻了…… 
  Anders Hejlsberg:也许有些臭问题只有放到下一个版本才能解决了。但是我认为这样一个过程可以保证不会遗漏任何问题,因为它们都被登记在册。有时候,你面对这些问题呆坐了很长时间,可能也没什么结果。但问题毕竟是被我们逮住了,总有一天会再去“拜访”它的。也可能不会再去“拜访”了,但问题终归是不会被弄丢的。 
  Bill Venners:C#设计小组包含哪些成员,他们都担当什么角色? 
  Anders Hejlsberg:参与最初的C#设计的有Scott Wiltamuth、Peter Golde、Peter Sollich、Eric Gunnerson和我。C#2.0设计小组包括:Peter Hallam、Shon Katzenberger、Todd Proebsting,以及我自己。微软研究院的Don Syme和Andrew Kennedy承担了大部分一般性研究工作。 
  2、语言可用性研究和语言美学 
  Bill Venners:在C#的设计中,可用性研究、市场策略和语言美学的侧重是如何权衡的? 
  Anders Hejlsberg:一般而言,好的语言设计过程体现了对设计小组成员的美学品味取向的综合,也就是你刚才所说的语言美学观。美学品味带有极大的主观性,很难定论,只有产品出来后,你才能仔细体味它。我认为任何程度的可用性研究都不能取代语言美学的价值,因为可用性研究是极有针对性、非常具体的。可能有人问你:“你认为这部分功能如何?”这个问题很难回答。“你对这个语言有什么看法?”你从何谈起呢?你怎么可能花两个小时就解决掉所有可用性问题?绝无可能。 
  Bruce Eckel:人们必须深入理解这个问题。 
  Anders Hejlsberg:使用一种编程语言会经历一个感觉微妙变化的过程。只有使用几个月之后用户才能真正喜欢上它。他们会逐渐发现:“哦,它给人的感觉很舒服嘛。”你不能急于求成。 
  开始说过,我们在可用性研究上也做了大量工作,但主要是针对特定的功能。 
  Bill Venners:可以举个例子么? 
  Anders Hejlsberg:我们将可用性研究的重点放在了IDE功能实现上。我们会问自己:“用户是否知道在这里点击右键会有什么结果?”在纯语言功能部分,我们也考虑了一些可用性问题——例如在一些属性和事件上——不过没什么必要,真的。 
  我想在可用性研究上,语言特性不可能带来和IDE特性一样高的收益。你可以看到用户点击一个菜单项立即得到正确的反馈信息。而对语言来说,问题要复杂一些。例如:“它的概念容易理解么?”我们通过用户咨询会、留言板,比较好的解决了这些问题。用户需要有个说话的地方,“对于这个新特性,我有这样一些想法,你们是如何考虑的呢?”这样的问题提得越多、尖锐越好,因为你肯定是最希望在产品出来以前就能知道用户的想法,而不是产品推出以后。在一个语言特性被完全敲定前,我们通常都会考虑用户的建议和意见的。 
  二 
  1、对Checked Exceptions特性持保留态度 
  (译者注:在写一段程序时,如果没有用try-catch捕捉异常或者显式的抛出异常,而希望程序自动抛出,一些语言的编译器不会允许编译通过,如Java就是这样。这就是Checked Exceptions最基本的意思。该特性的目的是保证程序的安全性和健壮性。Zee&Snakey(MVP)对此有一段很形象的话,可以参见: 
  http://www.blogcn.com/user2/zee/main.asp。 
  Bruce Eckel 也有相关的一篇文章(《Does Java need Checked Exceptions》),参见: 
  http://www.mindview.net/Etc/Discussions/CheckedExceptions) 
  Bruce Eckel:C#没有Checked Exceptions,你是怎么决定是否在C#中放置这种特性的么? 
  Anders Hejlsberg:我发现Checked Exceptions在两个方面有比较大的问题:扩展性和版本控制。我知道你也写了一些关于Checked Exceptions的东西,并且倾向于我们对这个问题的看法。 
  Bruce Eckel:我一直认为Checked Exceptions是非常重要的。 
  Anders Hejlsberg:是的,老实说,它看起来的确相当重要,这个观点并没有错。我也十分赞许Checked Exceptions特性的美妙。但它某些方面的实现会带来一些问题。例如,从Java中Checked Exceptions的实现途径来看,我认为它在解决一系列既有问题的同时,付出了带来一系列新问题的代价。这样一来,我就搞不清楚Checked Exceptions特性是否可以真的让我们的生活变得更美妙一些。对此你或许有不同看法。 
  Bruce Eckel:C#设计小组对Checked Exceptions特性是否有过大量的争论? 
  Anders Hejlsberg:不,在这个问题上,我们有着广泛的共识。C#目前在Checked Exceptions上是保持缄默的。一旦有公认的更好的解决方案,我们会重新考虑,并在适当的地方采用的。我有一个人生信条,那就是——如果你对该问题不具有发言权,也没办法推进其解决进程,那么最好保持沉默和中立,而不应该摆出一个非此即彼的架势。 
  假设你让一个新手去编一个日历控件,他们通常会这样想:“哦,我会写出世界上最好的日历控件!我要让它有各种各样的日历外观。它有显示部分,有这个,有那个……”他们将所有这些构想都放到控件中去,然后花两天时间写了一个很蹩脚的日历程序。他们想:“在程序的下一个版本中,我将实现更多更好的功能。” 
  但是,一旦他们开始考虑如何将脑海中那么多抽象的念头具体实现出来时,就会发现他们原来的设计是完全错误的。现在,他们正蹲在一个角落里痛苦万状呢,他们发现必须将原来的设计全盘抛弃。这种情况我不是看到一次两次了。我是一个最低纲领主义者。对于影响全局的问题,在没有实际解决方案前,千万不要将它带入到整个框架中去,否则你将不知道这个框架在将来会变成什么样子。 
  Bruce Eckel:极限编程(The Extreme Programmers)上说:“用最简单的办法来完成工作。” 
  Anders Hejlsberg:对呀,爱因斯坦也说过:“尽可能简单行事。”对于Checked Excpetions特性,我最关心的是它可能给程序员带来哪些问题。试想一下,当程序员调用一些新编写的有自己特定的异常抛出句法的API时,程序将变得多么纷乱和冗长。这时候你会明白Checked Exceptions不是在帮助程序员,反而是在添麻烦。正确的做法是,API的设计者告诉你如何去处理异常而不是让你自己想破脑袋。 
  2、Checked Exceptions的版本相关性 
  Bill Venners:你提到过Checked Exceptions的扩展性和版本相关性这两个问题。现在能具体解释一下它们的意思么? 
  Anders Hejlsberg:让我首先谈谈版本相关性,这个问题更容易理解。假设我创建了一个方法foo,并声明它可能抛出A、B、C三个异常。在新版的foo中,我要增加一些功能,由此可能需要抛出异常D。这将产生了一个极具破坏性的改变,因为原来调用此方法时几乎不可能处理过D异常。 
  也就是说,在新版本中增加抛出的异常时,给用户的代码带来了破坏。在接口中使用方法时也有类似的问题。一个实现特定功能的接口一经发布,就是不可改变的,新功能只能在新版的接口中增加。换句话说,就是只能创建新的接口。在新版本中,你只有两种选择,要么建立一个新的方法foo2,foo2可以抛出更多的异常,要么在新的foo中捕获异常D,并转化为原来的异常A、B或者C。 
  Bill Venners:但即使在没有Checked Exceptions特性的语言中,(增加新的异常)不是同样会对程序造成破坏么?假如新版foo抛出了需要用户处理的新的异常,难道仅仅因为用户不希望这个异常发生,他写代码时就可以置之不理吗? 
  Anders Hejlsberg:不,因为在很多情况下,用户根本就不关心(异常)。他们不会处理任何异常。其实消息循环中存在一个最终的异常处理者,它会显示一个对话框提示你程序运行出错。程序员在任何地方都可以使用try finally来保护自己的代码,即使运行时发生了异常,程序依然可以正确运行。对于异常本身的处理,事实上,程序员是不关心的。 
  很多语言的throws语法(如Java),没必要地强迫你去处理异常,也就是逼迫你搞清楚每一个异常的来源。它们要求你要么捕获声明的异常,要么将它们放入throws语句。程序员为了达到这个要求,做了很多荒谬可笑的事情。例如他们在声明每个方法时,都必须加上修饰语:“throws Exception”。这完全是在搧这个特性的耳光,它不过是要求程序员多作些官样文章,对谁都没有好处。 
  Bill Venners:如此说来,你认为不要求程序员明确的处理每个异常的做法,在现实中要适用得多了? 
  Anders Hejlsberg:人们为什么认为(显式的)异常处理非常重要呢?这太可笑了。它根本就不重要。在我印象中,一个写得非常好的程序里,try finally和try catch语句数目大概是10:1。在C#中,也可以使用和类似try finally的using语句(来处理异常)。 
  Bill Venners:finally到底干了些什么? 
  Anders Hejlsberg:finally保证你不被异常干扰,但它不直接处理异常。异常处理应该放在别的什么地方。实际上,在任何一个事件驱动的(如现代图形界面)程序中,在主消息循环里,都有一个缺省的异常处理过程,程序员只需要处理那些没被缺省处理的异常。但你必须确保任何异常情况下,原来分配的资源都能被销毁。这样一来,你的程序就是可持续运行的。你肯定不希望写程序时,在100个地方都要处理异常并弹出对话框吧。如果那样的话,你作修改时就要倒大霉了。异常应该集中处理,并在异常来临处保护好你的代码。 
  3、Checked Exceptions的扩展性 
  Bill Venners:那么Checked Exceptions的扩展性又是如何呢? 
  Anders Hejlsberg:扩展性有时候和版本性是相关的。 在一个小程序里,Checked Exceptions显得蛮迷人的。你可以捕捉FileNotFoundException异常并显示出来,是不是很有趣?这在调用单个的API时也挺美妙的。但是在开发大系统时,灾难就降临了。你计划包含4、5个子系统,每个子系统抛出4到10个异常。但是(实际开发时),你每在系统集成的梯子上爬一级,必须被处理的新异常都将呈指数增长。最后,可能每个子系统需要抛出40个异常。将两个子系统集成时,你将必须写80个throw语句。最后,可能你都无法控制了。 
  很多时候,Checked Exceptions都会激怒程序员,于是程序员就想办法绕过这个特性。他要么在到处都是写“throws Exception”,要么——我都不知道自己看到多少回了——写“try, da da da da da(译者注:意思是飞快的写一段代码), catch curly curly(译者注:即‘{ }’)”,然后说:“哦,我会回头来处理这些空的异常处理语句的。”实际上,理所当然的没有任何人会回头干这些事情。这时候,Checked Exceptions已经造成系统质量的极大下降。 
  所以,你可能很重视这些问题,但是在我们决定是否将Checked Exceptions的一些机制放入C#时,却是颇费了一番思量的。当然,知道什么异常可能在程序中抛出还是有相当价值的,有一些工具也可以作这方面的检查。我不认为我们可以建立一套足够严格而严谨的规则(来完成异常检查),因为(异常)还可能是编译器的错误引起的呢。但是我认为可以在(程序)分析工具上下些功夫,检测是否有可疑代码,是否有未捕获的异常,并将这些隐藏的漏洞给你指出来。 
  三 
  1、Simplicity和Simplexity 
  Bill Venners:C#和Java传递对象事件的方式有所不同。Java使用类(通常是内部类(inner classes),它实现监听接口(listener interfaces)。C#使用了委托(delegates。译者注:VJ++6.0就引入了delegates),它有点儿类似函数指针。为什么要采用委托方式呢? 
  Anders Hejlsberg:请允许我首先谈谈对于一般意义上的Simplicity的看法。没有任何人怀疑简单的正确性,但是在如何实现简单的问题上则千差万别。有一种简单,我想称之为Simplexity。你做了一个很实际上复杂的东西,当你将它包装为一个简单的东西时,通常是将它的复杂性隐藏起来。所以实际上,你并不是在设计一个真正简单的系统。这样的一个包装过程,从某些角度上看,系统可能被你搞得更复杂了,因为用户有时候需要知道被隐藏地东西。这就是我说的Simplexity。 
  对我而言,简单必须是真正的简单,也就是说,当你将来某个时候需要钻研系统内部结构时,它应该显得更加简单,而不是比它表面那个样子更复杂。 
  2、委托和接口 
  Anders Hejlsberg:委托提供了与类和接口无关的实现方式,这是我认为最重要的地方。过去的很多编程语言已经认识到了这种方式的重要性。这种方式有很多名字,如:函数指针、成员函数指针,在Lisp语言中,被称为closures, 它是非常有用处的。 
  Bill Venners:那么这是如何实现的呢? 
  Anders Hejlsberg:使用接口的确可以完成委托具有的所有功能,但是你会被迫面对烦杂的“家务管理”。我们可以对比一下Java和.NET处理事件的过程。因为在Java中没有委托,所以最终必须使用接口。 
  接口是对应于事件的,一个接口可以定义1、2、3、4个甚至更多的方法。这点就已经产生了问题,因为这个“对应”没有明确的规范。到底应该定义多少个接口来处理事件呢?是每个事件对应一个接口还是所有的事件用一个接口?让你难以取舍。好了,先胡乱选择其一去处理组件的事件。接下来,你必须实现这些接口。理所当然的,如果处理两个组件的同样的事件,你就必须将接口实现两次——你当然不愿意这么干,所以在这种情况下,你还需要创建一个适配器。这样,烦杂的“家务管理”就跑到你面前来了。 
  内部类在处理家务事上能帮点小忙,但最终,有些问题你是逃避不了的——事件接收者必须知道它什么时候接收事件。这样你又必须明确的实现一个监听接口。与此相反,如果你使用委托,只要信息是兼容的,你就可以将所有事件放在一起处理。这家伙并不关心自己是怎么被调用的,它仅仅是一个(处理事件的)方法。 
  Bruce Eckel:看来,委托是非常精瘦的。 
  Anders Hejlsberg:对,的确如此。 
  Bruce Eckel:它也更加灵活。 
  Anders Hejlsberg:的确是这样。它仅仅依赖于信息的兼容性,如参数是否一致。如果是兼容的,你就可以将多个事件放在一起处理。从概念上说,这也完全满足了用户对一个回调的结果期望,对吧?只要给我一些参数,我就可以编写程序了。听起来很像一个方法吧,那么我给定该方法的一个引用,这个引用就是我所说的委托了。 
  Bruce Eckel:最后你也不会丢掉类型检查。类型检查是在运行时进行的么? 
  Anders Hejlsberg:不,大多数都是在编译时进行。你创建一个委托的实例后,它就和程序员在C++中使用的成员函数指针差不多。委托指向了某对象的一个方法。如果是虚拟的方法,你也能准确地判断委托的指向。所以从某种意义上说说,你可以在实例化委托时就解决虚拟问题。通过委托实现的调用可以看作一个间接的(方法)调用指令。 
  Bruce Eckel:除此之外,就不再需要其他的间接支持了。 
  Anders Hejlsberg:是的,构造一个委托的时候,你就可以一次性解决虚拟函数表(VTBL)和委托的指向问题;通过委托实现的调用都可以直接准确的得到它对应的方法。所以,委托比接口派遣更有效率,即使和标准的方法派遣相比,它的效率也要高一些。 
  Bruce Eckel:C#中也有Multicast类型的委托(译者注:Multicast即多点传送。是指一个委托可以对应多个方法;委托被调用时,就可以引起多个方法的调用。更详细的说明可以参考:http://msdn.microsoft.com/vjsharp/productinfo/ 
  visualj/visualj6/technical/articles/general/delegates/), 它能够使多个函数被调用。 这是一个orthogonal特性吗? 
  Anders Hejlsberg:Multicast是一个彻头彻尾的orthogonal特性。老实说,在Multicast是否重要这个问题,我也是持保留态度的。我承认它有它的用处,但保守地讲的话,我认为所有的委托都是single cast的。有些人觉得Multicast十分重要,使用它有很多优点,但是在大多数情况下委托恰恰都是single cast的。事实上,我们构造的(C#)系统是使用single cast的,只要你(在这个系统里)不使用Multicast,就不会为它付出什么代价的。 
  Bill Venners:委托是怎么来体现你前面所说的 Simplicity和 Simplexity的呢?它们都体现在哪些地方? 
  Anders Hejlsberg:如果你仿效接口来实现委托,那么你最终无可避免地要面对“家务管理”和适配器问题。其实,我们可以观察任何一个捆绑了JavaBeans的开发工具,它们都会生成一些适配器并告诉你:“你不要修改下面的代码,我会分发一些非常瘦小的帮助类给你。”这对于我来说就太复杂了。它并不是一个真正简单的系统,它实际上非常复杂,不过是貌似简单而已。 
  3、组件概念在C#中的至高地位 
  Bill Venners:O'Reilly网站发布过对于你的一次采访,当时你这么评价C#对于属性和事件的支持:“现在,程序员人员每天都在开发大量的软件组件。他们不是在开发彼此孤立的应用程序和类库。每个人都在开发继承自环境提供的组件的新组件。这些新组件覆盖了父组件的一些方法、属性,处理一些事件,然后将这些新组件放回去(到组件库)。这是一个首先要树立的概念。” 
  我希望能更好理解你的谈话精神,因为我一直认为我是在开发类而不是组件。你是说很多人都在为别人开发在Delphi、VB和Java中使用的组件么?你说的“组件”到底是什么呢? 
  Anders Hejlsberg:“组件”一词包含的最重要的意思是组件可以很好的移植。这听起来十分美妙,但我们可能对此有不同的理解。在一个最简单的form中,一个组件可能就等同于一个附加了一些数据的类。组件是一个独立的软件部件,并不仅仅包含代码和数据。它是一个通过属性、方法和事件来实现自我暴露的类;是一个包含了元数据、命名模式等很多附加特征的类。这些特征可以给特定的开发环境提供动态信息,如:组件怎么使用,组件如何持久化自己的数据。开发环境使用组件的元数据就能够实现组件功能的智能解释并给出相应的说明文档。“组件”包含了如上所述的全部(内容)。 
  Bill Venners:我使用Java作开发时想到的是,我是在开发类库而不是组件库,可能是因为我觉得get/set太笨重了。在激发事件的时候,我也使用get/set,但我没有打算将这些类拿到集成开发环境中去使用,我一直想象这些类就是给那些那些纯编码的人们使用的。所以我很想知道现在到底有多少人在开发类似JavaBean那样的组件,面向组件开发是否是未来的趋势,因为在我的职业生涯中,和组件打的交道太少了。 
  Anders Hejlsberg:当今,主流的面向对象编程语言实际上都是混血儿。其中有大量的结构化编程,对象基本上也不过是一个包含了一系列方法和一个this指针的结构体。当你想到一个对象或者组件时,我想,从概念来说,你应该意识到它是有属性和事件的。如果编程语言能给予这些概念头等待遇的话,理解它就要容易一些。 
  有人可能说,C#对于属性和事件的支持不过就是个“甜果”(译者注:原文为syntactic sugar。Peter Landin发明的一个术语,意思是“增加到语言中、让人们感觉更舒服的特性”)而已。其实它的确就是个甜果,对不对?哼,对象本来就是个甜果嘛。我们不过就是在那些虚拟函数表(VTBL)上打转,你用C语言的宏也能实现,对吧?真的,你可以用C语言来面向对象编程,不过它的纷繁复杂能让你坠入地狱。同样的,你可以在C++或者Java里编写组件,但是因为这些语言没有给予组件概念足够重要的地位,所以要痛苦得多。还得说明一下的是,属性,并不是真的是一个属性,而是getBla和setBla(两个方法)。在属性观察器中,你看到的是Bla,但你应该知道它内部映射到了get/set。 
  很明显,组件导向是大势所趋。我们就是通过组件来使用我们的类的,但是在大多数语言中,组件并没有成为最重要的概念。我想强调的是,组件应该拥有头等地位。对于PME编程模式——属性、方法、事件——的谈论已经持续了很长的时间,我们也都在日复一日的使用这些东西来编程,那为什么不在编程语言中给予它应有的至高待遇呢? 
  附注:Anders Hejlsberg简历 
  Anders Hejlsberg是Microsoft公司卓越的软件工程师,领导设计了C#(发音为“C Sharp”)编程语言。Hejlsberg于上个世纪80年代初投身软件事业,为MS-DOS和CP/M平台开发了Pascal编译器。成立不久的一家小公司——Borland——很快聘用了Hejlsberg并收购了他的编译器,然后改名为Turbo Pascal。Hejlsberg接下来领导开发了Turbo Pascal的替代产品:Delphi。1996年,Hejlsberg在为Borland工作13个春秋后,加盟Microsoft公司(译者注:因为Borland公司内部矛盾和Microsoft的殷勤)。


转自http://www.cnitblog.com/sugar/archive/2006/03/19/7773.html 

[人物介绍] 
    Anders Hejlsberg,微软著名工程师,带领他的小组设计了C#(读作:C-Sharp)程序设计语言。Hejlsberg第一次登上软件界历史舞台是在80年代早期,因为他为MS-DOS和CP/M设计了Pascal编译器。当时,还是一个小公司的Borland很快雇用了他,并买下了他的编译器,改称Turbo Pascal。在Borland,Hejlsberg继续开发Turbo Pascal,并最终带领他的小组设计了Turbo Pascal的替代品:Delphi。1996年,在进入Borland 13年后,Hejlsberg加入了微软。最初,他做Visual J++和Windows Fundatioin Classes(WFC)的架构师。随后,Hejlsberg成为C#的首席设计师和.NET Framework的关键参与者。目前,Anders Hejlsberg还在领导着C#程序设计语言的继续开发。 

    Bruce Eckel,Think in C++(C++编程思想)和Think in Java(Java编程思想)的作者。 

    Bill Venners,Artima.com的主编。 

[内容] 
    一、泛型概述 
    二、C#中的泛型 
    三、C#泛型和java泛型的比较 
    四、C#泛型和C++模板的比较 
    五、C#泛型中的约束 



一、泛型概述 

    Bruce Eckel:您能对泛型做一个快速的介绍么? 
    Anders Hejlsberg:泛型其实就是能够向你的类型中加入类型参数的一种能力,也称作参数化的类型或参数多态性。最著名的例子就是List集合类。一个List是一个易于增长的数组。它有一个排序方法,你可以为它做索引,等等。现在,如果没有参数化的类型,那么不论使用数组还是使用List都不是很好。如果你使用数组,你能获得强类型,因为你可以声明一个 Customer类型的数组,但你失去了可增长性和那些方便的方法;如果你使用一个List,你能够得到所有的便利,但你失去了强类型。你难以说出一个 List是什么(类型的)List,它只是一个Object的List【译注:“什么类型的List”指的是List存放的元素是什么类型的】。这会给你带来麻烦,因为类型只能在运行形时进行检查,也就是说在编译时不会进行类型检查。就算你硬要把一个Customer放进一个List并试图从中得到一个 String,编译器也不会不高兴。在运行之前你根本无法发现它不能工作。同时,当你将简单类型【译注:指值类型】放入List时,还必须对它们进行装箱。正是由于这些问题,你不得不在List和数组之间徘徊,你经常要很痛苦地决定应该使用哪一个。 
    泛型的伟大之处在于你现在可以尽情地享受你的蛋糕了,因为你能够定义一个List<T>(读作:List of T)【译注:中文可以说成“T类型的List”】。当你使用List时,你居然能够说出它是什么类型的List,并且你将获得强类型,编译器会为你检查它的类型。这些只是直觉上的好处,它还有其它许多优点。当然,你并不是只能将它用于List,Hastable、Dictionary(将键影射到值上的数据结构)——所有你想调用的都行。你可能想将String影射到Customer、将int影射到Order,在这些情况下你都能获得强类型。 

二、C#中的泛型 

    Bill Venners:泛型在C#中是如何工作的呢? 
    Anders Hejlsberg:在没有泛型的C#中,你只能写class List {...};而在带有泛型的C#中,你可以写class List<T> {...},这里的T是一个类型参数。在List<T>中,你可以把T就当作一个类型来用。当它实际用来建立一个List对象时,你要写 List<int>或List<Customer>。这样你就从List<T>构造了一个新的类型,看起来就好像你用你的类型变量替换了所有的类型参数。所有的T都变成了int或Customer,你无须进行向下转换,它们是强类型的,任何时候都会被检查。 
    在 CLR(Common Language Runtime,公共语言运行时)中,当你编译List<T>或其它泛型类型时,它们和普通类型一样被转换为IL(Intermediate Language,中间语言)和元数据。IL和元数据带有附加信息,可以知道这是一个类型参数,当然,原则上泛型类型的编译和其它类型一样。在运行时,当你的应用程序第一次引用List<T>时,系统会看看你是否已经使用过List<int>。如果没有,它会调用JIT将带有 int类型变量的List<T>编译为IL和元数据。当JIT即时编译IL时,同样会替换类型参数。 

    Bruce Eckel:所以它是在运行时被实例化的。 
    Anders Hejlsberg:它确实是在运行时实例化。它在需要的时候才产生特定的原生代码(native code)。字面上,当你说List<T>时,你会得到一个int类型的List。如果泛型类型中使用的是T类型的数组,它会变成int类型的数组。 

    Bruce Eckel:这个类会在某一时刻被垃圾收集器收集么? 
    Anders Hejlsberg:是也不是,这是一个正交的问题。它会在该程序集中建立一个类,这个类在程序集中会一直存在。如果你终止了程序集,这个类会消失,和其它类一样。 

    Bruce Eckel:但如果我的程序中声明了一个List<int>和一个List<Cat>,但我从未使用过List<Cat>…… 
    Anders Hejlsberg:…… 那么系统不会实例化List<Cat>。当然,下面的情况除外。如果你使用NGEN产生一个镜像,也就是说如果你预先生成了一个原生代码的镜像,会预先实例化。但是如果你在一般的环境下运行,则这个实例化是纯需求驱动(demand driven)的,会尽可能地延迟【译注:正如上面所说,直到使用时才进行实例化】。 
    实际上,我们所要进行实例化的所有类型都是值类型——如List<int>、List<long>、List<double>、 List<float>——我们为每一个都建立一份唯一的可执行原生代码的拷贝。因此,List<int>有它自己的代码,List<long>有它自己的代码,List<float>有它自己的代码。对于所有的引用类型我们共享它们的代码,因为它们在表现上是一样的,它们只是一些指针。 

    Bruce Eckel:因此你只需要转换。 
    Anders Hejlsberg:不,实际上是不需要的。我们可以共享原生镜像,但他们实际上具有独立的VTable。我要指出的是,我们只是尽量对代码进行有意义的共享,但我们很清楚,为了效率,有很多代码是不能共享的。典型的就是值类型,你会很关心List<int>中到底是不是int。你肯定不希望将它们被装箱为 Object。对值类型进行装箱是一种共享的方法,但对它们进行装箱开销会很大。 

    Bill Venners:对于引用类型,所不同的只是类。List<Elephant>不同于List<Orangutan>,但他们实际上共享了所有方法的代码。 

    Anders Hejlsberg:是的。作为实现的细节,它们实际上共享了相同的原生代码。 

三、C#泛型和java泛型的比较 

    Bruce Eckel:如何比较C#中的泛型和java中的泛型呢? 
    Adners hejlsberg:Java 的泛型最初是基于Martin Odersky和其它人一起做的称作Pizza的一个项目的。Pizza后改名为GJ,然后成为JSR,最后以被Java语言收容而告终。这种泛型以能够在原有的VM(Virtual Machine,虚拟机)上运行为关键设计目标。也就是说,你不必修改你的VM,但它会带来很多限制。这些限制并不会很快出现,但很快你就会说:“嗯,这有点陌生。” 
    例如,使用Java泛型,我觉得你实际上不会获得任何的执行效率,因为当你编译一个Java泛型类时,编译器会将所有的类型参数替换为Object。当然,如果你尝试建立一个List<int>,你就需要对所有的int进行装箱。因此,这会有很大的开销。另外,为了让VM高兴,编译器必须为所有的类型插入类型转换。如果一个List是Object的,而你想将这些Object视为 Customer,就必须将Object转换为Customer,以让类型检查器满意。而它在实现这些的时候,真的只是为你插入所有这些类型转换。因此,你只是尝到了语法上的甜头,却没有获得任何执行效率。所以我觉得这是(泛型的)Java实现的头号问题。 
    第二号问题,我觉得也是一个很严重的问题,这就是由于Java泛型是依靠消除所有的类型参数来实现的,你就无法在运行时获得一个和编译时同样可靠的表现。当你在 Java中反射一个泛型的List的时候,你无法得知这是个List什么类型的List。它只是一个List。因为你失去了类型信息,任何由代码生成方案或基于反射的方案所产生的动态类型都将无法工作。唯一让我认为清晰的趋势就是,越来越多的东西将不能运行,就是因为你丢掉了类型信息。但在我们的实现中,所有这些信息都是可用的。你可以使用反射来获得List<T>对象的System.Type。但你还不能建立它的一个实例,因为你并不知道T 是什么。但是接下来你可以使用反射来获得int的Sytem.Type。然后你就可以请求反射将这两个System.Type结合起来并建立一个 List<int>,然后你还能获得List<int>的另一个System.Type。因此,所有你在编译期间能做的在运行时同样可以。 

四、C#泛型和C++模板的比较 

    Bruce Eckel:如何比较C#泛型和C++模板呢? 
    Anders Hejlsberg:我认为对C#泛型和C++模板之间的区别最好的理解是:C#泛型更像类,只不过它带有类型参数;C++模板接近宏,只不过它看起来像类。 
    C# 泛型和C++模板之间最大的区别在于类型检查发生的时机和如何进行实例化。首先,C#在运行时进行实例化。而C++在编译时,或者可能是连接时进行实例化。不管怎么说,C++是在程序运行前进行实例化。这是第一点不同。第二点不同是当你编译泛型类型时,C#会进行强类型检查。对于一个非约束的类型参数,如List<T>,能够在类型为T的值上执行的方法仅仅是那些能够在Object类型中找到的方法,因为只有这些方法是我们能够保证存在的。在C#中,我们要保证在一个类型参数上执行的所有操作都能成功。 
    C++正相反。在C++中,你可以在类型参数所指定的类型的变量上执行你想做的任何操作。但是一旦你对它进行了实例化,它就有可能无法工作,你将会得到一些含义模糊的错误信息。例如,如果你有一个类型参数 T,而x和y是T类型的变量,然后你执行x+y,如果你对两个T定义了一个operator+还好说,否则你就只能得到一些没意义的错误消息。因此,从某种意义上说,C++模板实际上是无类型的,或者说是弱类型的。而C#泛型是强类型的。 

五、C#泛型中的约束 

    Bruce Eckel:约束是如何在C#泛型中工作的呢? 
    Anders Hejlsberg:在C#泛型中,我们能够为类型参数施加约束。以我们的List<T>为例,你可以说class List<T> where T : IComparable。这意味着T必须实现IComparable接口。 

    Bruce Eckel:有意思。在C++中,约束是隐式的。 
    Anders Hejlsberg:是的。在C#中我们也可以这样做。譬如我们有一个Dictionary<K, V>,它有一个Add()方法,这个方法带有K key和V value参数。Add()方法的实现将希望能够将传递进来的key和Dictionary中已经存在的key进行比较,而且它希望使用一个称作 IComparable的接口。唯一的途径就是将key参数转换为IComparable接口,然后调用CompareTo方法。当然,当你这么做的时候,你就为K类型和key参数建立了一个隐式的约束。如果传递进来的key没有实现IComparable接口,你会得到一个运行时错误。这在你的所有方法中都有可能出现,因为你的约定没有要求key必须实现IComparable接口。当然,你还得为运行时类型检查付出代价,因为你实际上进行了动态类型转换。 
    使用约束,你可以消除代码中的动态检查,而在编译时或装载时进行。当你要求K必须实现IComparable接口时,会发生很多事情。对于K类型的值,你现在可以直接访问接口方法而无需类型转换。因为程序在语义上可以保证它实现了这个接口。无论什么时候你尝试建立这个类型的一个实例时,编译器都会检查这些类型是否实现了这个接口,如果没有实现,会给你一个编译错误。如果你使用的是反射,你会得到一个异常。 

    Bruce Eckel:你是说编译器和运行时(都会进行检查)? 
    Anders Hejlsberg:编译器会检查它,但你仍有可能在运行时通过反射来做这些,因此系统还会检查它。正像我前面说的,编译时可以做的任何事都可以在运行是通过反射来做。 

    Bruce Eckel:我可以做一个函数模板,换句话说,一个带有不知道类型的参数的函数?你为约束添加了强类型检查,但我是不是能像C++模板那样得到一个弱类型模板?例如,我能否写一个函数,它带有两个参数A a和B b,并在代码中写a+b?我能不能说我不在乎对于A和B是否有operator+,因为它们是弱类型的? 
    Anders Hejlsberg:你真正要问的问题应该是这在约束中如何说吧?约束,和其他特性一样,最终将可以是任意复杂的。当你考虑它的时候,约束只是一个模式匹配机制。你可能希望能够说“这个类型参数必须有一个带有两个参数的构造器、实现了operator+、有这个静态方法、有那两个实例方法、等等”。问题是,你希望这种模式匹配机制有多复杂? 
    从没有任何东西到完全模式匹配是一个整个的连续体。没有任何东西(的模式匹配)太小了,不能说明问题;而完全模式匹配又太复杂了,因此我们需要在中间找一个平衡点。我们允许你将约束指定为一个类、一个或多个接口,以及一些构造器约束。譬如,你可以说:“这个类型必须实现IFoo和IBar”或“这个类型必须继承基类X”。一旦你这么做了,在编译时和运行时都会检查这个约束是否为真。这个约束所隐含的任何方法对于类型参数所指定的类型的值都是直接有效的。 
    现在,在C#中,运算符是静态成员。因此,运算符不能是接口的成员,因此接口约束不能带给你operator+。你只能通过类约束获得operator+,你可以说这个类型参数必须继承自比如说Number类,并且 Number类对于两个Nubmer有operator+。但你不能抽象地说“必须有一个operator+”,我们无法知道这句话的具体含义。 

    Bill Venners:你通过类型进行约束,而不是签名。 
    Anders Hejlsberg:是的。 

    Bill Venners:因此这个类型必须扩展一个类或实现一个接口。 
    Anders Hejlsberg:是的。而且我们还能够走得更远。实际上我们也想过再走远一些,但这会变得相当复杂。而且增加的复杂性与所得到的相比很不值得。如果你想做的事情在约束系统中不直接支持,你可以使用一个工厂模式。例如你有一个Martix<T>,而在这个Martix(矩阵)中,你可能想定义一个“点乘”【译注:矩阵上的一种乘法运算,另一种称为“叉乘”】方法。这意味着你最终将要考虑如何将两个T相乘,但你不能将这说成是一个约束,至少当T不是int、 double或float时你不能这么说。但你可以让你的Martix带有一个Calculator<T>作为参数,而在 Calculator<T>中,有一个称为Multiply的方法。你可以在其中进行实现,并将结果传递给Martix。 

    Bruce Eckel:而且Calculator也是一个参数化的类型。 
    Anders Hejlsberg:是的。这有些像工厂模式,还有很多方法可以做到,这也许不是你最喜欢的方法,但做任何事情都要付出代价。 

    Bruce Eckel: 是呀,我开始认为C++模板是一种弱类型机制。而当你想其中添加了约束后,你从弱类型走向了强类型。但这一定会带来更多的复杂性。这就是代价吧。 
    Anders Hejlsberg: 关于类型你可以认为它是一个标尺。这个标尺定得越高,程序员的日子就会越不好过,但更高的安全性随之而来。但你可以把这个标尺向任何一个方向调节。

posted on 2015-04-12 11:38 小高 阅读(192) 评论(0)  编辑  收藏 所属分类: java基础DotNetException 异常处理


只有注册用户登录后才能发表评论。


网站导航:
 

导航

<2015年4月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

统计

常用链接

留言簿(3)

随笔分类(352)

收藏夹(19)

关注的blog

手册

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜