`
RednaxelaFX
  • 浏览: 3049314 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

结合自己接触的编程语言,写点最近接触C#与D之后的感想

阅读更多
这个学期开始后,我主要使用的程序语言发生了不小的改变,从以Java为主转移到了以C#为主.然后,在国庆期间开始确实的接触D语言,阅读了语言规范与一些范例代码.生活所迫,现在实在不能继续向D语言投入更多的精力,需要暂时将它再封印起来.但这段时间所看到的和感受到的不能不记录下来,不然时间就真的是浪费了.

这里要记录的,是结合我的个人在编程方面走过的轨迹,观察Java, C#与D的一些特点.只代表从我的角度能看到的状况,有很强的主观性.
同时,推荐对编程语言感兴趣的同好到Lambda the Ultimate - The Programming Languages Weblog去看看.

=======================================================================

先回顾下我学习编程的经历.

最初接触的近乎是编程语言的是LOGO.那是很小的时候,在单色显示器上指挥着小乌龟快乐的画图.然后是QBasic,小学的时候,我也不太记得是以什么为交换条件从Dani那里交换来了一本用QBasic写小游戏的小册子.但我只是粗略的翻了一段时间,虽然也有沉浸在自己写出游戏的幻想中,却一直都没实际去写点什么.
就这么混过了初中和高中,一直到大学进了软件学院,才真正开始学习编程.一上来接触的是计算系统原理课程里里的LC-2的汇编.汇编很直观,有LOGO的基础,对过程式程序的结构还是能轻松理解的.但汇编的表达能力太弱,特别是像LC-2那种最多只有16条指令的体系结构,连一个乘法都得自己写例程,不方便.计算系统原理课程的后半开始教授C语言的基础.真的只是教了基础,连稍微复杂一点的指针都没讲到,主要就是讲完了变量/函数声明,条件/循环控制语句,数组等.这就是我后续学习的基础.一切都是以LC-2的超简化体系结构/指令集与C语言的子集为基础.

大一的第一学期里,在接触到C之后,深感课本里讲的不够用,所以另外找了些书来辅助学习.印象深刻的有清华出版社的谭浩强《C程序设计》,这真的是相当糟糕的一本书.排版问题大概不能怪作者,但内容本身也乱,难以让人系统理解,这大概就怪不了出版社了.这本书跟后来读过的两本谭浩强的书我可能一辈子都忘不了,写得如此糟糕,如同有混乱效果的魔法道具一般.接着,师兄借给我一本由Nell Dale, Chip Weems, Mark Headington写的PROGRAMMING IN C++(SECOND EDITION),感觉就好多了,总算把我带到了C++的门口.这本书的最后稍微带过了些面向对象相关的知识,但是太简短,即使说了ADT啊class之类的概念,还是没办法让我捉摸到头绪.

如是来到了大一的第一学期的结束.读完一次PROGRAMMING IN C++后,我只对C++有模糊的感觉,不知道自己知道什么,更不知道不知道什么.甚至连C与C++的关系到底该如何看待,它们之间的区别的细节如何都没摸清.Anyway,通过课程和自学掌握了基本的C的语法后,我觉得很是鼓舞,可以拿来写点什么好玩的了.就用最原始的方式写了个扫雷来玩.没有用Borland C的图象库,也没有用Curses,只有最原始的界面:以*来显示未打开的位置,#表示旗子,X表示踩到雷,数字表示周围的雷数,然后提示用户输入指令来指定操作类型与参数(坐标).写出来之后很是有成就感,虽然实现的深度优先遍历算法效率很低,虽然界面很糟糕...嘛,更糟糕的是代码的组织方式,大概所有新手都会这样,把整个程序放在一个源文件里,拼命往里塞功能,塞得到后来自己都不想再碰那文件了.

大一的第二学期开始,课程里出现了正式的教授编程语言的课程,这门课同时也是我们课程中最初系统教授面向对象概念的一门.与高一年级的前辈的课程不同,他们用的是C++,而从我们这届开始换成了Java.单以教授面向对象概念这点说,这个改变应该是个不错的决定,再怎么说Java也是更纯粹的面向对象语言.学习到Java,并接触到Swing/JFC之后,觉得可以把以前的扫雷拖出来大改造一番了.要改变程序结构,让它更"面向对象";要改变交互方式,让用户能用图形用户界面(GUI)来游戏;要改进xxx,yyy,zzz...

我将扫雷区(field)设计为装有每个坐标所表示的单元(cell)的容器和遍历雷区状态的操作的对象,然后每个cell是一个包含显示(一个JLabel)/数据(一个int)和一些相关操作的对象.实现出来的改进版扫雷确实能运行,而且也有图形界面,而且也用上了时髦的"对象"概念.我沾沾自喜觉得自己在这种简单小程序的设计上已经很不错,不料却踩到了一个大陷阱:那是一个各功能间高度耦合的设计.这对OO模糊认识下的设计显然是很糟糕的.

到大二的第一学期,我们开始上C++的课程.说真的,还是没教得太深入.C++太大了,支持多种编程范型也意味着许多使用C++的人只能用到C++的很小一部分.一个只用C++中的C子集编程的人与一个专攻C++ metaprogramming的人之间或许就没任何共同语言,虽然他们用的都是C++.我们的C++课程基本上教到基本的面向对象概念,重载new/delete操作符之类的,就没再讲了.像template,STL那些常用的东西都得靠自学.GUI相关的库一个也没教,不知道高一年级的前辈用MFC是不是都是自学的.

=======================================================================

Java

虽然那时课程教的是C++,但我从大一结束的时候开始直到这学期开始前的时间我都是以Java为主要的编程语言.最直接的原因就是,我懒,有很直观又方便的库,再加上有自动内存管理,就这么用了下来.后来其实也知道C++里方便的库也不少,即使要写GUI程序,用wxWidgets,QT,GTK这些都行,也不是一定要用MFC;想要自动内存管理也不是没有,但就是懒得换回来了.加上后面的很多课程都是以Java为范例语言,更是让我懒得换了.

大一结束后的那个暑假,开始与Meta小组一起参加一些程序设计相关的比赛.其中也是以用Java为主——J2EE项目嘛.后来参与的学院里的一个项目也是J2EE项目,可惜那项目在我手上毁了,真的,可惜,也后悔怎么就不负责点把它做好.虽然项目毁了,我积累到的经验还是不少.那是我参与过的最大规模的东西了,无论是从代码行数,还是系统涉及的方面都是.从下面的数据库,到ORM的DAO,到中间的业务逻辑,到上面的控制逻辑,最后到顶上的表现层,然后背后的服务器配置等.应该说,我看到了使用Java来开发这样的系统不错的开发效率,在做一些中型规模的项目时还是很合适的;也看到了一些不足的地方,主要还是"繁琐".

课外时间做的一些逆向工程也有不少是用Java来实现的,但这时Java就显得很笨拙.因为我要做的工具流程很清晰,规模也很小,说实话OO插不上什么手,我最后写出来的东西也不过是object-based而不是object-oriented的.Java的OO长处没发挥出来,底层控制力薄弱的弱点却出来了.这种情况下我依然使用Java的原因不过是一种惯性的推动,加上标准库中String和regex的功能还不错,效率低一点也罢了.我也尝试过别的可能,例如换回用C,或者用Python来做这些小工具,确实是要比Java顺手.纯粹是人的惰性,让我懒得换语言.

不过,真要说我对Java有什么使用上的抱怨的话,首先就是对Swing/JFC很不满.一开始只是做些小游戏什么的,还没太觉得,后来想写点稍微复杂点的GUI就来问题了.Swing的布局非常的不好用.除非能接受一般的BorderLayout,BoxLayout,FlowLayout或者GridLayout提供的简单布局,又或者愿意手工实现一些布局的相关操作,不然GridBagLayout或许就是Swing库中唯一现成可用的布局管理器.很可惜,它的实现是失败的.我已经数不清大二时我为了修理一个用了这布局的界面而有多少个晚上没睡着了.举个例子来说,请看下面使用了GridBagLayout的这个窗口:

在它的初始大小上很正常,一点问题都没有.但要是想稍微把窗口变大一点...:

唔,貌似有点不对? 再拉一下就...:

就变成这样了.我试过把初始大小设大一点看看,结论是初始大小有多大都没关系,都能正常显示;但是拖动之后就没保证了.我肯定我设置的GridBagConstraints符合JavaDoc上所描述的规定,但一拉伸就总会RP.本来我就是因为懒得手写在窗口resize时要做的每个组件的大小重计算和更新,但用GridBagLayout自身提供的resize行为却不对.当时我实在搞不定但作业又到期了,索性把窗口大小给锁住,于是也不需要任何布局管理器,直接把坐标和大小硬编码到程序里就完事.要是谁能指出我的代码的错误,请与我联系,不尽感激 ^ ^
除了布局外,Swing还有众多RP的地方,最典型的两个或许是: 内存泄漏; Look and Feel做得感觉很怪,不够本地化.前者是一直没彻底解决的问题,而后者在Java SE 6里似乎好了些,不过要"根治"也没什么希望.

看到这里,应该会有人要开始推销SWT了.不错,SWT在许多方面比Swing好了不少,不过那布局管理器也很难用,要做到自己满意的效果还是需要花点工夫.外加,无论是SWT还是Swing,要做透明窗口/不规则形状窗口的挺麻烦的.如果只要整体的半透明那SWT还好,如果要做不规则形状窗口,两者那是五十步笑百步了.

OK,作为本文前半段的小结,再回顾一下在这个学期之前,我主要使用的编程语言的顺序:
LOGO->(QBasic)->LC-2 Assembly->(C/C++)->Java
中间还有用过些别的语言,但没成什么气候:
JavaScript(这个用得最多), Python(次多),TVP JavaScript (TJS)(偶尔),OCAML(极少),i386 Assembly(逆向工程时经常看,很少写),Visual Basic(只在大一大二帮别人做作业时用过),Ruby & Lua(基本上没用过,只看过些资料)

这样的接触顺序与范围,对我对编程语言的认知有一定影响.
例如说,正是因为会有需要实际用JavaScript,所以会知道其实它也是一种面向对象编程语言,而不像某些所谓学者(嗯,例如说出什么面试宝典的)声称它不是OO的.只不过JavaScript(ECMAScript v1-3)采取了prototype-based的方式来实现OO,而C++,Java,C#等采用了class-based,这并不等于只有class-based才是OO机制的唯一实现方式.两种方式各有特点,并不能笼统的比较出个优劣.我们可以看到,class-based的OO设计模式里要实现一个Prototype模式要写多少东西,而同样的功能在prototype-based的OO语言中直接就是内建于语言中,使用成本自然不在一个级别上.
也是因为有用过JavaScript,Python,TJS与OCAML,我接触并了解到了closure(闭包)这个有趣概念.将自由变量与一个上下文绑定在一起的这种语言构造相当方便,在很多时候可以让代码更有条理.但用得不好也可能会有难以发现的糟糕后果,比如说写了糟糕JavaScript代码然后让IE崩溃之类.所以看到C# 2.0的匿名方法也支持闭包,又或是Java SE 7有可能加入闭包,我都没有惊讶,只是觉得这或许就是应有的趋势.

=======================================================================

C#

接下来,话题转到C#上.如本文开头提到的,我这个学期开始上.NET课之后才开始系统的学习C#.结果我现在觉得我已经离不开它.可能有人会觉得说,C#与Java在语言上不是一回事么,语法和构思都很像,哪里能算得上"大改变"呢? 实际使用经验告诉我,这两者在外表上的相似使在两者间迁移/转换的成本相当低,而背后提供的支持其实颇为不同,可以根据自己的需求去选用合适的.
我目前会碰到的一些使用场景,已经没有什么大的工程了,反正短期内肯定不会再参加像之前在学院做的那个项目那样规模的(换句话说,用不上Java EE).简单实用的语言就是我所希望能使用到的.对我来说,C#最能吸引我的地方,在于它在语言级提供的丰富功能.没错,其中有大量syntactic sugar,但那又有什么关系,好用就行.另外一点,使用C#来创建Windows上的GUI程序无疑非常方便,而且能保证look and feel是绝对的native;即便要跨平台使用自己写的GUI程序,Mono也能对System.Windows.Forms提供相当程度的支持.要说安全性,世界上没有绝对安全这么一说,要说ASP.NET不安全的话,JSP也有它自身的问题.

在我现在的使用场景中,之所以我会在C#与Java的选择中倾向前者,是因为:
1. 我需要做的使用,Java能做的C#都能做
2. 我的使用范围内,Mono几乎都能提供不错的支持,让我不用担心在linux上无法使用C#的问题
3. C#的语法虽然比Java复杂一些,但提供的功能都相当实用.至少,内建的property,indexer,delegate,event,这些都是我在用Java时很期待却得不到的;匿名方法又比Java的匿名内部类语法更简洁而且支持真正的闭包;内建的#region指令能更规范的整理代码;partial class机制很好的将自动生成的代码与手写的代码分离
4. C#的运行速度经常比Java快,至少在Windows上绝对比Java快
5. CLI比JVM更完善,更先进
6. 即使是ASP.NET,Mono也有XSP来提供支持,不用担心被锁在Windows上.将来也会对Silverlight提供支持(Moonlight计划)

就挑些我比较关注的地方来举例说说吧.上面的几点中1,2是基础,达不到的话我或许还是会出于惯性而不愿意转换语言;3是最重要的地方;4,5是附加得分点;6是我暂时用不到的地方.最近写的所有C#程序,包括作业和一些别的小程序,我都同时部署到.NET Framework 3.5 Beta 2与Mono 1.2.5.1上来观察效果,觉得Mono的完成度比我原本想象的要高,像System.Windows.Forms这种看起来平台相关性特别重的部分都实现得七七八八.我写的几个GUI程序,在Windows XP上用Microsoft .NET Framework 3.5 Beta 2的C#编译器编译后,直接放在openSUSE 10.2上跑也能(近乎)正确的跑起来,界面的显示,程序的功能什么都基本正常.这让我挺欣慰的,不用担心选择C#恒等于选择Microsoft/Windows.
写这篇blog时正好与超哥聊了下,交换了些意见.确实,就现状来说要完全发挥出C#的能力还是得在Windows平台上,用Microsoft的.NET Framework,但我相信Mono能够越来越完善,被越来越多的商业project所接受.不能小看了开源社区的力量,特别是当它有大公司出资出人来支持时.Mono计划从Microsoft得到的支持也不少,例如Moonlight小组使用的测试套件就是Microsoft那边直接给来的.做过TDD的人都会知道,一个测试套件意味着什么.

在Java中,要使用Observer模式很可能是很痛苦的.如果按照标准库里提供的机制,那么实现一个Observer模式需要用到java.util.Observable类与java.util.Observer接口.让被观察的类继承Observable类,让观察者实现Observer接口.这几乎能解决问题了,还差那么一点...就那么一点就要命了.差的地方,就在Java的OO是单根继承的,如果被观察者位于某个继承体系中(但不直接继承java.lang.Object)那就用不了java.util里提供的机制了.变通的办法有好几种,一种是改变自己程序中原本的继承体系,把被观察者的基类改为一个接口,让出基类的位置;还有一种就是抛弃Java标准库提供的机制,手工实现相关的功能,包括添加/删除观察者,维持观察者列表等.痛苦.
实际应用Observer模式一般是有背景的;一般是与"事件"相关的处理上,会很容易想到用Observer模式.上面可以看到用java.util包里的机制去实现Observer模式有限制,Java在"事件"问题上提供了另外一组设施:java.util.EventObject类与java.util.EventListener标记接口.
a. 当做参数传递的事件信息对象要继承EventObject类,
b. 而要作为监听器使用的类,一般是实现一个特化的接口,这个接口继承自EventListener标记接口并声明事件用到的方法名.
c. 最后,要发送事件的类必须自己实现一个监听器列表.
d. 用addXXXListener()方法将事件监听器(一个类)加入监听器列表中.
一个简单的例子: 参考 http://www.blogjava.net/chenweicai/archive/2007/04/13/110350.html

Java中实现Observer模式或者使用事件的痛苦,到C#里就轻了很多.C#内建了event关键字用于声明事件,实际上是一个syntactic sugar,同样会生成一个处理事件的类(包括其中的监听器列表的管理等).于是上面描述的Java中的几步,就变成:
a. 需要特化的事件参数时,继承System.EventArgs类.
b. 不需要.
c. 用event关键字,以System.EventHandler<T>为类型声明一个事件.其中T是具体的EventArgs类.
d. 用+=操作符将事件监听器(一个方法)作为参数传给具体的EventHandler就行.
在最简单的情况下,这样就完成了C#中事件的声明.当然还需要具体实现产生,发送事件的方法等,不过这些在C#与Java中都差不多所以省略了没说.很明显,一般情况下在C#中使用事件机智比在Java中要方便很多.然而C#也留给用户自己编写EventHandler的机会,可以自定义处理监听器(内建的机制无法满足性能需求时).

有时候我们并不真的需要一个"事件",而只是需要一个回调方法(callback method).Observer模式的存在可以说是过程式编程语言中callback function的一个生硬的实现.在C#中便不必痛苦,可以直接使用"delegate".以delegate关键字声明一个类型安全的"函数指针",在这里就叫一个delegate;然后直接就拿这delegate类型与具体的方法名(静态/成员)或匿名方法结合使用就行.Java中没有任何对应的机制,要做出callback的效果,多半涉及新创建一个接口(相当于C#的声明delegate类型)并实现它,然后在需要callback的地方以接口为类型来传递参数.

举这两个例子,主要想说明的是为什么我觉得第三点很重要,为什么出于这个原因我宁可接受语法比较复杂的C#.这是因为,我在使用别的语言(Java)时遇到了一些常见问题,但语言本身没有提供任何通用且简洁的机制去解决问题,迫使程序员接受相对繁琐的解决方式,or let's say "workarounds";假如这时我碰到了另外一种语言,它与我原本使用的语言十分相似(迁移成本低),而且正好有非常简洁的方法解决我原本遇到的问题(迁移价值高),那我没有什么理由继续抱着原来的语言不放而拒绝新语言.

但...there's always the "but".对我来说,由于我在学习C#之前已经学过了很多语言,对我来说delegate,event等关键字带来的是一种清爽感,让我觉得代码可以得到期待已久的简化.但假如有一个没接触过编程,或者是只有很浅的编程经验(例如只用QBasic写过很小的程序之类)的人来从头学习C#,我觉得C#很多内建功能反而会把人家吓跑.
这就是许多反对C#的人会提到的"kitchen sink".他们的观点是语言就是要保持本身的纯洁,简单,不应该看什么好就加什么,最后弄得像kitchen sink一样乱七八糟.这观点固然不错,历史也见证了许多事物从开始的简单,到中期的完善,到后期因功能过度膨胀而被新生的更简洁的事物取代的过程.不过这观点也给人一丝"洁癖"的感觉.我开发经验尚浅,没什么立场去评价这样的功能膨胀到底是好是坏;至少,C#还是很合我胃口.许多反对向Java SE 5之后的语言核心加入任何新功能的人之中,有不少是主张应该把精力集中在完善标准库上而不是增加语言特性去增加程序员的学习成本.嘛,众口总是难调,反正C#是先走了一步,Java要不要"Me Too"就等着瞧吧.

=======================================================================

D

我大概是一年多前看到"D Programming Language"这名词的.当时是在收集游戏引擎的资料,找到了一个叫Yaneurao系列的引擎,同时在作者的介绍页上看了个「D言語研究室」的链接,好奇点进去,看到了D语言的相关介绍.可惜那时没仔细看,只是随便瞄了眼,看到作者最新的更新也只到2004年;提到了C#与Java,然后看到范例代码与这两种语言极其相似;看到有reflection与GC...D语言就这样在我脑海里留下了错误的印象:
1. 我没看D语言的创立者是谁,还以为是Yaneurao系列游戏引擎的作者无聊造出的语言(因为造出样子很像C#/Java的语言的日本人有前例,嗯我说的是TJS)
2. 接上前一点,我以为它不过是"又一个Java"
3. 看到RTTI与GC,我将D语言默认为是以VM为基础的语言

其中,第一点,我过了好一段时间后凑巧发觉D语言的创立者是Walter Bright,还好.但余下的两点误解则一直带到了最近.毕竟,如果不过是"又一个Java"的话,我已经学习并使用TJS,旁边还有完成度很高的C#,何必花费精力去学又一个相似的东西呢.也不知道为什么,到今年国庆前的几天,我突然觉得兴头起来了,开始仔细了解D语言的实际情况.然后我惊讶了.实际状况跟我一直以来的印象完全不同:
1. D语言由Walter Bright创立,由Digital Mars开发并维护
2. 有两种主要的编译器,Digital Mars提供的DMD与GNU的GDC
3. 提供自动内存管理与GC,并在语言级提供了相关支持(默认的new运算符会将对象分配在托管堆上)
4. 提供比C++更完善的RTTI
5. 许多语言构造都有内建属性,例如数组有length属性
6. 有函数指针,有delegate;有限度支持闭包
7. 支持可变长度参数
8. 支持函数重载
9. 支持数组切片
10. 支持泛型编程
11. 支持契约编程
12. 支持mixin
13. 有scope guard,包括scope(exit), scope(success), scope(failure)
13. 语言级支持RAII(Resource Acquisition Is Initialization)
14. 不使用VM,代码是直接编译为native code的(!!)
15. 支持C语言式的调用方式,可以直接调用C语言写的库并与其相连接
// ......关于更多的D语言特性,请参考官网介绍
简单几句话根本列举不完D语言的特性.不过,要是让我用一句话概括我对D语言的认识的话,那就是"另一种C++的进化",删除小部分不需要的功能,调整了一些语义的默认行为,并大幅增强了其它部分的又一种编程语言.由于它像C++一样支持多种编程范型,只把它称为"面向对象编程语言"总觉得委屈它了.这么多的语言特性当然也伴随着很高的学习成本.我自认为我算是知道一点C,知道一点C++,熟悉Java,正不断熟悉C#,但即使是这样我在读D Language Specification时还是犯晕乎——语言内建的东西实在太多了!!

而经过两个星期的"挣扎",我确实的感到了Digital Mars的D语言主页上放在头条的那句评论的含义:
"It seems to me that most of the "new" programming languages fall into one of two categories: Those from academia with radical new paradigms and those from large corporations with a focus on RAD and the web. Maybe it's time for a new language born out of practical experience implementing compilers." -- Michael

D语言不是专供学术研究的那种先进但未必成熟的语言,也不是把关注点集中在RAD和web开发的大公司创造出来的商业味浓厚的语言.它只是根据实际需要而诞生的又一种语言,相对高效且实用.这里"实用"一词要表达的意思或许跟它的本意不太一样,我想说的是...如果现在有什么还活着的编程语言能被称为"kitchen sink"的话,那么在D的面前,Java根本排不上号,C#只能望洋兴叹,C++也无法与之想匹敌.D语言是名副其实的大杂烩,什么特性好用又只会有限度影响效率就会给加进来.
主说,要GC,不要麻烦,所以D语言内建了GC作为默认的内存管理方式
主说,要效率,不要VM,所以D语言把所有代码都编译到native code
主说,要泛型,不要重复代码,所以D语言提供了改进自C++的泛型语法,提供了独特的mixin特性
主说,要精简,不要膨胀的目标代码,所以D语言可以在编译时运行部分程序计算出结果
主说,要文档,不要混乱,所以D语言有独特的内嵌代码形式
...
...and you've got a kitchen sink.

该如何看待这个大杂烩(kitchen sink)呢? 学术上看这种方案显然不是简洁优雅的,因而academia guys大概不会支持它.初学者会看到一大堆语言特性而感到眼花缭乱,从一开始就对它感到畏惧.大公司在短时间内未必会接纳这个尚未成熟的方案.强调实干,敢于尝新的人或许会很高兴看到这么一种特性丰富的语言,并乐于从C++等语言转到D上继续编写软件.
我自己觉得并不会太在意语言提供非常丰富的语言特性,事实上我十分喜欢D语言,觉得这跟我理想中的使用语言很接近,以后写不带GUI的小程序时或许会首选D语言.但我会比较希望语言规范或资料中能将这巨大的语言特性列表归个类,将其划分到不同的aspect上,让熟悉不同编程范型的人能马上掌握D语言提供的相关机制,而不必在刚接触的时候受到自己用不到的特性的干扰.如果能做到这一点,我也可以向别的D语言初学者建议只从单一的编程范型(例如说基本的OOP)着手去认识D语言,避免混乱.在初步熟悉了一个方面的D语言之后,再慢慢了解其它的方面,灵活运用D语言丰富的内建特性来简化和加强自己的代码.
一种语言提供太多内建特性的一个坏处,就是大家虽然使用的是同一种语言,只使用核心语言的其中一个方面的人可能完全无法理解只使用另一方面的代码.这就是为什么很多时候一些语言的设计决定会是精简核心语言,而将多数的额外功能做到库里.D语言现在的设计决定显然有自己的道理,但对其的态度,还是见仁见智吧.

D语言是演化自C++的又一种语言,但它与Java/C#的方式有着绝大的不同.不采用VM不是单纯为了速度性能而做的设计考虑,而是在更高的层次——语言用途目标上的考虑.D适合于编写系统软件,在source-level能保证对多目标平台的支持就足够了,并且性能是重要的因素;Java/C#适合于编写能够直接部署在多种(异质)目标平台的应用程序,必须在各个目标平台上保证binary-level的兼容性,并且安全是重要的因素.避免使用VM,把源代码直接编译为native code,D能提供Java/C#达不到的性能指标,而且能保持source-level跨平台的能力,使其十分适合于编写performance-critical的系统软件.通过使用VM,Java/C#能提供D的程序无法轻易得到的安全性,并且可以在binary-level上跨越平台,简化了部署过程,使其十分适合于网络相关的开发.D与Java/C#在主要应用场景上其实并不冲突,也不太可能出现相互替代的状况.如果只是个一般的小程序,那累死VB之类的语言也都能行,也并不一定要在D与Java/C#之间作决断了.

大杂烩般的D语言为了混合一些无法兼容的特性而作出了不少奇怪的设计.下面只是其中很少的几个例子:

delete运算符:
熟悉C++的人应该不会对delete运算符感到陌生.这是C++中释放资源并/或使对象析构的重要手段.而熟悉Java与C#等内建了GC的人也会知道,这些语言一般会明确说明"没有确定性GC";也不提供delete运算符,因为没有意义.但在D这个大杂烩中,可以看到既有GC,又有能达到确定性GC效果的delete运算符.

Walter Bright对delete的解释是:(原文无修改)
"Deleting a pointer (or reference) will automatically null out the pointer (or reference). However, if you still have a dangling pointer to it elsewhere, that pointer will now point to garbage. I don't know how to eliminate this problem. This means that operatore delete is more of an optimization thing than a core feature."
按照Walter的意思,delete只是个"optimization"作用的关键字的话,那给人的感觉确实像一些人提到的,与C中的register关键字很像;但C中的register并不保证变量被分配到寄存器上,D中的delete却保证其操作数被GC掉,留下损坏数据的可能性.同时拥有指针,引用,GC与delete运算符不得不说是个十分糟糕的设计...

private修饰符:
在Java推广之初,很多C++背景的程序员都对Java中的protected感到困惑.现在,C++/Java/C#程序员肯定会对D语言中的private感到困惑.
"Private means that only members of the enclosing class can access the member, or members and functions in the same module as the enclosing class."
所以,D中的private基本上就是C++中的friend的变种一样."真正"的private跑哪里去了呢?

闭包:
在D语言中,可以定义嵌套的函数,并且内部函数可以使用外部包围(enclosing)函数的参数和局部变量.同时,静态的内部函数可以由function pointer指向,非静态内部函数可以由delegate指向.当这个function pointer或delegate被用在别的作用域中时,外部闭包函数就对内部函数形成了一个闭包,使原本是自由变量的参数和局部变量绑定到内部函数上.这看起来跟一般的闭包没什么区别,但是...
但是,D语言所支持的闭包是"动态闭包"(dynamic closure)而不是一般认识中的闭包.指向内部函数的function pointer或delegate,只能在外部包围函数的栈框架有效时使用.换句话说,不可以把一个内部函数作为delegate类型的返回值返回到"外面"然后再调用.

举例来说,下面的D代码是正确的:
struct Foo
{
    int a = 7;
    int bar() { return a; }
}

int foo(int delegate() dg)
{
    return dg() + 1;
}

void test()
{
    int x = 27;
    int abc() { return x; }
    Foo f;
    int i;

    i = foo(&abc);      // i is set to 28
    i = foo(&f.bar);    // i is set to 8
}

这段代码显示了"动态闭包"的应用,以及使用一个内部函数与一个成员方法作为delegate类型的参数的行为等价性.

但是下面的代码却不可行:
/* NOTE NOTE NOTE
 *
 * THIS CODE IS BROKEN.  This is an example of something that you
 * should NOT do!
 *
 * You can never return a stack delegate because the data pointer of that
 * delegate points to the stack frame where the delegate was created.  Once
 * you return from the function, that stack frame goes away and the memory
 * is reused by some other function.
 *
 * Most likely, when you call this stack delegate, your program won't crash
 * (since the pointer points to a valid address in the stack), but you will be
 * reading trash values, since the memory has been reused by some other
 * function.
 *
 * Of course, if one of the variables is a pointer, then you would crash when
 * you read & follow the pointer that is no longer valid.
 */

import std.stdio;

int delegate() foo() {
    int a = 1;
    int b = 2;

    writefln("int delegate() foo() is called. Locals a = %d, b = %d", a, b);
    writefln("BUG!  You must NEVER return a stack delegate!");

    return delegate int() { return a+b; };
}


int main() { 
    foo();
    return 0;
}

如果把main()中对foo()的调用的返回结果用一个delegate接着,并且调用那个delegate的话,则行为是未定义的,因为局部变量a与b已经不存在了.

美其名曰"动态闭包",用惯了JavaScript或者C#等支持完全闭包语言的人肯定难以接受D里的这种半调子的闭包...但这是Walter Bright的一个设计决定,为了性能考虑,他决定不采用需要在堆上分配空间的完整闭包语义,而只采用在栈上分配空间(也就是,相对普通的函数调用栈没有区别)的"动态闭包".
分享到:
评论
21 楼 lwwin 2010-07-06  
习惯了C-SYTLE的C++以后,要从它上面转投C#和D非常困难~_~
不知道FX大当时怎么就跳到C#去了(虽然你事后做了解释)

看别人用过D,但是还是在这里感觉到啥时候该用用D,不过还是那句话,懒- -是最好的借口=_,=+
20 楼 RednaxelaFX 2007-10-19  
呵呵,Mono小组在WinForms上的态度很模糊.在mono-project.com上看关于移植的建议的话,有一处是写着推荐使用WinForms,因为它(将可能)提供更好的可移植性.当然,没完整实现出来之前,都是空话.另一方面,与Mono绑定在一起的开发环境,MonoDevelop却尚未实现WinForms Designer而是实现了Gtk#的designer,让刚接触的人很自然会用到Gtk#.

Mono与MS Win32上的POSIX接口有本质上的区别.前者起源于个人开源项目,后来得到的主要赞助商与MS的关系却很微妙,在竞争的同时保持有限度的合作;后者就是MS自己的project,演变成Windows Services for UNIX,我没怎么用过,不太清楚差在什么地方.MS对Mono不冷不热的态度无法阻止Mono的发展,就像Sun无法阻挠Harmony的发展一样.相对另外几个CLI实现,Mono已经算做得不错了,Portable .NET的动作缓慢倒是能让人看出GNU对CLI没什么兴趣.

有一点绝对是没错,redsea前辈提到"单一供应商"与"借口"的问题,把C#和CLI提交到ECMA和ISO成为标准当然是MS的商业手腕.在OOXML上问题上MS也是用同一招,不过比起CLI受到的阻力大多了.

P.S. 今天才在Mono的mailing list上看到一个关于libgdiplus的实现不完整出现的问题.幸好我没在Mono上用zedgraph...= =||
19 楼 oldrev 2007-10-19  
Mono很有前途的项目,只是不重视 winforms,原因有两个:
1) QT/GTK+ 各自有 Qyoto/GTK# 的 .Net 绑定,大家自然不喜欢非 native look & feel 的 WinForms
2) WinForms 可能包含 M$ 的专利
18 楼 redsea 2007-10-19  
看看 ms 对 posix 接口, unix com 项目的态度, 就可以想象 mono 的前途.

Windows NT 当年通过了 posix 标准, 从而美国军方可以购买,现在呢 ? 标准系统里面还带着 posix.dll, 但是unix service for windows 的开发早已经停止.

com for unix 更是连正式版都没有推出, 只有beta.

MS 需要有一个 .net 的非 windows 实现, 以便推销 .net 平台的时候, 说, 这不是单一供应商技术, 放心吧 (有些买主可能也就需要一个借口向上面交差而已); 而实际对 .net 开发提供的支持, 只是让它勉强可以运作, 谈不上好.

17 楼 RednaxelaFX 2007-10-19  
嗯,Mono直到现在1.2.5.1对WinForms的支持恐怕都不足以支持注重健壮性的商业程序,特别是直接从MS .NET移植而没做必要的修改的话.有好些已经算"支持"了的功能的具体行为与MS .NET的也不尽相同,例如说ToolTipText的显示的实现等.也有不少根本就没实现而直接抛NotImplementedException的.不过,状况比起两年前已经好了很多,至少让人看到了希望,以后将能够放心在Mono上使用WinForms的这种方向.虽说WPF出来之后WinForms何去何从还有待观察,至少现在它还是值得使用的一套GUI框架.

提到Mono,如果真要用,最好是从SVN HEAD下最新的版本.发布的稳定版(例如现在的1.2.5.1)比SVN HEAD的进度差了好多,有些奇怪的bug早就解决了却没反映到稳定版里.之前我从1.2.5换到1.2.5.1之后有个项目编译运行失败,好生奇怪,然后下了SVN HEAD重新编译了一次Mono就没问题了.
16 楼 mathgl 2007-10-19  
关于Mono 直到现在(ver 1.2.5)对winform的支持还不完善

有些异常处理 直接就退出程序了。对中文的支持也是差强人意

我在公司有个生产用的客户端用.Net的 现在要移植到linux上去
所以考察过Mono一段时间。

如果用Gtk#更是得不偿失。装个runtime都上百M
15 楼 oldrev 2007-10-18  
恩,看岔了,javaeye 的字体太小了
14 楼 oldrev 2007-10-18  
1) 目前所有的C/C++编译器都忽略 register 存储类。任何对程序的手工优化都只能在 profile 的数据上进行,80/20法则说明了人类的猜测是无法达到目的的。

2) D 的GC只会在分配内存的时候动作,对程序性能的影响不大,对于一般应用已经足够,就如同malloc/free对C的普通应用足够好一样,如果有优化的需要可以使用其他的内存分配策略如 "free list"。

GC是未来语言必备的特性,当程序中的对象多到一定程度时,手工内存管理是无法应付的,像是 Firefox 至今无法解决内存泄露问题,为此许多 C/C++ 程序都已经开始使用GC库。

至于让其他程序有内存用,那是 OS 操心的事。
13 楼 redsea 2007-10-18  
to oldrev:

编译器分配寄存器也未必比手工指定高效啊 (除非是那种根据动态运行结果来指导代码生成的 profile 引导的优化过程.)

例如, 一个函数内有两个参数, 分别根据两个参数做两个循环, 编译器会判断不出那个循环可能次数更多, 应该主要将寄存器用于那个循环, 而我们有业务知识可以判断出来. 编译器多半就根据变量出现的次数来做寄存器优化了(排除 profile 引导的优化).

拿delete 来说, 如果我在32bit 地址空间的机器, 申请了一个内存是 1G 的大小, 我觉得这个内存在D 里面, 能够被动态释放掉的可能性几乎是0, 因为1G 占了 32bit机器的地址空间的 1/4, gc 扫描堆栈, 寄存器找可能指针的话, 几乎不可能碰不到刚好指向这个内存的值(而这个值可能只是一个疑似指针, 不是真实指针), 而这将导致这个内存无法释放掉.

当然, 事情可能不那么极端, 但是我的程序倒确实有申请16M 的内存的情况.

tango 的 gc 代码, 我是大致看过的; gc 也并不像编译器寄存器申请一样有只能.

等gc 回收的好处, 无非是gc 的时候, 不必对mutex加锁(因为此时其他thread 已经 pause 了), 就能扫描释放内存, 独立delete 要多一次加锁的代价, 这对于释放小内存并不合算, 但是对于即时释放出大块内存来说,我觉得额外的开销并不大, 让本程序/OS/其他程序能够及时有更多内存用, 不是更好吗 ?

photos/tango 目前的memory 管理代码, 还没有用各 thread 内部内存管理, 每次申请都要对mutex 加锁, 其实并不是一个很高效的设计 (但比起更复杂的方案, 代码量比较小, 初始开销也比较小), 复杂的方案每个线程会有一些基本开销, 带来的好处是线程的内存申请和释放, 大多数情况下不必加锁.
12 楼 oldrev 2007-10-18  
在有 GC 的系统上显式调用 delete 反而是低效的方式,就如同编译器分配寄存器比手工指定高效一样。
D的 delete 是留给重载或没有GC的运行库用的。
11 楼 RednaxelaFX 2007-10-18  
嗯,在接触面方面我确实还是很不足.毕竟学习的时间不够长,我现在没办法把见到的东西全都学好,只能挑立即会用到的来先精通.实际做过的稍微大点的东西也根本算不上"大",不过是典型的Java EE应用而已;另外的一些则是linux系统实验之类的,其实算不上"开发",只是单纯的学习.

===========

Anyway,很明显我不认为Java/C#是万能语言.即使在我自己的开发里,一般我也是混用一些语言来同时保证开发与运行效率.嗯,不过做的东西都不大,所以也说不上什么深刻体会的.正是因为这样,我前面的文里写的是"D与Java/C#在主要应用场景上其实并不冲突,也不太可能出现相互替代的状况." 相信redsea前辈想说的与这句并没有什么冲突,除非前辈认为D在各方面都(将)能够全面取代Java/C#.

但是D的特性,应该如何体现出它的"正交"? 这里我暂时无法理解.希望能得到各位前辈的提点.

Walter在被问及为何要将GC内建于语言中时提到,"所有动态空间分配都是'慢而不稳定'的".特别是在大量分配/释放堆空间后,托管堆的分配很可能会比非托管堆更快(碎片问题);回收时,使用非托管堆或许比使用托管堆快,但这有其中的代价.很多时候使用非托管堆的程序员会转向自己实现链表来管理一块内存,我觉得这不失为一个合理的解决方案,毕竟都用了那么多年了.在自己有完全自主控制的情况下,使用delete运算符当然没问题,出了错也只能怪自己.但在内建了GC,内存控制并不自主时,提供delete运算符很多时候会给程序员以错觉,以为这也是完全安全的.其实在用Java/C#时我经常希望能马上释放一些对象的空间,但没有这样的机制;在D里看到这运算符我本来是很高兴的,只是,使用的时候也必须注意它的pitfalls.查阅Digital Mars的官网,Memory Management与Garbage Collection都没有提到delete的可能危险,Language Spec里的Expressions一项也只提到了语法没提到可能出现的问题.鉴于目前除了官方的Language Spec之外没有什么好资料,这种subtle issue很容易让开发者陷入错误.假设,D的delete能把所有指向被回收对象的指针/引用都设成null,那倒是很好,没什么问题,不过照现在的实现来说,这不太可能做到.

应该再次说明,我的开发经验确实很少,而且对D的认识尚不完全.希望各位前辈在发现我说的不对的地方能指出来,谢谢 ^ ^
10 楼 redsea 2007-10-18  
楼主的接触面还是太窄了, 不仅仅是指语言, 还有实际开发.

如果 Java/C# 的做法完全可以应付当前各种软件需要, C++ 这么一种复杂的东西, 不是早就完蛋了? D 设计来可以处理系统编程问题,设计目标和 Java/C# 是不同的.

系统编程语言提供的语言设施, 应该是让你在各种条件下都可以完成编程工作, 而不是编写80% 的项目很方便, 剩下 20% 的项目就根本无法做. 

大杂烩的问题:
  一个语言的各种feature, 如果是可以正交使用, feature 之间没有多少冗余, 这不应该叫做大杂烩, 应该叫特性丰富, 这是需要的, 毕竟世界是复杂的, 只有一把斧头是不够的.  否则, Java 已经有了 OO, 为什么要搞 generic ---- 虽然它的generic 实在很滥.

  如果有很多 feature, 每个feature 使用的时候, 有n 多需要注意的问题, feature A 和 feature B 搭配的时候, 又产生 n 多需要注意的问题, 这种才叫大杂烩.


gc 虽然用得比较广泛, 但并不是万灵药, 还是存在一些问题的.

delete 这个设施, 你不需要完全可以不用, 但是, 有些场合甚至需要 disable gc, 而完全进行手工分配内存. 即使没有这么极限的场合, 对于一个很大块的内存, 自己主动delete 掉, 而不是等gc 回收, 对系统性能应该也是有好处的.

private 这一点, 其实也是习惯问题了. 换一个角度看, 同一个模块内的代码, 密切相关, 缺省就是friend, 也有自己的道理, 先用 delphi 的人, 会觉得很自然, 没有什么问题.

闭包的问题, 我倒觉得不是决定性的因素, 如果以后用户广泛要求一种性能差些, 但功能更强的闭包, 实现起来也是很简单的, 但是可能就需要用户自己认识到两个版本之间的性能差异 ---- 毕竟这是系统编程语言.




9 楼 ahadf 2007-10-17  
关于delete,如果我理解得不错,Walter 说的应该只是很平常的悬挂引用,引用了delete 后的指针而已,只不过没有办法把关联到该内存的所有指针重置为NULL。既然选用了delete,那就肯定要退回到C++的做法,承担相应的风险和责任,世上哪有免费的午餐呢?

如果我理解得有出入,请为我举一个具体例子,说明D中delete比C++中还要危险。

PS:两位好像对功能强大的GC很推崇啊。只不过光用更新更强更健康的GC的话,那还发明D干什么。如果GC没有性能障碍,D还加入指针干什么?加入delete,只不过是加入指针后的必然选择。
8 楼 Colorful 2007-10-17  
delete还有一些其他的问题存在。
.NET的释放模式可以使用,唯一谨慎的是析构函数的使用。
在.NET中可以放心应用该模式,但在D中最简单的办法就是虽然用释放模式,但是不写析构函数。Developer谨慎编码确保昂贵资源得到释放。但是这样的结果是如果不小心忘记释放了,GC不会给你擦屁股!

此外,有些场景还是需要delete的,GC也可以禁掉,综合着用吧,最重要还是清楚你的需求,了解程序运行时行为和深刻认识语言特性。
7 楼 RednaxelaFX 2007-10-16  
SupressFinalize()么? 嗯确实,D里没有任何相应的机制去抑制析构.
这种情况下只能把delete运算符看成一个extra but dangerous sweet-spot了.比较合适的似乎是,只对不会被外部使用的局部变量使用delete运算符,除此之外尽量避免这运算符的使用...?
6 楼 Colorful 2007-10-16  
我指的糟糕说的是目前在D语言中采用.NET释放模式比较糟糕!
因为在.NET中GC足够强大,比如可以通知GC不用再调用析构函数,内存可以整理,在GC堆上分配内存相当快(我做过相关测试,在.NET的GC堆上分配对象跟在D中类分配在堆栈上速度相当,而.NET在堆栈上分配对象则和D中struct的分配速度相当)、当我们在D中采用释放模式时,没有办法通知GC不再调用析构函数(当然大多数情况我们不写析构函数,但是这是隐患所在)。此外.NET也没有那些悬挂引用问题。
而在D中,释放模式并不能解决该问题。
4 楼 ww21xx 2007-10-16  
个人情况和楼主的情况一样,目前主要是用于C# 吃饭哇!C也在恶补,D才接触! 相当看好 D 
3 楼 RednaxelaFX 2007-10-16  
多谢Colorful前辈过来支持,不然这还真是要零回复了...T T
不过零回复倒也没什么关系,这玩意主观性太强,本来也只是记下来方便自己回顾用.

一个语言默认使用完全引用语义,伴随自动内存管理机制,在需要释放非内存资源,又或者是需要精确控制内存使用状况时,势必会遇到问题.Java与.NET是建议通过实现Closable接口/IDisposable接口来解决释放非内存资源的问题,但在精确控制内存资源方面就没什么办法了.D的delete运算符本来可以是个不错的设施,只是现在的设计与实现有点"危险".请Colorful前辈说说.NET的释放方案糟糕在哪里好吗?

如果维持现在的实现,则可能会出现无效指针;如果把delete改为类似将引用设置为null的效果(当GC看到delete的对象已经不再被任何引用或指针指向时,立即执行GC;否则不执行),则delete运算符会变得鸡肋.两难...
2 楼 Colorful 2007-10-16  
上面说错了。
采用.NET的释放模式是个更加糟糕的方案。

相关推荐

    C#语言编程练习

    C#语言编程练习是针对初学者设计的一系列实践项目,旨在帮助他们掌握C#这门强大的面向对象编程语言。C#是由微软开发的一种现代、类型安全的编程语言,广泛应用于开发Windows应用程序、游戏开发(尤其是Unity引擎)、...

    C# 编程语言详解

    C#编程语言详解 C#编程语言详解

    C#编程语言设计与开发

    《C#编程语言设计与开发》是一门深入探讨C#编程技术的课程,涵盖了C#的基础语法、高级特性以及在.NET框架下的应用。这门课程旨在帮助开发者掌握C#的核心概念,提升在实际项目中的开发效率。课程内容包括但不限于循环...

    C#和R语言的混合编程案例

    C#和R语言混合编程案例涉及的知识点主要包含如下几个方面: 1. 混合编程的基本概念:混合编程是指在一个应用程序中使用两种或多种不同的编程语言来共同完成任务。在本文案例中,主要探讨的是如何将R语言嵌入到C#...

    C#编程语言与面向对象基础教程.rar

    C#编程语言是一种由微软开发的强大且广泛应用的编程语言,主要设计用于构建Windows平台上的应用程序。它被广泛应用于游戏开发、Web应用、桌面应用以及移动应用等。C#结合了面向对象编程(OOP)的概念,使其成为学习...

    c#代码编程语言

    c#代码编程语言

    C#编程语言详解.pdf

    尽管描述部分和部分内容中并未给出具体的章节或知识点,但我们可以基于C#编程语言的基础概念、核心特性以及应用场景等方面来构建相关的知识点。 ### C#编程语言简介 C#是一种现代、面向对象且类型安全的编程语言,...

    关于C#编程语言的一些常见基础面试题.pdf

    2. C#与其他编程语言(如Java、C++等)有哪些不同之处?请说明C#相对于其他语言的优势。 3. C#中的命名空间(Namespace)是什么?请说明在C#中命名空间的作用和用法。 4. C#语言中的面向对象编程(Object-Oriented ...

    Microsoft.NET编程语言C#教程

    Microsoft.NET编程语言C#教程 Adobe Acrobat Reader格式 C#--微软.NET的第一语言 本书着重介绍语言本身,比较少涉及应用,不错的入门书,从头讲起,不怕不明白,想学C#的同学可以看一下作为入门教材。...

    手机上的C#语言编程工具 pccsharp

    C#是一种广泛应用于桌面应用、游戏开发、移动应用和Web服务的高级编程语言,由微软公司于2000年推出。随着技术的发展,C#的使用场景不断扩展,现在甚至可以在手机上进行编程。标题提到的“pccsharp”(可能是笔误,...

    使用C#编程语言编写PLC上位机软件-master.zip

    在本文中,我们将深入探讨如何使用C#编程语言来开发PLC(可编程逻辑控制器)上位机软件。上位机软件是与PLC设备进行通信、监控和控制的计算机程序,它通常运行在个人电脑或其他工业设备上,用于管理和协调自动化系统...

    C++ C#混合编程

    在IT行业中,C++和C#是两种广泛使用的编程语言,它们各自有着独特的优势和应用场景。C++以其高效、低级特性和强大的面向对象能力在系统编程和游戏开发等领域占据重要地位;而C#作为.NET Framework的一部分,适用于...

    C#语言经典编程900例

    【标题】:“C#语言经典编程900例”是一个专为C#初学者设计的学习资源,涵盖了丰富的C#编程实例。这个标题暗示了该压缩包包含了大量的代码示例,旨在帮助初学者通过实践理解C#语言的核心概念和常用编程技巧。 ...

    C# 编程语言详解

    C# 编程语言详解 高清晰的PDF版本,将原有的超星格式重新制作成完整的PDF版本,便于阅读

    C#编程实例与技巧pdg版

    在阅读《C#编程实例与技巧》时,读者会接触到各种标签,如“C#编程实例”,这表明书中的每个实例都是一个完整的、独立的编程练习,可以帮助读者巩固和深化对C#语言的理解。而每个实例都可能涉及不同的编程领域,如...

    C#语言参考.pdf

    C#(发音为“C Sharp”)是一种由微软开发的面向对象的高级编程语言,它的设计兼顾了简洁性和强大的功能,旨在提供一个既易于学习又具备强大能力的编程环境。C#语言的开发起源于C和C++语言,它的很多语法都借鉴了这...

    Unity3d脚本编程 使用C#语言开发跨平台游戏

    Unity3d脚本编程 使用C#语言开发跨平台游戏,适用于新手,本书全面系统地介绍了基于C#的Unity 3D跨平台开发,涉及Unity 3D 跨平台原理分析、Unity 3D 和Mono 的结合,以及在游戏脚本编程中使用C#语言

    C#语言参考文档官方中文版

    C#语言作为微软公司推出的一种面向对象的高级编程语言,从入门到高级应用,都有非常丰富的知识点可供学习和掌握。以下将从给定文件中的标题和部分内容提炼出C#语言的关键知识点: 1. 入门简介:对于初学者而言,...

    C#编程语言程序设计与开发

    C#(C Sharp)是一种由微软公司开发的面向对象的高级编程语言,它于2000年首次推出,并与.NET框架紧密集成。C#的开发受到了C和C++的影响,并且考虑到先前编程语言的优点,设计成既简单又功能强大的编程语言。C#的...

    C#语言基础+C#中的面向对象编程+C#高级特性+C#与.NET框架+C#中的泛型编程+C#异步编程+C#中的LINQ等全套教程

    C#语言基础 C#中的面向对象编程 C#高级特性 C#与.NET框架 C#中的泛型编程 C#异步编程 C#中的LINQ C#与数据库交互 C#中的单元测试 C#游戏开发基础 C#与Unity引擎 C#网络编程 C#多线程编程 C#桌面应用程序开发 C#与WPF...

Global site tag (gtag.js) - Google Analytics