2006年01月27日

原文见:Code Generation FAQ 

注:本文翻译未经过任何许可,仅用作学习,请勿用作商业用途
代码生成是什么?

代码生成是使用程序生成代码的一种技术。这些程序包括从很小的帮助性质的脚本,到创建大量完整应用程序中的业务逻辑模型。对于代码生成应用软件,并没有一种固定的模式,可以使用命令行或者GUI,它们可以创建一种或多种程序语言的代码,可以多次创建代码。没有固定的输入和输出。代码生成的共同的特点就是生成器的输出是代码,这些代码是可以通过手工编写来完成的。

 

代码生成有哪些好处?

代码生成技术的带来的好处可以总结为四个方面:

1.       质量:通过代码生成器生成的代码的质量是由用来生成目标代码的代码和模版的质量决定的,当这些代码和模版的质量提高时,重新生成的代码的质量基于此也会提升。

2.       一致性:由代码生成器生成的代码有很强的一致性,变量命名,方法命名,类命名都通过相同的方式创建。这些使得生成的代码便于使用和分层。

3.       生产力:当代码生成器第一次运行的时候,对于代码生成技术的这一好处是很容易认识到的。你从输入一些对输出代码的设计中所需的输入开始,几乎就在同时,你得到了你的设计所实现的代码。无论怎样,真正的高效率就在你运行生成器来创建基于设计的代码时就开始了。你有几次看见一个项目的需求没有变化过?这就是基于变化的需求生成代码的能力。

4.       抽象:一些生成器基于目标代码的抽象模型来创建代码。比如:你可以创建一个sql Schema 和数据访问层代码,从xml中定义数据库中的表,字段,关系和查询。这种抽象的价值在于生成器可以重新定位以创建其他平台上的代码。这意味着你的业务逻辑和规则拥有了可移植性。而你不必为不同的平台编写代码。

 

对于代码生成,为什么会有正反两种观点

正面的一个观点就是代码生成在短期内提供了很多的好出。“神”经常会在工程完全理解了代码生成对他们所面对的问题的益处的时候。就象面向对象来到时的‘a-ha’一样。这样很容易造成一种“找到了银弹”的想法,但是时间不久,就在不同的方向上出现了错误。代码生成应当用在合适的地方。

相反的,有一些工程师被代码生成刺痛了。他们或者使用着生成器,或者使用着生成的代码,而这些代码正在慢慢的腐化,或者他们开发的生成器并没有实际应用起来。不管什么原因,一些工程师确实不喜欢代码生成。

 

代码生成的工作流程是怎样的?

根本的软件开发流程就是:写代码-编译-测试。我们写下一些代码,然后编译(如果需要的话),然后测试,如果需要变更就重复这个周期。代码生成的流程依赖于使用主动的或被动的代码生成。主动代码生成器创建的代码,你可以不再编辑它们,或者你可以编辑特定的部分使其看起来就象是生成的一样。被动代码生成器一次创建代码,然后你自己来编写核心的内容。

被动代码生成的工作周期就是:运行生成器,然后对生成的代码遵循编写代码-编译-测试的流程。

对于主动代码生成器,你先运行生成器,然后编译,测试输出。在这一点上,如果你在生成的代码中发现问题,你可以修改模版或者改变生成器的输入来重新生成。如果问题是出现的手写代码部分,再遵循编写-编译-测试的流程。

 

什么是主动和被动生成器?

主动生成器创建的代码你可以不用修改或者只修改那些在再生成周期内依然是安全的特定的标记部分。主动生成器的目的在于对于不同的输入产生相同的输出。

被动生成器一次创建代码,然后交给你去维护以使代码可以长时间运行。

被动生成器在开始的阶段是高效的。但是主动生成的益处-代码维护和可以修改模版中的bug,在被动生成中丧失了。

 

什么东西使用生成器生成?

代码生成器可以用在很多不同类型的应用软件开发中。最常用的在数据库访问和用户界面上。

最多的是用在数据库访问方面,因为数据访问的代码是重复性非常高的。另外,生成数据访问层为提供了技术平台的可移植性和平台可移植的益处提供了标准。

用户界面编码也有很大的重复性,所以它也成为生成器的通常的目标。另外,一个用户界面生成器也可以提供多方面的实现,比如针对同一份定义,生成胖客户端和瘦客户端。

 

生成器的下面是什么?

象许多种技术一样,代码生成也有正反两面,在你实现类似工具的同时,你应当确定已经掌握了这些要点。

1.       文档和训练:你需要代码生成器的文档,来解释生成器是怎样工作的,怎样来维护它,你还需要教会项目组的其他人生成器的价值所在和怎样使用。

2.       维护:在你的工作流程中添加一个生成器,就是在为你的应用环境添加一个新的工具,这意味着你将在这个工具的生命周期内发布和维护它。

3.       复杂性:依赖于项目和生成器本身的复杂性,你得到一个巴洛克式设计的生成工具。也许这是一个有挑战性的问题,可以说最好的生成器经常是很简单的,可以把几天的工作简化成几小时的宏。因此复杂性并不是生成器的必须的考量因素。

理解生成器什么时候该用,什么时候不该用很重要。代码生成不能替代面向对象设计。

生成器的种类有哪些?

对应用程序分类是困难的,特别是代码生成器的多样性。一种区分的方法是观察生成器的输入和输出。在输入端你可以使用代码作为输入,或者一个设计所描绘的一个抽象。在输出端,你可以根据一些条件创建输入,或者设计部分实现的新的代码。或者设计的完全实现的代码且需求不会扩展。

基于这样的输入和输出,我们可以得到一些生成器的模型:

1.       Code munger:Code munger读入代码作为输入,然后创建新的代码作为输出,这些新代码可以部分的或者完全的依赖于生成器的设计,Xdoclet,从JavaDoc的代码中的嵌入式注释创建EJB的支持Beans,就是Code munger的一个例子。

2.       内联代码增强器:这种模型读入代码作为输入,以输入代码作为基础创建新代码,但是有一部分代码基于原始代码的设计扩展而来,嵌入式sql语言,比如Pro*C,就是内联代码增强器的例子。SQL是用C代码写的,生成器从实现了命令和查询的C代码中的sql扩展,创建C代码。

3.       混合代码生成器:这种模型使用代码作为输入,然后使用输入代码为基础创建新代码,但是返回输出代码到输入的文件中。向导经常就作为混合代码生成器来实现。代码中嵌入的特别的注释,来定义生成器添加新代码的范围。

4.       局部代码生成器:局部代码生成器使用一个抽象的定义来作为输入,然后创建输出代码,用户自己使用子类化来作扩展,以完整实现设计。

5.       层生成器:这种类型的生成器从一个抽象的设计创建完整的输出代码集。这种代码意味着使用“as-is”,不作任何扩展。这是目前开源和商业生成器最普遍的形式。

 

对于代码生成,开发者最关心哪些

开发者对象代码生成这类新的技术和工具有很多关注点。一些恐惧,担心和怀疑。还有一些合理的担心归咎于当前或先前对于技术的经验。我们希望在下面几个方面有更多关注:

1.       代码生成想作的太多了:市场上很多代码生成器都是Tier Generators(层生成器),这意味着它们被设计用来从一个代码抽象模型创建大量的代码。生成这么多的代码需要被迫学习许多工具和输出代码。但是,这并不是唯一的一种生成器。除过一些小的方面,有许多小的生成器可以提供相同的益处。你可以创建自己的生成器来适应自己的要求。因为大多数目标更广泛的商业化的生成器并不意味着比小的,有特定目标的生成器项目有更高的可用性。

2.       代码生成不能在现有代码基础上工作:这确实是个问题。层生成器趋向于为项目生成新的代码,主要是因为这样更容易命中目标。在现有框架下进行编写代码的工作意味着大规模的修改。用来创建代码的模版适应它们来达到现有的标准。有一种工具,比如Codify,可以在现有代码和新代码情况下很好的工作。

3.       输出代码的质量很差:生成代码维护着质量的一致性级别。这个级别可以从坏到好,依赖于代码的质量和用来创建代码的模版。理想状态下,质量很好,但是如果不是,仍然有一致性的好处。手写的代码的质量在项目进行期间趋向于不同,但是整体在提高。但是经常不是这种情况。邱吉尔被一个女人攻击:“邱吉尔!你是个酒鬼!”。他回答说:“也许我是个酒鬼,但是你很丑,明天我会清醒,但是你依然很丑”。这样的情况也出现在代码生成器上。代码生成器可以被编辑来创建一致的,好的代码。但是人们采用手工的方式一直编写着不一致的代码。

4.       代码生成会吃掉我的工作:如果你的工作包含编写大量的代码。不妨尝试下自动过程,你也许是对的。但是我们中的很多人进入这个行业是因为喜爱创建有趣的,可以帮助人们的程序。代码生成帮助你更快的创建程序。所以对很多人来说代码生成带来了很多机遇。

5.       代码生成太复杂了:生成器关注于它所要创建的代码的复杂性和生成关系的复杂性。它们可以很复杂,但是生成器的输出比起一个灵活的运行时库里的函数化的代码来说,经常是很容易调试和维护的。尽管这样没错,但是生成器的复杂度还时要注意的,不论是开发期间还是部署期间。. “you aren’t going to need it” (YAGNI)的原则要严格的用于生成器的设计和实现。

6.       你不能生成X技术:任何文本文件都是可生成的。所以没有什么不可能。一个问题就创建一个生成器和手写代码之间的投入产出的对比。举个例子:生成器自己可以被生成,但是我假想它是复杂的项目

7.       生成器是很好的起点,但是他们从不维护:这是非常合理的相法。有许多次我看到生成的代码被手工改动。“DO NOT EDIT”的注释依然存在于代码中,很久都没人理会。的确,这只能通过长期对开发者培训如何维护生成的代码来解决。但是也有一些实践的观点,就是从来不在代码管理系统中保留生成的代码。代码生成是创建程序过程中的活跃部分。保证代码从来没有手工修改过,而且生成器经常维护或者丢弃。

 

代码生成和CASE工具有怎样的关系?

CASE工具,在80年代末和90年代初被夸大为“程序员的终结”。分析师可以使用可视化工具定义业务逻辑,而且生成器可以创建所有的程序代码。在这方面,这些声音更象是MDA,为那些时下流行的平台,象J2EE,.NET,编译UML到代码。代码生成的边界和CASE工具的核心落在了同一个地方。使用生成器从抽象的定义创建应用程序。

代码生成的不同在于它是一种自下向上的,来减轻编码工作量的方法。开发者为自己编写工具来使他们的工作更简单,CASE工具是自上向下的,它们企图使用工具替代受过训练的开发者。这是两者在某些方面,但更是最根本的不同。

转自: http://www.cnblogs.com/dahuzizyd/archive/2006/01/26/323637.html

      感觉对大家有用, 转给大家, 我在看过文章后, 查了一下我的手机, 里面果然有2个我自己根本不知道的服务信息, 也从来没有收到过他的什么短信, 每个月扣我8元钱, 想想吧, 我可能这样白白给他们交了1/2/3年的钱(我的手机号码用了7年)?

      如果大家觉得本文有用, 希望广泛转载, 让更多的人知道. 谢谢.

(本文为转贴)

去年,我已在星星发表了一个关于免费发短信的各类软件的黑幕所在。而事实上的SMS(即短信)的黑幕远不止于此,今天,我终于有空坐下来,把其中的一些让你感觉平常却实际触目惊心的事情告诉你们,让你们知道SMS为什么是抢钱的最佳方式!

数据:2004年互联网全国总收入中,74%来源于网站嫁接了短信业务。手机短信如何让这些商家如此疯狂的敛财?不就一些手机文本短信嘛,为什么居然是现在的IT界第一收入来源?

本人josou,去年从事于中国某大型门户网站(中国三大门户之一),于去年底辞职走人,现从事图书事业,不再跨进IT行业半步,特别是无线行业中的SMS短信业,一个字,黑!

我本人的数据,经我的手开发维护的该网站所在公司的SP(短信服务提供商)的网关收发平台,去年一年赚了中国手机用户共1.8亿元!绝对包括龙族中的部份兄弟,用“赚”是客气话,实际是“抢”或“骗”!

你们一定开始奇怪:手机在我手里,每个月话费我交给移动公司,一个第三方公司怎么抢我的钱啊?别急,听我细细道来!


第一阶段:抢
———————————————————–
实际就是SP公司的早期发家阶段,这一阶段最为疯狂!而招数也最为简单直接,但各位并非行内人,并不知就里。

先讲一下移动(或联通)这个公司,这两个叫运营商,能发手机短信的平台被这两个运营商所垄断,大家用手机写短信或收短信,都是移动或联通的一个系统作为中转平台,各位的手机是终端,打个简单的比方,移动和联通是服务器,而大家的手机是终端PC,这就好理解了。

不知从哪一年起,运营商把这个短信垄断权开始解冻,允许一些公司获得移动或联通的认证,分配给每个SP一个端口,你收到的短信显示什么号码,那个号码就是端口。然后接入这个中转平台,于是,这些公司也具备了短信中心的能力。也就是说,获得了抢钱的资历!

于是这些SP设计一些短信产品服务,比如叫“精彩新闻”,只要你发短信“A”到它的端口号,比如“55188”,那么就相当于你采用了它的服务,而运营商也承认了这个服务,那么移动和联通就代SP向手机用户收服务器,比如8元。

如果是正规操作,应是你主动用手机发A到55188才算开通服务,那么移动联通就会从你的手机费中扣去8元(这8元实质上是运营商跟SP分赃)。

这个流程是正当的,因为是用户知情的情况下主动申请开通的,那么扣这8元也是天经地义,无话可说。

可是大家知道短信就那么70个字,用来看新闻基本看不到什么,所以会主动去订这个服务的人不多。那这样的话,SP的如意发财梦就做不下去了,怎么办呢?――――好,抢开始了!

早期的移动公司短信平台BUG很多,全被SP钻了空子!SP偷偷的,没有任何先兆的情况下,向你的手机账号发了一个计费数据包,移动收到这个包在早期是不会跟用户作确认的,于是直接扣钱,那时候用户这方面的意识极为淡薄,一个月被SP偷偷扣掉了8元钱,他却混然不知!一个用户偷扣8元,如果是1万个用户呢?8万元!10万个用户呢?

80万!100万个用户呢?800万!一个月800万,一年呢?….这只是一个服务,如果SP同时在几个服务上用这一招,一年几个亿不成问题!而用户的投诉率却少得可怜!

那时,广州的讯龙公司最深谙此道,用这个方法迅速发展成为国内最大的SP!新浪看到有机可图,巨资收购讯龙公司,那一年,新浪在短信服务这一块,一个季度收入为7000万美金!……

这是一个什么样的概念!!!!

这个抢的时期,是由于手机普及不久,大家对话费减少如果不是很大的话,一般都不太敏感,导致了这场SP的大掠夺!而这些只有业界的人才知道,而作为受害者的手机用户,却在完全不知情的情况下被抢了,他们连讯龙是什么公司都不知道……


第二阶段:骗
———————————————————–
渐渐的,用户的维权意识上来了,学会频密地查话费了,消费者委员会给中国移动和中国联通巨大的压力,SP想偷抢已困难。但他们不甘心就此结束暴富。他们的第二招很快成型!

这时候,中国移动和联通的整治行为是建立在制度上的,在技术上,那个中转短信平台还没有完成升级,这给SP很大的可趁之机!

移动的制度如下:任何SP不得在用户没有主动发短信确认的情况下,单方面进行计费(业界叫反向绑定手机),否则扣除该SP的当月收入,严重者吊销证书,关闭SMS端口!

这条规定看起来科学,即要用户主动发短信来订阅SP的产品了,才能扣钱,但是这条短信系统却无法界定。本来一定是要系统匹配用户发的是A到55188,才允许SP扣费,是最科学的,可是现在系统还没升级到这个能力,也就是说,系统只要测到用户手机有主动上来的短信,那就算是对SP的服务发生了订购关系,于是就允许SP扣用户的钱了!

这是多么可怕的BUG!

很快就被SP利用这个BUG了!

好,我是SP,我设计一款产品,叫同城约会,主要是通过短信来聊天的业务,包月制的,一个月需交10元。但是要用户主动发短信上来才能扣用户的钱啊,怎么让用户主动发上短信来呢?呵呵,SP公司的那些所谓“智囊团”点子多着呢!

1、同时给很多手机(当然包括你喽!)发一条很类似是朋友发来的短信,于是你的手机收到这样的信息“不好意思,我不小心在电话本里删了你,请你再告诉我你是谁?”

发来的号码是 551881389827363。看到这样的短信,你怎么办呢?我统计过,20%左右的人以为真是朋友发来的,于是发回各种各样的信息回复过去,比如“你是谁啊?”“我是龙族,你的手机怎么被抢的?”――――――惨,你回复信息给SP了!你一回复,就相当于移动的平台系统认为是你主动发信息给SP的,于是SP开怀大笑,当月马上扣你的话费10元,就这样,奸计成功!

2、发类似的聊天类信息,一般是异性,来骗你回复,比如“好想你啊,自从我们一起来到这个地方,我就天天偷偷关注你,爱在心口难开!我在网上用这个匿名的号码发短信给你,能回复我吗?”类似这样的,我也统计过,这样大概会骗来约 9%-23%的回复,一回复,SP又光明正大的扣你的钱了。

3、设计出类似泡泡,QQ,UC等免费发短信的软件。凡是要使用免费发短信的软件,都需要用手机先注册,一般你在QQ,UC上开通短信服务时,你填入你的手机号,然后“提交”,一两秒后,你收到SP发过来的确认码,然后你把确认码填在软件提交里,一提交,也相当于是你发了短信到SP端口,被系统接受的了。由于是你主动开通这个服务的,所以怎么扣你的钱,什么时候扣(这种一般都是延迟比较久才扣,用户几乎不会发觉),都是SP决定的。等用户发现被扣了钱后,已经无证可查,因为移动规定业务数据要保存60天,60天后SP可主动清空数据库,所以,你能奈他何?所以,为什么新浪要收购UC?你明白了吗?免费发短信,本来就是一个很大的洞,要你向里跳!

4、还有别的包括利诱,抽奖等,骗取回复。反正原理一样,一回复那个端口号,就算是主动发短信给SP,这个月的钱是扣定了!

更可怕的是,只要第一次被扣,你不会去退订,SP是每个月都按时扣这10元的!!!!

我说过,这个阶段的用户开始有话费意识,会每个月定期查几次话费,看有没有SP公司扣钱,一般是打1860去询的。问到有,当然是大怒,,可是要投诉的话,SP一口咬定你是发过订阅指令上来才扣你的费的,1860的人员一查,哎,果然,SP扣你的钱,是发生在你主动发了一个短信给SP的端口后的,但这个短信查不到具体内容。于是1860也帮不了你,一切亏,你自己吃!

这个阶段,黑心的SP(像新浪这么大的)一个季度至少能营利5000万左右的美元!


第三阶段:保
————————————————————
到去年中旬开始, SP的骗行为已经又被用户大量投诉发现,消协又给移动巨大的压力,称移动再不处理,法院上见!

移动怕了,马上召来卓望公司和华为公司,对现有短信平台进行大升级,新平台的名字叫做MISC,大家可用这个关键字上GOOGLE去查,看是什么东东。原则上,从技术层面,把上面的抢和骗的行为BUG都堵了,用户的手机安全性比之前空前提高。由于是技术性的,SP在这方面没有空子可钻。这个平台于去年九月调试成功,并于2004年12月,全部经营全国范围内的大型SP全部先上这个平台,地级的SP在2005年5月之前也全部要更新这个平台。
SP内部已经达成共识:暴富时代基本过去,现在是保富时代。

因为MISC平台有个规定,原来平台的用户数据必须导入到MISC平台去,旧平台作费。怎么导呢?就是说,在更新这个平台的前一个月,SP进行过扣费成功的用户,才能带进这个平台。龙族各位手机有没有带进去我不知道。反正带进去了,个个月还是扣钱,但已经不是SP扣,是移动扣了。

而由于骗和抢都不再可能,因此SP争夺新用户的机会已经很小。他们的目的,是要保住这一大批被他们成功骗抢了N久的呆用户,这些用户对话费不敏感,是SP未来短信的主要收入。

他们会不断地给这些老用户发短信,因为MISC平台有个规则:只要经常性给这些用户发短信,并每个月按时扣费,那么就能一直扣下去,SP的收入得以保持。

这个阶段,大家一定要注意:现在只要一退订SP的任何服务,SP就拿你没办法了,他也无法再给你发任何信息!必须要你主动发明确的订购指令短信到指定的端口,并且MISC平台会发出第二次收费确认。

SP当然最不想的就是你发出退订指令了!这个阶段还不退,更待何时???

我给大家的专业建议:

1、在未来一个月30天内,每天打1860(联通的是另一个号),转人工服务,然后问小姐,我这个月有没有梦网费用被扣?

2、如果有,问他是哪家SP,你记下来,并记下这家SP的客服电话,然后不要单独跟SP联系,一定要在1860面前给这个SP留下恶性投诉记录,它就死定了!比如,你说你没有订制过这家公司的任何产品(前提是你的确没订过),为何扣钱?小姐会说你联系一下他们问问怎么回事吧?

记住:这是1860和SP勾结了的说法,千万不要被踢皮球!坚决的说:不!扣我钱是你们移动公司,不是什么SP,你们跟SP什么关系我不管,反正谁扣我钱我找谁!现在你们移***扣了我10元,我要求赔偿100倍! 移动的客服小姐一定会很客气对你说:先生,按我们的规定,就算是乱收费,也最多赔两倍。 你不要管她,你说:这是你们的规定,不是法律,我没有遵守的必要,就这件事,久是索赔一百倍,即一千元,不管是你们赔给我也好,是什么SP赔给我也好,你给我解决它,我要求在24小时内得到答案,如果没有,我将发律师函到你们的客服中心。

说完这句话,不要听小姐甜美的声音任何的解说,马上挂机。 根据我的经验,移动的小姐一定会把这个皮球踢给SP,然后SP会给你打电话,你同样说赔100倍一分不少,24小时不给答复,马上发律师函。这时SP可能抵赖,说他们查了一下,是你主动发了短信订阅的,你说,我就算发过,也没有发这个产品的订阅指令,不信我到移动要求他们查原始数据! 一提到查原始数据,任何SP都会胆战心寒! 根据我的经验,他们绝不敢因为你把事闹大,这一千元就肯定到手了!同时要求退订服务。

这件事的关键:一定要硬!不要因为SP和1860的小姐态度好,你就软,一软就办不成了!

3、如果1860说今天为止还没有梦网费用,你不要就此放心睡大觉,要坚持30天内天天查,因为SP什么时间给你下话费数据包进行扣费,是谁也不知道的,你今天查没有,明天查可能就有了。坚持30天没有,才算安全!


四、反击的绝招!!!!
—————————————————————————-
————
告诉你他们的客户服务中心的运作规则,其中我们有可利用来反击的武器。

1860是移动的客户服务中心,包括受理投诉;SP公司自己也有客服中心。这两个客服中心一般有一个默契的勾结:1860只要接到关于SP的投诉,她们会尽可能把你引到SP的客服中心去解决,这样一来1860她们落得轻松,二来这也是SP的需要,因为SP把投诉接过来,相当于跟用户私了,从而不影响他的绝大部份用户。但是1860毕竟是国企公司的一个部门,再怎么勾结,还是有一些上头的硬性规定要做。比如,移动1860接到一个投诉,一定要记录在案,并要确定最终解决,不管她们把皮球踢给SP还是自己处理,都必须有一个解决的确认。国家信息产业部规定:如果用户产生二次投诉同一个问题,且态度极为强硬的,定义为“恶性投诉”,这种投诉是1860和SP都怕的。但再怕,1860必须解决,这就是我们可利用的武器!

也就是说,你一定要造成恶性投诉!方法如下:

1)1860接到有关乱扣费的投诉后,会把SP的电话给你,要你和SP接洽,你一定要拒绝!且强硬地要求1860在一定的时间内给于答复。

2)就算你没有联系SP,但经过你这样强硬的投诉,1860也绝对会把这事抛给SP,SP会主动给你打电话。你的态度一样强硬,且不给一点的缓和余地。对奸商宽容,就是对自己残忍!

3)对1860和SP的客服,你都坚持一点:如果不在规定时间内给我想要的答复,你马上会让你的律师发出律师函(哪怕你没有律师,也要这样说),一旦涉及到法律解决,1860和SP都会十会害怕。因为不对的是他们,打这个官司,他们只有败,没有胜的机会。

4)除了律师,你还要搬出媒体,说你同时会有法律和媒体的力量来维权。这种事,他们最怕爆光。

5)两个客服中心的小姐对你的态度一定都是非常好的,因为这是上头的硬性要求。但你不能吃这种糖衣炮弹,你一旦有和缓的语气,这事就不好办了.

2006年01月26日

为了适应公司不断发展壮大的需要,我们终于要准备使用Team Foundation Server了。原来的代码控制程序是在老板笔记本上面运行的VSS,经常老板出去开会,我们几个人之间就没有办法更新了。除此之外,VSS还不时地出一些莫名其妙的bug,比如说遇到过本地显示已经更新完毕,但是服务器上面还是旧版本,或者本地脱机状态时签出,但是连上服务器之后便成了签入状态(实际上本地的代码还是新代码,再次签出也不会覆盖)。其实最重要的问题还是性能太糟糕了,整个项目更新一遍需要5~10分钟,基本上每次更新的时候只好聊聊天上上网,极大地影响了工作效率。好,二话不说,赶紧装TFS。

虽然以前我用过TFS,但毕竟没装过。虽然我知道装起来很麻烦,但是没有想到居然这么麻烦。为了装TFS,我和老板折腾了两天半的时间。可能还是Beta的缘故吧,除非机器很干净,操作系统是英文版的,一路安装都是默认、默认、默认……否则你就等着吧,肯定给你弄点莫名其妙的事情出来。一路装来我是抓狂了好几次,如果有人要装TFS,也遇到了抓狂的事情,不妨参考一下我的安装历程。我在这里主要是要将安装TFS beta3 Refresh时遇到的问题记录下来,以供参考。

先说说这次安装机器的配置:
硬件
P4-2.4 HT
512MB (现升级为1.5G)
80GB * 2 (Raid 0)
软件
Win2k3 Server

作者补充:先说明白了,这次安装是在一台不干净的机器上装:已经有一个默认实例的SqlServer2000,以及使用该数据库的一个非默认站点(但是IP地址为“全部未分配”,Port80)。别问为什么,就是非要在这台机器上面装不可,咱这里就是有这要求,要是你用干净机器装就别看了,下面遇到的问题大部分只有非干净机器才会遇到。此外,我上来装的时候老板已经为了装TFS而装了一个非默认实例的SqlServer2005,以及一个SharePointPortal2003,没错,是Portal。

其他的不是关键的就不说了。正式装TFS之前,还要安装几个大头:IIS/Active Directory/Windows SharePoint Service 2.0 SP2/SQL Server 2k5/.NET FX 2.0。好吧一个个来,不过如果你还没有安装Active Directory,那么请你不要先装这个,因为装了域控会出现很多意想不到的安全问题(这种类型的问题后面会有所描述)。此外,需要安装Active Directory只是凭籍我的记忆,实际上在Beta3的安装帮助上面看到这么一段话:


Active Directory Domain Requirement
To set up and configure Team Foundation Server in dual-server deployment, you must use computers that are joined to an Active Directory domain. For single-server deployment, you can use a computer that is a member of a workgroup, or one that is joined to an Active Directory domain. When using computers that are joined to an Active Directory domain, Team Foundation Server requires an Active Directory domain that does not contain Windows NT 4.0 domain controllers. Use the following table to determine if Team Foundation Server supports your Active Directory domain functional level:

所以我不太清楚现在的版本是否可以不用安装在加入域的机器上面,但是按照以前的使用经验,似乎如果不加入域,那么就只能够在装有TFS的机器上面运行,在其他机器上面是没有办法建立项目的(似乎,不敢确定)。为了避免浪费不必要的时间,所以我选择装上域控。当然了,目前没有那么多台机器用来搞试验,而且目前项目规模也没有大到需要多台服务器的地步,因此我的安装是将AD、SQL2k5、WSS、TFS都装在一台机器上面了。如果你打算将TFS的数据层和应用层安装在不同的服务器上面,那么按照上面那一段话的解释,就必须装在连接域控的机器上面了。此外,我在这里提供一个别人的多服务器安装提示:
http://briantest.cnblogs.com/archive/2005/09/30/247416.aspx

那么怎么个安装的顺序可能比较合适呢?我觉得应该是 IIS->.NET FX 2.0->SQL SERVER 2k5->Windows SharePoint Service 2.0 SP2->Active Directory->Team Foundation Server。其中比较容易出问题的地方是SQL Server 2k5和Windows SharePoint Service 2.0 SP2。

首先说一下 SQL SERVER 2k5,其实问题很简单,也很容易解决,只是通常大家不知道这个问题才会踩地雷。TFS有一个已知的缺陷,就是必须使用SQL SERVER 2k5,而且SQL SERVER 2k5必须安装成默认的实例——你给他起任何名字都不认帐!如果你不凑巧,机器上面装了SQL SERVER 2000,并且很幸运的装成了默认实例,那不用想都知道,你装Sql Server 2k5的时候肯定会起一个名字。这么一来,到了最后你才发现无法安装,只好卸了重装,甚至你还会因此遇到域控严格的安全策略限制的问题。简单一句话:Sql Server 2k5要安装默认的实例。(补充一句:记得打开Agent服务。)

接下来说该死的Windows SharePoint Service 2.0 SP2的问题。首先,TFS要求有英文版的Template Language Pack(好奇怪的要求啊),所以你有两个选择:1、装英文版的;2、装中文版+Language pack(英文版)。如果你选择2,那么还有两点需要注意:1、下载英文版Language Pack的页面是英文的,你要选择了中文的那就等于白搭;2、Language  pack SP1可以直接下载运行,但是SP2很神奇,下载英文版的吧,说语言版本不对,可是又偏偏没有中文版的下载。不过倒是可以到WindowsUpdate去打上Language Pack SP2的补丁……。

前面这个还只是小问题,如果你的服务器上面已经在跑一些Web应用的话,那很可能你就会栽跟头!至少我在装的时候就遇到这样的问题了:因为我之前安装Sql Server 2k5的时候不是默认实例,结果无法安装下去,导致我在安装Windows SharePoint Service 2.0的时候不敢使用除了默认以外的其他选项。其中有一个选项是SharePoint Service要安装在哪个站点上面,因为我这里已经在跑一个网站xxx.yyy.com了,而默认的站点还没有被使用,因此我毫不犹豫地就选择了“默认”。这一下可就没完没了了,整个安装过程80%的时间都耗在这个上面了。SharePoint  Service的安装太搞笑了,既然能够列出来“默认”和“xxx.yyy.com”这两个站点,那么我选择安装到“默认”那就是安装到“默认站点”吧?不是!安装完之后才发现它竟然装到xxx.yyy.com上面去了,而且还捣毁了里面的诸多设施,例如web.config。它要自作主张装到别的地方吧,也不瞅瞅里面是不是空的,安装过程还没有任何警告,三下五除二就给“装好”了。要知道xxx.yyy.com是我们内部测试用的站点,这么一整,那头正在开会做演示的就只好放假了(幸好是自己内部开个小会)。

如果你现在正准备装SharePoint Service 2.0,那么请注意检查下列几点:
1、你是否已经装有某个站点,例如xxx.yyy.com。(如果没有,请跳至5。)
2、xxx.yyy.com的站点属性当中的“网站->IP地址”当中是否有一项是“全部未分配”?(如果没有,请不要选择“默认”,实践证明,你将会无法正确安装,并请跳至5。)
3、你是否打算在安装的时候选择“默认”,以避免日后可能会遇到的莫名其妙的问题?(如果不是,请跳至5。)
4、请检查所有存在的站点,是否有多个站点的地址是属于“全部未分配”?(如果是,建议只保留一个你想要安装的目标站点为“全部未分配”。虽然目前我的观测发现,只有Port为80、IP为“全部未分配”的站点才是WSS安装选项里面的“默认”,但是这一点我无法证明和保证。)
5、请检查你打算要安装的站点全空,这对后面安装结果的判断比较有帮助。

如果检查并确保这几点,那么安装应当是比较顺利的。
请注意:
Windows SharePoint Service 2.0所有的安装过程都会将“默认(站点)”等同IP地址为“全部未分配(Port:80)”对待,千万不要以为安装之前将站点暂时改成别的IP,装好了之后再改回来就完事大吉了。如果你日后打SP(包括Language Pack的SP),都很有可能会在你的xxx.yyy.com上面重新安装。破坏了你的站点不说,还可能会搞坏TFS的数据库!此外,也不要自作聪明暂停xxx.yyy.com,这样不解决问题,WSS的安装会同样自作聪明的把xxx.yyy.com给启动起来,然后装在上面。

装完了WSS,你最好不要急着装AD,赶紧先用一下试试。因为你可能会遇到一些奇怪的问题,比如我就是装完之后没有办法自动生成默认站点,然后需要我自己手动到管理平台上面生成一个,而且站点明明生成好了,却死活不让访问。更可恨的是,web.config明明修改了customErrors mode="Off",仍然没有出现详细信息。如果你也遇到这样的问题,你可以检查一下_layout所在的实际目录的web.config,先把它的customErrors mode="On" 设置为Off。我估计你再次刷新这个页面就会看到类似NT Authority\Network Service读取或者写入某个文件的权限被拒绝这样的提示了。假如你遇到的是这样的问题,请检查C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files这个目录的安全性设置,看看里面有没有加上NT Authority\Network Service。如果没有,请加上这个特殊帐号(如果你手动输入不认,那么请用搜索-〉高级-〉立即搜索找到这个帐号)。你需要给它加上的权限是:列出/执行、读取/列表、写入/新建、附加/新建目录。如果你的系统里面干脆没有.NET FX 1.1,那么请你看看相应版本的对应目录(出错提示信息里面就应该有啦)。

如果你曾经在安装完WSS和AD之后,出现了某些问题(比如我上面遇到的站点安装错误问题),把WSS卸载了重装,那么你就会出现了第一次所没有遇到的问题——无法为站点选择一个模版,也就是说装到最后一个选择模板的步骤(访问类似 localhost/sites/mysitename/_layouts/1033/templatepick.aspx 的网址),可能会发现ASP.NET出错了,但是访问比如说根目录上的文件则是正常的。那么你可以检查一下_layout所在的实际目录的安全性设置,看看里面有没有加上NT Authority\Network Service。如果没有,请加上这个特殊帐号(请参考上一段的设置),不过这里我建议把这个帐号加在_layout所在的目录上面,而不是_layout目录上面,因为其他目录估计也有类似的问题。我估计这是由于AD的安全性提高所引起的。

作者补充:
请注意!不要安装SharePoint Portal 2003,装了这个东西TFS的安装就无法开始,因为它会告诉你TFS和SPP2003不兼容!我卸载WSS有一小部分原因跟这个有关。

如果你很不幸,和我一样,不仅仅卸载了WSS,还卸载了Sql Server 2k5重装,那么你可能还会遇到两个小问题。第一个和上面WSS的_layout目录比较类似,你要看看Reporting Service 所使用的目录有没有Network Service的权限设置。另外一个小问题是,重装Sql Server 2k5时,可能会无法顺利安装其中的Reporting Service,这可能是由于卸载WSS不完整造成的。如果遇到这个问题请不要担心,装完了WSS再回过头来装Reporting Service就好了。

在经历完这么多的劫难之后,我这里的前置条件所要求的软件已经全部正确安装完毕。接下来装TFS就没有什么惊险的地方了,不过装完之后的实验还是遇到3个不大不小的问题,折腾了我半天的时间,这几个问题都是无法创建一个新的项目。第一次遇到的问题实在新项目建立滚动条到了大约80%的时候出错,提示说:

Time: 2006-1-21 0:42:09
Module: Engine
Event Description: TF30162: Task "SharePointPortal" from Group "Portal" failed
Exception Type: Microsoft.TeamFoundation.Client.PcwException
Exception Message: Unable to connect to the Windows SharePoint Services at MYNETDEV
Exception Details: The Project Creation Wizard was not able to connect to the Windows SharePoint Services
at MYNETDEV. The reason for the failed connection cannot be determined at this time.
Because the connection failed, the wizard was not able to complete creating
the Windows SharePoint Services site.
Stack Trace:
   at Microsoft.VisualStudio.TeamFoundation.WssSiteCreator.Execute(ProjectCreationContext context, XmlNode taskXml)
   at Microsoft.VisualStudio.TeamFoundation.ProjectCreationEngine.TaskExecutor.PerformTask(IProjectComponentCreator componentCreator, ProjectCreationContext context, XmlNode taskXml)
   at Microsoft.VisualStudio.TeamFoundation.ProjectCreationEngine.RunTask(Object taskObj)
 –   Inner Exception   –

后面的Inner Exception就不罗列了,太长。这个问题到Google上面一搜,就会发现这个问题是TFS上面注册的Windows SharePoint  Service的Admin站点端口号错误,即,它跟实际上WSS管理站点的端口号不一致造成的。你想,TFS想要连到WSS管理站点却给错了Port号,那么出这个错误确实很正常。大家可以使用下列地址看看是否属于这个问题:
http://localhost:8080/services/v1.0/Registration.asmx?op=GetRegistrationEntries
在这里点击“Invoke”(不要在文本框里面填东西),然后查找WssAdmin,就应该看到类似这样的信息:

<RegistrationEntry>
   <Type>Wss</Type>
-  <ServiceInterfaces>
-   <ServiceInterface>
     <Name>WssAdminService</Name>
     <Url>http://MYDOMAIN:17012/_vti_adm/admin.asmx</Url>
 </ServiceInterface>
 …
</ServiceInterfaces>
<RegistrationEntry>
  
很明显,注册的Port是17012,而我也不知道怎么去修改TFS的注册信息,只好修改WSS管理站点的Port号了。这个问题是两方面引起的,一个是每次WSS安装的时候,都会以某种方式生成跟上一次不一样的Port号。而同时不知道为什么,TFS只得到了第一次WSS安装时的管理站点Port号。结果就成这样了。如果你的WSS没有装了又卸,我估计是不会出这样的问题的。

在解决完之后,我再次新建一个项目,结果出现另外一个错误,Log如下:

2006-1-24 10:11:14 | Module: Engine | Thread: 6 | Running Task "SharePointPortal" from Group "Portal"
—begin Exception entry—
Time: 2006-1-24 10:11:25
Module: Engine
Event Description: TF30162: Task "SharePointPortal" from Group "Portal" failed
Exception Type: Microsoft.TeamFoundation.Client.PcwException
Exception Message: Client found response content type of ‘text/html’, but expected ‘text/xml’.
The request failed with the error message:

<html>
   <head>
      <title>??????????????????
</title>
   </head>
   <body>
      <h1><font face=Verdana color=#ff3300>??????????????????????????????
</font></h1>
      <p>
      <font face=Verdana>
        ??????????????? Web ????????????????????? Web ??????????????????????????????????????? Web ??????????????????????????????????????????????????????
</p>
   <p>
   <b>?????????????????????:
</b> ?????????????????????????????????????????????????????? Web ?????????????????????????????????????????????????????????????????????????????????????????????????????????
 </p>
   </body>
</html>
–.
Stack Trace:
   at Microsoft.VisualStudio.TeamFoundation.WssSiteCreator.Execute(ProjectCreationContext context, XmlNode taskXml)
   at Microsoft.VisualStudio.TeamFoundation.ProjectCreationEngine.TaskExecutor.PerformTask(IProjectComponentCreator componentCreator, ProjectCreationContext context, XmlNode taskXml)
   at Microsoft.VisualStudio.TeamFoundation.ProjectCreationEngine.RunTask(Object taskObj)
 –   Inner Exception   –

天地良心,上面那些???不是我搞的什么花样,这确实就是TFS给我的Log——他的Log竟然不支持中文。我看到那么一大堆的问号,就在哪里后悔这个操作系统为什么不是英文的,.NET FX为什么不是英文的。后悔也没有用,只能动手去解决。上网搜索了一大通,都没有找到相关的解决方案,倒是看到有几个人遇到了和我相似的问题。但是我在网上倒是看到有一个帖子说,某个CLSID为{BA….-…-…-…..}的DCOM服务无法启动,可能会导致类似的另外一个问题(其实错误信息完全不一样,只不过他原文就是这么说的)。然后我查看事件查看器,发现里面确实有一大堆的DCOM警告。顺着搜索,发现是netman这个dcom组件的安全性设置里面没有加上NT AUTHORITY/Network Service。我给这个帐号加上了“激活”和“本地运行”两个权限之后,却发现还是出现同样的错误。不过值得安慰的是,事件查看器里面已经不再出现这个错误记录了。

可是问题还是没有解决啊!只好用Reflector看TFS到底在搞什么鬼,结果发现他是在访问localhost/_vti_bin/sites.asmx时出错的。我手动浏览了一下这个“页面”,结果发现果然无法运行!提示是“Web应用程序无法运行”云云,但是我却可以访问localhost/report里面的页面。这个问题我是整来整去都没整出问题所在,后来我认真搜索了一下事件查看器,发现有一个错误是.NET FX 1.1发出的,说同一个Web应用无法同时运行两个版本的.NET FX。于是我火了,只好死马当活马医了——把localhost/_vti_bin这个虚拟目录的ASP.NET调整为使用.NET 2.0,结果竟然可以访问了。我想难道是.NET 1.1的毛病?又把它改回到.NET 1.1,结果更神奇,竟然还是能够访问(语言有点不一样,一个是中文一个是英文,莫名其妙)。心想,好了吧,这回可以了吧,用TFS再次建立新项目,结果更加神奇:竟然一开始就报错!说Reporting Service无法启动之类的信息。仔细想想,会不会还是跟那个无法同时运行两个版本的.NET FX有关呢?于是乎又看了一眼事件查看器,一看就明白了:这回又发现一个几乎相同的记录,只不过这次是.NET 2.0发出的。这下子我彻底火了,干脆直接把默认站点给改成.NET 2.0,结果Reporting Service是好了,但后面又回到刚才那个content type不符合的问题了!浏览sites.asmx也恢复提示“Web应用程序无法运行”,噢,天啊!抓狂了!冷静下来,才发现默认站点下面有好几个虚拟目录,仔细一看才发现这几个地方的ASP.NET版本的设置还不一样!我把每一个都改成.NET 2.0之后,一切都正常了。

事后分析:WSS使用的是.NET 1.1,TFS用的是2.0。localhost包括那些_vti_bin等虚拟目录都是WSS建立的,所以都配置成.NET 1.1,而Report这个虚拟目录是TFS建立的,因此用的是.NET 2.0。他们用的都是同一个应用程序池,所以绝对是不可能同时运行的。从后来出现的各种现象看,TFS会首先访问Report目录下面的Web service,所以.NET 2.0就起来了。而建立新项目的后期需要用Wss建立一个portal,于是需要访问_vti_bin这个虚拟目录,于是需要启动.NET 1.1,但是这个时候应用程序池已经被2.0占领了,结果.NET 1.1就到事件记录上面击鼓鸣冤。而我更改_vti_bin的ASP.NET的设置时,所在的应用程序池就重启了。后来在我改回来用.net 1.1之后,我首先手动访问了_vti_bin里面的东西,应用程序池就被.NET 1.1占领了。这导致了后来建立新项目时Reporting Service访问失败,以及.NET 2.0到事件记录上面击鼓鸣冤。

希望这里写的一大堆文字对安装TFS遇到问题的朋友有所提示和帮助。其实TFS的安装使用可能会遇到的问题会很多很多,大部分我都没有机会见识。所以如果不是上面罗列的问题,要是你问我估计也是白问。我的建议是多用Google搜一下,尤其是论坛搜索,应该会有所帮助的(要搜英文哦)。

在2005年底微软公司正式发布了C# 2.0,与C# 1.x相比,新版本增加了很多新特性,其中最重要的是对泛型的支持。通过泛型,我们可以定义类型安全的数据结构,而无需使用实际的数据类型。这能显著提高性能并得到更高质量的代码。泛型并不是什么新鲜的东西,他在功能上类似于C++的模板,模板多年前就已存在C++上了,并且在C++上有大量成熟应用。

本文讨论泛型使用的一般问题,比如为什么要使用泛型、泛型的编写方法、泛型中数据类型的约束、泛型中静态成员使用要注意的问题、泛型中方法重载的问、泛型方法等,通过这些使我们可以大致了解泛型并掌握泛型的一般应用,编写出更简单、通用、高效的应用系统。

 

什么是泛型
我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。读完本篇文章,你会对泛型有更深的了解。

 

为什么要使用泛型
为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但功能是实现一个栈,这个栈只能处理int数据类型:

public class Stack

    {

        private int[] m_item;

        public int Pop(){…}

        public void Push(int item){…}

        public Stack(int i)

        {

            this.m_item = new int[i];

        }

}

上面代码运行的很好,但是,当我们需要一个栈来保存string类型时,该怎么办呢?很多人都会想到把上面的代码复制一份,把int改成string不就行了。当然,这样做本身是没有任何问题的,但一个优秀的程序是不会这样做的,因为他想到若以后再需要long、Node类型的栈该怎样做呢?还要再复制吗?优秀的程序员会想到用一个通用的数据类型object来实现这个栈:

public class Stack

    {

        private object[] m_item;

        public object Pop(){…}

        public void Push(object item){…}

        public Stack(int i)

        {

            this.m_item = new[i];

        }

      

    }

这个栈写的不错,他非常灵活,可以接收任何数据类型,可以说是一劳永逸。但全面地讲,也不是没有缺陷的,主要表现在:

当Stack处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。
在处理引用类型时,虽然没有装箱和折箱操作,但将用到数据类型的强制转换操作,增加处理器的负担。
在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例):
Node1 x = new Node1();

            stack.Push(x);

         Node2 y = (Node2)stack.Pop();

上面的代码在编译时是完全没问题的,但由于Push了一个Node1类型的数据,但在Pop时却要求转换为Node2类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查。

 

针对object类型栈的问题,我们引入泛型,他可以优雅地解决这些问题。泛型用用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。

 

使用泛型
下面是用泛型来重写上面的栈,用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替。让我们来看看泛型的威力:

public class Stack<T>

    {

        private T[] m_item;

        public T Pop(){…}

        public void Push(T item){…}

        public Stack(int i)

        {

            this.m_item = new T[i];

        }

}

类的写法不变,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安全的。这个类的调用方法:

//实例化只能保存int类型的类

Stack<int> a = new Stack<int>(100);

      a.Push(10);

      a.Push("8888"); //这一行编译不通过,因为类a只接收int类型的数据

      int x = a.Pop();

 

//实例化只能保存string类型的类

Stack<string> b = new Stack<string>(100);

b.Push(10);    //这一行编译不通过,因为类b只接收string类型的数据

      b.Push("8888");

  string y = b.Pop();

 

这个类和object实现的类有截然不同的区别:

1.       他是类型安全的。实例化了int类型的栈,就不能处理string类型的数据,其他数据类型也一样。

2.       无需装箱和折箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和折箱。

3.       无需类型转换。

 

泛型类实例化的理论
C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以不同的封闭类的本地代码是不一样的。按照这个原理,我们可以这样认为:

泛型类的不同的封闭类是分别不同的数据类型。

例:Stack<int>和Stack<string>是两个完全没有任何关系的类,你可以把他看成类A和类B,这个解释对泛型类的静态成员的理解有很大帮助。

 

泛型类中数据类型的约束
程序员在编写泛型类时,总是会对通用数据类型T进行有意或无意地有假想,也就是说这个T一般来说是不能适应所有类型,但怎样限制调用者传入的数据类型呢?这就需要对传入的数据类型进行约束,约束的方式是指定T的祖先,即继承的接口或类。因为C#的单根继承性,所以约束可以有多个接口,但最多只能有一个类,并且类必须在接口之前。这时就用到了C#2.0的新增关键字:

public class Node<T, V> where T : Stack, IComparable

        where V: Stack

    {…}

以上的泛型类的约束表明,T必须是从Stack和IComparable继承,V必须是Stack或从Stack继承,否则将无法通过编译器的类型检查,编译失败。

通用类型T没有特指,但因为C#中所有的类都是从object继承来,所以他在类Node的编写中只能调用object类的方法,这给程序的编写造成了困难。比如你的类设计只需要支持两种数据类型int和string,并且在类中需要对T类型的变量比较大小,但这些却无法实现,因为object是没有比较大小的方法的。 了解决这个问题,只需对T进行IComparable约束,这时在类Node里就可以对T的实例执行CompareTo方法了。这个问题可以扩展到其他用户自定义的数据类型。

如果在类Node里需要对T重新进行实例化该怎么办呢?因为类Node中不知道类T到底有哪些构造函数。为了解决这个问题,需要用到new约束:

public class Node<T, V> where T : Stack, new()

        where V: IComparable

需要注意的是,new约束只能是无参数的,所以也要求相应的类Stack必须有一个无参构造函数,否则编译失败。

C#中数据类型有两大类:引用类型和值类型。引用类型如所有的类,值类型一般是语言的最基本类型,如int, long, struct等,在泛型的约束中,我们也可以大范围地限制类型T必须是引用类型或必须是值类型,分别对应的关键字是class和struct:

public class Node<T, V> where T : class

        where V: struct

 

泛型方法
泛型不仅能作用在类上,也可单独用在类的方法上,他可根据方法参数的类型自动适应各种参数,这样的方法叫泛型方法。看下面的类:

public class Stack2

    {

        public void Push<T>(Stack<T> s, params T[] p)

        {

            foreach (T t in p)

            {

                s.Push(t);

            }

        }

}

原来的类Stack一次只能Push一个数据,这个类Stack2扩展了Stack的功能(当然也可以直接写在Stack中),他可以一次把多个数据压入Stack中。其中Push是一个泛型方法,这个方法的调用示例如下:

Stack<int> x = new Stack<int>(100);

    Stack2 x2 = new Stack2();

    x2.Push(x, 1, 2, 3, 4, 6);

    string s = "";

    for (int i = 0; i < 5; i++)

    {

        s += x.Pop().ToString();

    }    //至此,s的值为64321

   

 

泛型中的静态成员变量
在C#1.x中,我们知道类的静态成员变量在不同的类实例间是共享的,并且他是通过类名访问的。C#2.0中由于引进了泛型,导致静态成员变量的机制出现了一些变化:静态成员变量在相同封闭类间共享,不同的封闭类间不共享。

这也非常容易理解,因为不同的封闭类虽然有相同的类名称,但由于分别传入了不同的数据类型,他们是完全不同的类,比如:

Stack<int> a = new Stack<int>();

Stack<int> b = new Stack<int>();

Stack<long> c = new Stack<long>();

类实例a和b是同一类型,他们之间共享静态成员变量,但类实例c却是和a、b完全不同的类型,所以不能和a、b共享静态成员变量。

泛型中的静态构造函数
静态构造函数的规则:只能有一个,且不能有参数,他只能被.NET运行时自动调用,而不能人工调用。

泛型中的静态构造函数的原理和非泛型类是一样的,只需把泛型中的不同的封闭类理解为不同的类即可。以下两种情况可激发静态的构造函数:

1.       特定的封闭类第一次被实例化。

2.       特定封闭类中任一静态成员变量被调用。

 

泛型类中的方法重载
方法的重载在.Net Framework中被大量应用,他要求重载具有不同的签名。在泛型类中,由于通用类型T在类编写时并不确定,所以在重载时有些注意事项,这些事项我们通过以下的例子说明:

public class Node<T, V>

    {

        public T add(T a, V b)          //第一个add

        {

            return a;

        }

        public T add(V a, T b)          //第二个add

        {

            return b;

        }

        public int add(int a, int b)    //第三个add

        {

            return a + b;

        }

}

上面的类很明显,如果T和V都传入int的话,三个add方法将具有同样的签名,但这个类仍然能通过编译,是否会引起调用混淆将在这个类实例化和调用add方法时判断。请看下面调用代码:

Node<int, int> node = new Node<int, int>();

    object x = node.add(2, 11);

这个Node的实例化引起了三个add具有同样的签名,但却能调用成功,因为他优先匹配了第三个add。但如果删除了第三个add,上面的调用代码则无法编译通过,提示方法产生的混淆,因为运行时无法在第一个add和第二个add之间选择。

Node<string, int> node = new Node<string, int>();

        object x = node.add(2, "11");

   这两行调用代码可正确编译,因为传入的string和int,使三个add具有不同的签名,当然能找到唯一匹配的add方法。

由以上示例可知,C#的泛型是在实例的方法被调用时检查重载是否产生混淆,而不是在泛型类本身编译时检查。同时还得出一个重要原则:

当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。

 

泛型类的方法重写
方法重写(override)的主要问题是方法签名的识别规则,在这一点上他与方法重载一样,请参考泛型类的方法重载。

 

泛型的使用范围
本文主要是在类中讲述泛型,实际上,泛型还可以用在类方法、接口、结构(struct)、委托等上面使用,使用方法大致相同,就不再讲述。

小结
C# 泛型是开发工具库中的一个无价之宝。它们可以提高性能、类型安全和质量,减少重复性的编程任务,简化总体编程模型,而这一切都是通过优雅的、可读性强的语法完成的。尽管 C# 泛型的根基是 C++ 模板,但 C# 通过提供编译时安全和支持将泛型提高到了一个新水平。C# 利用了两阶段编译、元数据以及诸如约束和一般方法之类的创新性的概念。毫无疑问,C# 的将来版本将继续发展泛型,以便添加新的功能,并且将泛型扩展到诸如数据访问或本地化之类的其他 .NET Framework 领域.

转自:
http://www.cnblogs.com/81/archive/2006/01/25/323189.html?Pending=true#Post

2006年01月25日

关于datagrid的打印
http://www.chinaaspx.com/article/csharp/295.htm

C#中为DataGrid添加下拉列表框
http://www.cnblogs.com/icesnaker/archive/2004/09/21/45015.aspx

DataGrid也玩分页
http://dev.csdn.net/article/32/32339.shtm

DataGrid的正反双向排序
http://dev.csdn.net/develop/article/26/26817.shtm

DataGrid删除确认及Item颜色交替
http://dev.csdn.net/develop/article/26/26768.shtm

DataGrid常见解决方案(三)–在DataGrid中选择,确认,删除多行复选框列表
http://dev.csdn.net/develop/article/26/26613.shtm

使用DataGrid动态绑定DropDownList
http://dev.csdn.net/develop/article/26/26590.shtm

DataGrid在分页状态下删除纪录的问题
http://dev.csdn.net/develop/article/26/26589.shtm

怎样使用DataGrid控件
http://dev.csdn.net/develop/article/26/26459.shtm

DataGrid Web控件深度历险(3) part1
http://dev.csdn.net/develop/article/26/26249.shtm

DataGrid Web控件深度历险(3) part2
http://dev.csdn.net/develop/article/26/26250.shtm

DataGrid Web控件深度历险(3) part3
http://dev.csdn.net/develop/article/26/26403.shtm

datagrid分页《非控件版》
http://dev.csdn.net/develop/article/26/26002.shtm

datagrid分页问题(前后跳页)《控件版》
http://dev.csdn.net/develop/article/26/26001.shtm

DataGrid Web控件深度历险(2) Part1
http://dev.csdn.net/develop/article/25/25948.shtm

DataGrid Web控件深度历险(1)
http://dev.csdn.net/develop/article/25/25816.shtm

DataGrid Web控件深度历险(2) Part2
http://dev.csdn.net/develop/article/25/25949.shtm

如何在DataGrid里面产生滚动条而不滚动题头
http://dev.csdn.net/develop/article/25/25538.shtm

使用在.net 框架上的DataGrid數據分頁控件
http://dev.csdn.net/develop/article/25/25474.shtm

将某一目录下的所有相同格式的 XML文件绑定到不同的DataGrid
http://dev.csdn.net/develop/article/25/25469.shtm

DataGrid连接Access的快速分页法(5)——实现快速分页
http://dev.csdn.net/develop/article/25/25294.shtm

DataGrid连接Access的快速分页法(4)——动态生成SQL语句
http://dev.csdn.net/develop/article/25/25293.shtm

DataGrid连接Access的快速分页法(3)——SQL语句的选用(降序)
http://dev.csdn.net/develop/article/25/25291.shtm

DataGrid连接Access的快速分页法(2)——SQL语句的选用(升序)
http://dev.csdn.net/develop/article/25/25290.shtm

DataGrid连接Access的快速分页法(1)——需求与现状
http://dev.csdn.net/develop/article/25/25288.shtm

显示DataGrid序号的一个适用的方法
http://dev.csdn.net/develop/article/25/25113.shtm

常见 Datagrid 错误(other)
http://dev.csdn.net/develop/article/24/24460.shtm

XP 风格的可拖动列、可排序、可改变宽度的DataGrid的例子
http://dev.csdn.net/develop/article/24/24441.shtm

利用radio实现Datagrid的单选
http://dev.csdn.net/develop/article/24/24295.shtm

重画系列:DataGridColumnStyle之测试代码
http://dev.csdn.net/develop/article/24/24191.shtm

将OleDbDataAdapter绑定到Winform下的DataGrid
http://dev.csdn.net/develop/article/24/24148.shtm

去除Asp:DataGrid中无用ViewState的方法(2)
http://dev.csdn.net/develop/article/23/23671.shtm

去除Asp:DataGrid中无用ViewState的方法(1)
http://dev.csdn.net/develop/article/23/23670.shtm

datagrid保存时无法提交更新的问题
http://dev.csdn.net/develop/article/23/23376.shtm

将DBF,XLS,XML,MDB文件导入C#DataGrid的方法
http://dev.csdn.net/develop/article/23/23036.shtm

动态创建DataGrid的模版列
http://dev.csdn.net/develop/article/22/22942.shtm

DataTable中数据记录的统计
http://dev.csdn.net/develop/article/22/22710.shtm

(ASP.NET)用动态属性和DataView实现DataGrid的双向排序
http://dev.csdn.net/develop/article/22/22513.shtm

如何同步滚动两个相同的DataGrid
http://dev.csdn.net/develop/article/22/22438.shtm

asp.net中DataGrid性能测试
http://dev.csdn.net/develop/article/22/22405.shtm

让Asp.NET的DataGrid可排序、可选择、可分页
http://dev.csdn.net/develop/article/22/22297.shtm

DataGrid传统分页方式
http://dev.csdn.net/develop/article/21/21997.shtm

在后代码里创建DataGrid控件
http://dev.csdn.net/develop/article/21/21934.shtm

实现类似Windows资源管理器的DataGrid
http://dev.csdn.net/develop/article/21/21933.shtm

ASP.net中的Datagrid自定义分页功能
http://dev.csdn.net/develop/article/21/21875.shtm

在Pocket PC应用程序中使用DataGrid控件
http://dev.csdn.net/develop/article/21/21844.shtm

创建可拖动列的DataGrid(2)
http://dev.csdn.net/develop/article/21/21594.shtm

创建可拖动列的DataGrid
http://dev.csdn.net/develop/article/21/21593.shtm

DataGrid和CheckBox的混合使用
http://dev.csdn.net/develop/article/21/21585.shtm

利用Session纪录datagrid模板列中CheckBox的状态
http://dev.csdn.net/develop/article/21/21294.shtm

DataGrid模板列中TextBox的焦点相应键盘事件
http://dev.csdn.net/develop/article/21/21290.shtm

给DataGrid添加确定删除的功能
http://dev.csdn.net/develop/article/20/20892.shtm

如何给DataGrid添加自动增长列
http://dev.csdn.net/develop/article/20/20887.shtm

如何利用RadioButtonList实现datagrid列的单选
http://dev.csdn.net/develop/article/20/20789.shtm

锦上添花DataGrid!
http://dev.csdn.net/develop/article/20/20770.shtm

ASP.Net WebMatrix中Datagrid使用模板列对数据显示进行排版
http://dev.csdn.net/develop/article/20/20576.shtm

格式化 DataGrid 输出
http://dev.csdn.net/develop/article/20/20307.shtm

如何实现单击在DATALIST(DATAGRID)的HEADER加入的CHECKBOX,进行DATALIST(DATAGRID)中的CHECKBOX列全选或全不选
http://dev.csdn.net/develop/article/20/20290.shtm

向datagrid中加横向 纵向的合计 (在datatable中实现,datatable间倒数据)
http://dev.csdn.net/develop/article/20/20221.shtm

基于ADO+Adodc控件+DataGrid控件制作的一个数据库编辑程序(完整原程序)
http://dev.csdn.net/develop/article/19/19600.shtm

如何实现自定义及自动逐页打印DataGrid显示的内容
http://dev.csdn.net/develop/article/19/19353.shtm

合并datagrid中内容相同的单元格
http://dev.csdn.net/develop/article/19/19122.shtm

创建固定表头、表格体滚动的DataGrid
http://dev.csdn.net/develop/article/18/18972.shtm

创建跨多列、多行表头的DataGrid
http://dev.csdn.net/develop/article/18/18971.shtm

在DataGrid中添加一个合计字段
http://dev.csdn.net/develop/article/18/18856.shtm

在DataGrid里添加确认删除的对话框
http://dev.csdn.net/develop/article/18/18838.shtm

为DataGrid添加自动编号功能
http://dev.csdn.net/develop/article/18/18783.shtm

格式化DataGrid的例子【将数据源中的0,1值转换成实际的文字】
http://dev.csdn.net/develop/article/18/18782.shtm

Henry手记—Web Form中的Datagrid的自定义分页
http://dev.csdn.net/develop/article/18/18760.shtm

创建完全可编辑的 DataGrid
http://dev.csdn.net/develop/article/18/18744.shtm

DataGrid控件通用打印类
http://dev.csdn.net/develop/article/18/18711.shtm

为DataGrid添加CheckBox控件
http://dev.csdn.net/develop/article/18/18615.shtm

VB.NET中关于DataGrid颜色的自定义
http://dev.csdn.net/develop/article/18/18512.shtm

在DataGrid快速添加新行
http://dev.csdn.net/develop/article/18/18487.shtm

Henry手记-Datagrid事件响应(二)
http://dev.csdn.net/develop/article/18/18315.shtm

datagrid技巧之一:代码控制选中行的颜色
http://dev.csdn.net/develop/article/17/17852.shtm

在C#里实现DATAGRID的打印预览和打印
http://dev.csdn.net/develop/article/17/17851.shtm

Binding a DataGrid to an ADO Recordset
http://dev.csdn.net/develop/article/17/17850.shtm

Creating DataGrid Templated Columns Dynamically – Part II
http://dev.csdn.net/develop/article/17/17846.shtm

Creating DataGrid Templated Columns Dynamically – Part I
http://dev.csdn.net/develop/article/17/17845.shtm

DataTable,DataView和DataGrid中一些容易混淆的概念
http://dev.csdn.net/develop/article/17/17840.shtm

动态的管理ASP.NET DataGrid数据列
http://dev.csdn.net/develop/article/17/17599.shtm

Henry手记-Datagrid键盘事件响应(二)
http://dev.csdn.net/develop/article/17/17424.shtm

Henry手记—从Datagrid的标题居中说起
http://dev.csdn.net/develop/article/17/17053.shtm

用嵌套的DataGrid实现主从式表的显示
http://dev.csdn.net/develop/article/16/16960.shtm

DataGrid中嵌套使用Repeater
http://dev.csdn.net/develop/article/16/16834.shtm

Henry手记 - Datagrid键盘事件响应(一)
http://dev.csdn.net/develop/article/16/16572.shtm

给datagrid控件建立稳固的双向排序(asp.net)
http://dev.csdn.net/develop/article/16/16563.shtm

asp.net笔记(dataset,datagrid)
http://dev.csdn.net/develop/article/16/16528.shtm

添加一个下拉框到DataGrid
http://dev.csdn.net/develop/article/16/16434.shtm

DataSet导出CSV格式(ASP.NET,C#)
http://dev.csdn.net/develop/article/16/16311.shtm

Henry手记:Datagrid事件响应
http://dev.csdn.net/develop/article/16/16273.shtm

Henry手记:WinForm Datagrid结构剖析(三)使用代码
http://dev.csdn.net/develop/article/16/16088.shtm

Henry手记:WinForm Datagrid结构剖析(三)类代码
http://dev.csdn.net/develop/article/16/16087.shtm

Henry手记:WinForm Datagrid结构剖析(三)
http://dev.csdn.net/develop/article/16/16086.shtm

ADO在vb.net中的使用(与datagrid结合)
http://dev.csdn.net/develop/article/15/15975.shtm

Henry手记:WinForm Datagrid结构剖析(二)程序
http://dev.csdn.net/develop/article/15/15929.shtm

Henry手记:WinForm Datagrid结构剖析(二)
http://dev.csdn.net/develop/article/15/15928.shtm

ASP.NET中的DataGrid的属性
http://dev.csdn.net/develop/article/15/15920.shtm

EnableViewState="false"的DataGrid分页
http://dev.csdn.net/develop/article/15/15913.shtm

Datagrid 链接数据库Access
http://dev.csdn.net/develop/article/15/15861.shtm

DataGrid 链接Access数据库
http://dev.csdn.net/develop/article/15/15860.shtm

Henry手记:WinForm Datagrid结构剖析(一)
http://dev.csdn.net/develop/article/15/15686.shtm

把Excel文件中的数据读入到DataGrid中
http://dev.csdn.net/develop/article/15/15544.shtm

如何创建一个用弹出窗口来查看详细信息的超链接列
http://dev.csdn.net/develop/article/15/15456.shtm

DataGrid使用技巧(四)
http://dev.csdn.net/develop/article/15/15282.shtm

DataGrid使用技巧(三)
http://dev.csdn.net/develop/article/15/15220.shtm

DataGrid使用技巧(二)
http://dev.csdn.net/develop/article/15/15139.shtm

DataGrid使用技巧(一)
http://dev.csdn.net/develop/article/15/15109.shtm

神奇的 DataGrid
http://dev.csdn.net/develop/article/14/14114.shtm

使用c#+(datagrid控件)编辑xml文件
http://dev.csdn.net/develop/article/13/13333.shtm

在DataGrid中创建一个弹出式Details窗口
http://dev.csdn.net/develop/article/12/12170.shtm

在DataGrid中创建一个弹出式Details窗口
http://dev.csdn.net/develop/article/12/12099.shtm

vs.net beta 2中利用DataGrid分页详解
http://dev.csdn.net/develop/article/11/11009.shtm

使用 ASP+ DataGrid 控件来创建主视图/详细资料视图 (2)
http://dev.csdn.net/develop/article/1/1865.shtm

使用 ASP+ DataGrid 控件来创建主视图/详细资料视图
http://dev.csdn.net/develop/article/1/1864.shtm

DataGrid的正反双向排序
http://dev.csdn.net/develop/article/26/26817.shtm
DataGrid删除确认及Item颜色交替
http://dev.csdn.net/develop/article/26/26768.shtm

DataGrid常见解决方案(三)–在DataGrid中选择,确认,删除多行复选框列表
http://dev.csdn.net/develop/article/26/26613.shtm

使用DataGrid动态绑定DropDownList
http://dev.csdn.net/develop/article/26/26590.shtm

DataGrid在分页状态下删除纪录的问题
http://dev.csdn.net/develop/article/26/26589.shtm

怎样使用DataGrid控件
http://dev.csdn.net/develop/article/26/26459.shtm

DataGrid Web控件深度历险(3) part1
http://dev.csdn.net/develop/article/26/26249.shtm

DataGrid Web控件深度历险(3) part2
http://dev.csdn.net/develop/article/26/26250.shtm

DataGrid Web控件深度历险(3) part3
http://dev.csdn.net/develop/article/26/26403.shtm

datagrid分页《非控件版》
http://dev.csdn.net/develop/article/26/26002.shtm

datagrid分页问题(前后跳页)《控件版》
http://dev.csdn.net/develop/article/26/26001.shtm

DataGrid Web控件深度历险(2) Part1
http://dev.csdn.net/develop/article/25/25948.shtm

DataGrid Web控件深度历险(1)
http://dev.csdn.net/develop/article/25/25816.shtm

DataGrid Web控件深度历险(2) Part2
http://dev.csdn.net/develop/article/25/25949.shtm

如何在DataGrid里面产生滚动条而不滚动题头
http://dev.csdn.net/develop/article/25/25538.shtm

使用在.net 框架上的DataGrid數據分頁控件
http://dev.csdn.net/develop/article/25/25474.shtm

将某一目录下的所有相同格式的 XML文件绑定到不同的DataGrid
http://dev.csdn.net/develop/article/25/25469.shtm

DataGrid连接Access的快速分页法(5)——实现快速分页
http://dev.csdn.net/develop/article/25/25294.shtm

DataGrid连接Access的快速分页法(4)——动态生成SQL语句
http://dev.csdn.net/develop/article/25/25293.shtm

DataGrid连接Access的快速分页法(3)——SQL语句的选用(降序)
http://dev.csdn.net/develop/article/25/25291.shtm

DataGrid连接Access的快速分页法(2)——SQL语句的选用(升序)
http://dev.csdn.net/develop/article/25/25290.shtm

DataGrid连接Access的快速分页法(1)——需求与现状
http://dev.csdn.net/develop/article/25/25288.shtm

显示DataGrid序号的一个适用的方法
http://dev.csdn.net/develop/article/25/25113.shtm

常见 Datagrid 错误(other)
http://dev.csdn.net/develop/article/24/24460.shtm

XP 风格的可拖动列、可排序、可改变宽度的DataGrid的例子
http://dev.csdn.net/develop/article/24/24441.shtm

利用radio实现Datagrid的单选
http://dev.csdn.net/develop/article/24/24295.shtm

重画系列:DataGridColumnStyle之测试代码
http://dev.csdn.net/develop/article/24/24191.shtm

将OleDbDataAdapter绑定到Winform下的DataGrid
http://dev.csdn.net/develop/article/24/24148.shtm

去除Asp:DataGrid中无用ViewState的方法(2)
http://dev.csdn.net/develop/article/23/23671.shtm

去除Asp:DataGrid中无用ViewState的方法(1)
http://dev.csdn.net/develop/article/23/23670.shtm

datagrid保存时无法提交更新的问题
http://dev.csdn.net/develop/article/23/23376.shtm

将DBF,XLS,XML,MDB文件导入C#DataGrid的方法
http://dev.csdn.net/develop/article/23/23036.shtm

动态创建DataGrid的模版列
http://dev.csdn.net/develop/article/22/22942.shtm

DataTable中数据记录的统计
http://dev.csdn.net/develop/article/22/22710.shtm

(ASP.NET)用动态属性和DataView实现DataGrid的双向排序
http://dev.csdn.net/develop/article/22/22513.shtm

如何同步滚动两个相同的DataGrid
http://dev.csdn.net/develop/article/22/22438.shtm

asp.net中DataGrid性能测试
http://dev.csdn.net/develop/article/22/22405.shtm

让Asp.NET的DataGrid可排序、可选择、可分页
http://dev.csdn.net/develop/article/22/22297.shtm

DataGrid传统分页方式
http://dev.csdn.net/develop/article/21/21997.shtm

在后代码里创建DataGrid控件
http://dev.csdn.net/develop/article/21/21934.shtm

实现类似Windows资源管理器的DataGrid
http://dev.csdn.net/develop/article/21/21933.shtm

ASP.net中的Datagrid自定义分页功能
http://dev.csdn.net/develop/article/21/21875.shtm

在Pocket PC应用程序中使用DataGrid控件
http://dev.csdn.net/develop/article/21/21844.shtm

创建可拖动列的DataGrid(2)
http://dev.csdn.net/develop/article/21/21594.shtm

创建可拖动列的DataGrid
http://dev.csdn.net/develop/article/21/21593.shtm

DataGrid和CheckBox的混合使用
http://dev.csdn.net/develop/article/21/21585.shtm

利用Session纪录datagrid模板列中CheckBox的状态
http://dev.csdn.net/develop/article/21/21294.shtm

DataGrid模板列中TextBox的焦点相应键盘事件
http://dev.csdn.net/develop/article/21/21290.shtm

给DataGrid添加确定删除的功能
http://dev.csdn.net/develop/article/20/20892.shtm

如何给DataGrid添加自动增长列
http://dev.csdn.net/develop/article/20/20887.shtm

如何利用RadioButtonList实现datagrid列的单选
http://dev.csdn.net/develop/article/20/20789.shtm

锦上添花DataGrid!
http://dev.csdn.net/develop/article/20/20770.shtm

ASP.Net WebMatrix中Datagrid使用模板列对数据显示进行排版
http://dev.csdn.net/develop/article/20/20576.shtm

格式化 DataGrid 输出
http://dev.csdn.net/develop/article/20/20307.shtm

如何实现单击在DATALIST(DATAGRID)的HEADER加入的CHECKBOX,进行DATALIST(DATAGRID)中的CHECKBOX列

全选或全不选
http://dev.csdn.net/develop/article/20/20290.shtm

向datagrid中加横向 纵向的合计 (在datatable中实现,datatable间倒数据)
http://dev.csdn.net/develop/article/20/20221.shtm

基于ADO+Adodc控件+DataGrid控件制作的一个数据库编辑程序(完整原程序)
http://dev.csdn.net/develop/article/19/19600.shtm

如何实现自定义及自动逐页打印DataGrid显示的内容
http://dev.csdn.net/develop/article/19/19353.shtm

合并datagrid中内容相同的单元格
http://dev.csdn.net/develop/article/19/19122.shtm

创建固定表头、表格体滚动的DataGrid
http://dev.csdn.net/develop/article/18/18972.shtm

创建跨多列、多行表头的DataGrid
http://dev.csdn.net/develop/article/18/18971.shtm

在DataGrid中添加一个合计字段
http://dev.csdn.net/develop/article/18/18856.shtm

在DataGrid里添加确认删除的对话框
http://dev.csdn.net/develop/article/18/18838.shtm

为DataGrid添加自动编号功能
http://dev.csdn.net/develop/article/18/18783.shtm

格式化DataGrid的例子【将数据源中的0,1值转换成实际的文字】
http://dev.csdn.net/develop/article/18/18782.shtm

Henry手记—Web Form中的Datagrid的自定义分页
http://dev.csdn.net/develop/article/18/18760.shtm

创建完全可编辑的 DataGrid
http://dev.csdn.net/develop/article/18/18744.shtm

DataGrid控件通用打印类
http://dev.csdn.net/develop/article/18/18711.shtm

为DataGrid添加CheckBox控件
http://dev.csdn.net/develop/article/18/18615.shtm

VB.NET中关于DataGrid颜色的自定义
http://dev.csdn.net/develop/article/18/18512.shtm

在DataGrid快速添加新行
http://dev.csdn.net/develop/article/18/18487.shtm

Henry手记-Datagrid事件响应(二)
http://dev.csdn.net/develop/article/18/18315.shtm

datagrid技巧之一:代码控制选中行的颜色
http://dev.csdn.net/develop/article/17/17852.shtm

在C#里实现DATAGRID的打印预览和打印
http://dev.csdn.net/develop/article/17/17851.shtm

Binding a DataGrid to an ADO Recordset
http://dev.csdn.net/develop/article/17/17850.shtm

Creating DataGrid Templated Columns Dynamically – Part II
http://dev.csdn.net/develop/article/17/17846.shtm

Creating DataGrid Templated Columns Dynamically – Part I
http://dev.csdn.net/develop/article/17/17845.shtm

DataTable,DataView和DataGrid中一些容易混淆的概念
http://dev.csdn.net/develop/article/17/17840.shtm

动态的管理ASP.NET DataGrid数据列
http://dev.csdn.net/develop/article/17/17599.shtm

Henry手记-Datagrid键盘事件响应(二)
http://dev.csdn.net/develop/article/17/17424.shtm

Henry手记—从Datagrid的标题居中说起
http://dev.csdn.net/develop/article/17/17053.shtm

用嵌套的DataGrid实现主从式表的显示
http://dev.csdn.net/develop/article/16/16960.shtm

DataGrid中嵌套使用Repeater
http://dev.csdn.net/develop/article/16/16834.shtm

Henry手记 - Datagrid键盘事件响应(一)
http://dev.csdn.net/develop/article/16/16572.shtm

给datagrid控件建立稳固的双向排序(asp.net)
http://dev.csdn.net/develop/article/16/16563.shtm

asp.net笔记(dataset,datagrid)
http://dev.csdn.net/develop/article/16/16528.shtm

添加一个下拉框到DataGrid
http://dev.csdn.net/develop/article/16/16434.shtm

DataSet导出CSV格式(ASP.NET,C#)
http://dev.csdn.net/develop/article/16/16311.shtm

Henry手记:Datagrid事件响应
http://dev.csdn.net/develop/article/16/16273.shtm

Henry手记:WinForm Datagrid结构剖析(三)使用代码
http://dev.csdn.net/develop/article/16/16088.shtm

Henry手记:WinForm Datagrid结构剖析(三)类代码
http://dev.csdn.net/develop/article/16/16087.shtm

Henry手记:WinForm Datagrid结构剖析(三)
http://dev.csdn.net/develop/article/16/16086.shtm

ADO在vb.net中的使用(与datagrid结合)
http://dev.csdn.net/develop/article/15/15975.shtm

Henry手记:WinForm Datagrid结构剖析(二)程序
http://dev.csdn.net/develop/article/15/15929.shtm

Henry手记:WinForm Datagrid结构剖析(二)
http://dev.csdn.net/develop/article/15/15928.shtm

ASP.NET中的DataGrid的属性
http://dev.csdn.net/develop/article/15/15920.shtm

EnableViewState="false"的DataGrid分页
http://dev.csdn.net/develop/article/15/15913.shtm

Datagrid 链接数据库Access
http://dev.csdn.net/develop/article/15/15861.shtm

DataGrid 链接Access数据库
http://dev.csdn.net/develop/article/15/15860.shtm

Henry手记:WinForm Datagrid结构剖析(一)
http://dev.csdn.net/develop/article/15/15686.shtm

把Excel文件中的数据读入到DataGrid中
http://dev.csdn.net/develop/article/15/15544.shtm

如何创建一个用弹出窗口来查看详细信息的超链接列
http://dev.csdn.net/develop/article/15/15456.shtm

DataGrid使用技巧(四)
http://dev.csdn.net/develop/article/15/15282.shtm

DataGrid使用技巧(三)
http://dev.csdn.net/develop/article/15/15220.shtm

DataGrid使用技巧(二)
http://dev.csdn.net/develop/article/15/15139.shtm

DataGrid使用技巧(一)
http://dev.csdn.net/develop/article/15/15109.shtm

神奇的 DataGrid
http://dev.csdn.net/develop/article/14/14114.shtm

使用c#+(datagrid控件)编辑xml文件
http://dev.csdn.net/develop/article/13/13333.shtm

在DataGrid中创建一个弹出式Details窗口
http://dev.csdn.net/develop/article/12/12170.shtm

在DataGrid中创建一个弹出式Details窗口
http://dev.csdn.net/develop/article/12/12099.shtm

vs.net beta 2中利用DataGrid分页详解
http://dev.csdn.net/develop/article/11/11009.shtm

使用 ASP+ DataGrid 控件来创建主视图/详细资料视图 (2)
http://dev.csdn.net/develop/article/1/1865.shtm

使用 ASP+ DataGrid 控件来创建主视图/详细资料视图
http://dev.csdn.net/develop/article/1/1864.shtm

2006年01月24日

using System;
 
namespace Excellent.Data
{
    /**//// <summary>
    /// 金额类
    /// </summary>
    public sealed class Money
    {
        Variables#region Variables
        private const string UNIT = "分角元拾佰仟万拾佰仟亿拾佰仟万";
        private const string UPPER = "零壹贰叁肆伍陆柒捌玖";
        #endregion
 
        Properties#region Properties
        
        #endregion
 
        Constructors#region Constructors
        
        #endregion
 
        Overrides#region Overrides
        
        #endregion 
 
        Methods#region Methods
        /**//// <summary>
        /// 获取大写金额
        /// </summary>
        /// <param name="lower">小写金额</param>
        /// <returns>string</returns>
        public static string GetUpper(decimal lower)
        {
            string stResult = "", stLower = "";
 
            stLower = lower.ToString("0.00").Replace(".", "");
            if (stLower.Length <= UNIT.Length)
            {
                for (int i = 0, j = stLower.Length – 1; i <= j; i ++)
                {
                    stResult += UPPER[stLower[i] – 48].ToString() + UNIT[j - i].ToString();
                }
            }
            else
            {
                stResult = "#E";
            }
 
            return stResult;
        }
        #endregion
    }
}

Chinese.Dll是Kanas.net框架的一个外围类库,解决一些.Net Framework未解决的本地化相关的基础性问题。这个应该是与框架无关的。
主要功能是:
一、人民币金额大写,符合相关的规范。
二、农历计算,本例采用查表法,只能处理1900~2050年共150年。笔者写过一个带农历的DatePicker就是用的这个类库。另外还有一个按标准历法精确计算(基于VSOP87算法)的版本,可以处理公元前9999~9999年的农历。但事实上并不实用,所以没有提供。
三、汉语拼音字头检索,在算法方面进行了一定的优化,对处理多音字效果很好。
用法参见以下说明。

Kanas.Supports.Chinese命名空间主要提供一些本地化支持的特别类型服务。这些类型在
Chinese.Dll
单元中。

Kanas.Supports.Chinese.Currency

金额类型。为值类型(继承自System.ValueType)

构造器:

构造完整的金额类型

Currency(double currency)

公共属性:

名称

类型

说明

Value

double

实际金额值

公共方法:

获取金额的中文大写金额串

string ToChineseCurrencyString()

返回值:返回符合有关机构对票据填写要求的中文大写金额串。

附:中国人民银行的有关规定:

 

正确填写票据和结算凭证的基本规定

 

银行、单位和个人填写的各种票据和结算凭证是办理支付结算和现金收付的重要依据,直接关系到支付结算的准确、及时和安全。票据和结算凭证是银行、单位和个人凭以记载账务的会计凭证,是记载经济业务和明确经济责任的一种书面证明。因此,填写票据和结算凭证,必须做到标准化、规范化,要要素齐全、数字正确、字迹清晰、不错漏、不潦草,防止涂改。

一、中文大写金额数字应用正楷或行书填写,如壹(壹)、贰(贰)、叁、肆(肆)、伍(伍)、陆(陆)、柒、捌、玖、拾、佰、仟、万(万)、亿、元、角、分、零、整(正)等字样。不得用一、二(两)、三、四、五、六、七、八、九、十、廿、毛、另(或0)填写,不得自造简化字。如果金额数字书写中使用繁体字,如的,也应受理。

二、中文大写金额数字到“元”为止的,在“元”之后,应写“整”(或“正”)字,在“角”之后可以不写“整”(或“正”)字。大写金额数字有“分”的,“分”后面不写“整”(或“正”)字。

三、中文大写金额数字前应标明“人民币”字样,大写金额数字应紧接“人民币”字样填写,不得留有空白。大写金额数字前未印“人民币”字样的,应加填“人民币”三字。在票据和结算凭证大写金额栏内不得预印固定的“仟、佰、拾、万、仟、伯、拾、元、角、分”字样。

四、阿拉伯小写金额数字中有“0”时,中文大写应按照汉语语言规律、金额数字构成和防止涂改的要求进行书写。举例如下:

(一)阿拉伯数字中间有“O”时,中文大写金额要写“零”字。如¥140950,应写成人民币壹仟肆佰零玖元伍角。

(二)阿拉伯数字中间连续有几个“0”时,中文大写金额中间可以只写一个“零”字。如¥6,007.14,应写成人民币陆仟零柒元壹角肆分。

(三)阿拉伯金额数字万位或元位是“0,或者数字中间连续有几个“0”,万位、元位也是“0,但千位、角位不是“0”时,中文大写金额中可以只写一个零字,也可以不写“零”字。如¥1680.32,应写成人民币壹仟陆佰捌拾元零叁角贰分,或者写成人民币壹仟陆佰捌拾元叁角贰分;又如¥107,000.53,应写成人民币壹拾万柒仟元零伍角叁分,或者写成人民币壹拾万零柒仟元伍角叁分。

(四)阿拉伯金额数字角位是“0,而分位不是“0”时,中文大写金额“元”后面应写“零”字。如¥16,409.02,应写成人民币壹万陆仟肆佰零玖元零贰分;又如¥325.04,应写成人民币叁佰贰拾伍元零肆分。

五、阿拉伯小写金额数字前面,均应填写入民币符号“¥”(或草写:)。阿拉伯小写金额数字要认真填写,不得连写分辨不清。

六、票据的出票日期必须使用中文大写。为防止变造票据的出禀日期,在填写月、日时,月为壹、贰和壹拾的,日为壹至玖和壹拾、贰拾和叁拾的,应在其前加“零”;日为抬壹至拾玖的,应在其前加“壹”。如115,应写成零壹月壹拾伍日。再如1020,应写成零壹拾月零贰拾日。

七、票据出票日期使用小写填写的,银行不予受理。大写日期未按要求规范填写的,银行可予受理,但由此造成损失的,由出票入自行承担。

 

本类型中的相关算法遵守以上基本规定,但尽可能短,即:可以不写“零”的地方一律不写“零”。此外,考虑到有可能“人民币”字样已印在纸样上,本类型返回的大写金额串结果中将不出现该字样。


获取指定原始值金额的中文大写金额串

static string ToChineseCurrencyString(double value)

参数:value:原始金额值

Kanas.Supports.Chinese.LundarDate

农历日期类型。为值类型(继承自System.ValueType)。考虑到实际需要与实现的代价间的平衡,该类型计算农历时采用词典算法,所以仅支持1900年至2050年,一共150年。

构造器:

以公历日期构造农历日期

LunarDate(DateTime date)

参数:date:公历日期

以农历日期参数构造农历日期

LunarDate(int year, int month, int day, bool isLeap)

参数:year:农历年;month:农历月;day:农历日;isLeap:是否为闰月

公共属性:

名称

类型

说明

Date

DateTime

公历日期

Year

string

农历年名

Month

string

农历月名

Day

string

农历日名

SolarName

string

节气名(如不存在节气则返回空串)

IsHoliday

bool

是否为假期,包括农历假期、五一、国庆假期

公共方法:

获取指定年月节气所在日列表

static int[] SolarTeamDays(int year, int month)

参数:year:指定年;month:指定月;

返回值:节气所在日的日期数组

Kanas.Supports.Chinese.PinyinList

拼音匹配表对象。该对象能够从一组中文串中查找出所有按拼音与给定码元串匹配的串。

匹配方式是取汉字对应拼音的第一个字母与码元串的对应字母比较,相等则为匹配。如果该汉字为多音字则可能匹配任何一个读音的第一个字母。如单字“长”和单字“朝”则既可匹配码元“c”也可匹配码元“z”。

例如:“李刚、李刚强、雷光、刘广青、梁忠”五个词汇,如果采用非完全匹配方式,给定串“l”,全部都可匹配;给定串“lg”,则“李刚、李刚强、雷光、刘广青”可以匹配;给定串“lgq”则只有“李刚强、刘广青”可以匹配。如果采用完全匹配方式,给定串“l”,没有可以匹配的;给定串“lg”,则“李刚、雷光”可以匹配;给定串“lgq”则只有“李刚强、刘广青”可以匹配。

构造器:

默认构造器

PinyinList()

公共方法:

比较中文源串与码元串中否匹配

bool Equals(string Source, string Code, bool Integraty)

参数:Source:中文源串;Code:码元串;Integrary:是否完整匹配,即是否先确定等长再匹配;

返回值:返回匹配结果

C#版

using System;
 
namespace Test
{
    /**//// <summary>
    /// Rmb 的摘要说明。
    /// </summary>
    public class Rmb
    {
        /**//// <summary>
        /// 转换人民币大小金额
        /// </summary>
        /// <param name="num">金额</param>
        /// <returns>返回大写形式</returns>
        public static string CmycurD(decimal num)
        {
            string str1 = "零壹贰叁肆伍陆柒捌玖";            //0-9所对应的汉字
            string str2 = "万仟佰拾亿仟佰拾万仟佰拾元角分"; //数字位所对应的汉字
            string str3 = "";    //从原num值中取出的值
            string str4 = "";    //数字的字符串形式
            string str5 = "";  //人民币大写金额形式
            int i;    //循环变量
            int j;    //num的值乘以100的字符串长度
            string ch1 = "";    //数字的汉语读法
            string ch2 = "";    //数字位的汉字读法
            int nzero = 0;  //用来计算连续的零值是几个
            int temp;            //从原num值中取出的值
 
            num = Math.Round(Math.Abs(num),2);    //将num取绝对值并四舍五入取2位小数
            str4 = ((long)(num*100)).ToString();        //将num乘100并转换成字符串形式
            j = str4.Length;      //找出最高位
            if (j > 15){return "溢出";}
            str2 = str2.Substring(15-j);   //取出对应位数的str2的值。如:200.55,j为5所以str2=佰拾元角分
              
            //循环取出每一位需要转换的值
            for(i=0;i<j;i++)
            {
                str3 = str4.Substring(i,1);          //取出需转换的某一位的值
                temp = Convert.ToInt32(str3);      //转换为数字
                if (i != (j-3) && i != (j-7) && i != (j-11) && i != (j-15))
                {    
                    //当所取位数不为元、万、亿、万亿上的数字时
                    if (str3 == "0")
                    {
                        ch1 = "";
                        ch2 = "";
                        nzero = nzero + 1;
                    }
                    else
                    {
                        if(str3 != "0" && nzero != 0)
                        {
                            ch1 = "零" + str1.Substring(temp*1,1);
                            ch2 = str2.Substring(i,1);
                            nzero = 0;
                        }
                        else
                        {
                            ch1 = str1.Substring(temp*1,1);
                            ch2 = str2.Substring(i,1);
                            nzero = 0;
                        }
                    }
                }
                else
                { 
                    //该位是万亿,亿,万,元位等关键位
                    if (str3 != "0" && nzero != 0)
                    {
                        ch1 = "零" + str1.Substring(temp*1,1);
                        ch2 = str2.Substring(i,1);
                        nzero = 0;
                    }
                    else
                    {
                        if (str3 != "0" && nzero == 0)
                        {
                            ch1 = str1.Substring(temp*1,1);
                            ch2 = str2.Substring(i,1);
                            nzero = 0;
                        }
                        else
                        {
                            if (str3 == "0" && nzero >= 3)
                            {
                                ch1 = "";
                                ch2 = "";
                                nzero = nzero + 1;
                            }
                            else
                            {
                                if (j >= 11)
                                {
                                    ch1 = "";
                                    nzero = nzero + 1;
                                }
                                else
                                {
                                    ch1 = "";
                                    ch2 = str2.Substring(i,1);
                                    nzero = nzero + 1;
                                }
                            }
                        }
                    }
                }
                if (i == (j-11) || i == (j-3))
                { 
                    //如果该位是亿位或元位,则必须写上
                    ch2 = str2.Substring(i,1);
                }
                str5 = str5 + ch1 + ch2;
    
                if (i == j-1 && str3 == "0" )
                {   
                    //最后一位(分)为0时,加上“整”
                    str5 = str5 + ‘整’;
                }
            }
            if (num == 0)
            {
                str5 = "零元整";
            }
            return str5;
        }
 
        /**//// <summary>
        /// 一个重载,将字符串先转换成数字在调用CmycurD(decimal num)
        /// </summary>
        /// <param name="num">用户输入的金额,字符串形式未转成decimal</param>
        /// <returns></returns>
        public static string CmycurD(string numstr)
        {
            try
            {
                decimal num = Convert.ToDecimal(numstr);
                return CmycurD(num);
            }
            catch
            {
                return "非数字形式!";
            }
        }
    }
}

JavaScript

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    <head>
        <title></title>
        <meta content="Microsoft Visual Studio .NET 7.1" name="GENERATOR">
        <meta content="VisualStudio.HTML" name="ProgId">
        <meta content="Microsoft Visual Studio .NET 7.1" name="Originator">
        <script language="jscript">
/*****************************************
     Copyright (c) 2004, Laser Lu      
          http://www.idow.net           
 *****************************************/
function convertCurrency(currencyDigits) {
// Constants:
    var MAXIMUM_NUMBER = 99999999999.99;
    // Predefine the radix characters and currency symbols for output:
    var CN_ZERO = "零";
    var CN_ONE = "壹";
    var CN_TWO = "贰";
    var CN_THREE = "叁";
    var CN_FOUR = "肆";
    var CN_FIVE = "伍";
    var CN_SIX = "陆";
    var CN_SEVEN = "柒";
    var CN_EIGHT = "捌";
    var CN_NINE = "玖";
    var CN_TEN = "拾";
    var CN_HUNDRED = "佰";
    var CN_THOUSAND = "仟";
    var CN_TEN_THOUSAND = "万";
    var CN_HUNDRED_MILLION = "亿";
    var CN_SYMBOL = "人民币";
    var CN_DOLLAR = "元";
    var CN_TEN_CENT = "角";
    var CN_CENT = "分";
    var CN_INTEGER = "整";
    
// Variables:
    var integral;    // Represent integral part of digit number.
    var decimal;    // Represent decimal part of digit number.
    var outputCharacters;    // The output result.
    var parts;
    var digits, radices, bigRadices, decimals;
    var zeroCount;
    var i, p, d;
    var quotient, modulus;
    
// Validate input string:
    currencyDigits = currencyDigits.toString();
    if (currencyDigits == "") {
        alert("Empty input!");
        return "";
    }
    if (currencyDigits.match(/[^,.\d]/) != null) {
        alert("Invalid characters in the input string!");
        return "";
    }
    if ((currencyDigits).match(/^((\d{1,3}(,\d{3})*(.((\d{3},)*\d{1,3}))?)|(\d+(.\d+)?))$/) == null) {
        alert("Illegal format of digit number!");
        return "";
    }
    
// Normalize the format of input digits:
    currencyDigits = currencyDigits.replace(/,/g, "");    // Remove comma delimiters.
    currencyDigits = currencyDigits.replace(/^0+/, "");    // Trim zeros at the beginning.
    // Assert the number is not greater than the maximum number.
    if (Number(currencyDigits) > MAXIMUM_NUMBER) {
        alert("Too large a number to convert!");
        return "";
    }
    
// Process the coversion from currency digits to characters:
    // Separate integral and decimal parts before processing coversion:
    parts = currencyDigits.split(".");
    if (parts.length > 1) {
        integral = parts[0];
        decimal = parts[1];
        // Cut down redundant decimal digits that are after the second.
        decimal = decimal.substr(0, 2);
    }
    else {
        integral = parts[0];
        decimal = "";
    }
    // Prepare the characters corresponding to the digits:
    digits = new Array(CN_ZERO, CN_ONE, CN_TWO, CN_THREE, CN_FOUR, CN_FIVE, CN_SIX, CN_SEVEN, CN_EIGHT, CN_NINE);
    radices = new Array("", CN_TEN, CN_HUNDRED, CN_THOUSAND);
    bigRadices = new Array("", CN_TEN_THOUSAND, CN_HUNDRED_MILLION);
    decimals = new Array(CN_TEN_CENT, CN_CENT);
    // Start processing:
    outputCharacters = "";
    // Process integral part if it is larger than 0:
    if (Number(integral) > 0) {
        zeroCount = 0;
        for (i = 0; i < integral.length; i++) {
            p = integral.length – i – 1;
            d = integral.substr(i, 1);
            quotient = p / 4;
            modulus = p % 4;
            if (d == "0") {
                zeroCount++;
            }
            else {
                if (zeroCount > 0)
                {
                    outputCharacters += digits[0];
                }
                zeroCount = 0;
                outputCharacters += digits[Number(d)] + radices[modulus];
            }
            if (modulus == 0 && zeroCount < 4) {
                outputCharacters += bigRadices[quotient];
            }
        }
        outputCharacters += CN_DOLLAR;
    }
    // Process decimal part if there is:
    if (decimal != "") {
        for (i = 0; i < decimal.length; i++) {
            d = decimal.substr(i, 1);
            if (d != "0") {
                outputCharacters += digits[Number(d)] + decimals[i];
            }
        }
    }
    // Confirm and return the final output string:
    if (outputCharacters == "") {
        outputCharacters = CN_ZERO + CN_DOLLAR;
    }
    if (decimal == "") {
        outputCharacters += CN_INTEGER;
    }
    outputCharacters = CN_SYMBOL + outputCharacters;
    return outputCharacters;
}
        </script>
    </head>
    <body>
        <INPUT id="Digits" type="text" name="Digits" size=20>
        <INPUT id="Convert" type="button" value="Convert" name="Convert" onclick="Result.value = convertCurrency(Digits.value);">
        <INPUT id="Result" type="text" name="Result" size=60>
    </body>
</html>

2006年01月23日

今天开个Google栏目, 对Google一直很感兴趣, 现在这里也不写什么或贴什么了, 先给大家一份见面礼, Google Hack 2nd Edition的完整电子书, 就是现在DearBook上面热卖的那本, 好不容易找到的. 只不过是英文版.

个人感觉英文版对于没有太大英文阅读障碍的朋友来说还是首先的, 如果有谁找到比较好的中文版告诉我一声.

下载地址是我在FileFront.com上的网络硬盘, :) , 压缩成RAR, 大约5M.

点击去我的网络硬盘下载页面

1. 前段时间把Gmail里的邀请都发完了, 今天发现又有了100个, 给自己朋友留10个, 其他发给大家, 有需要的在下面回复留地址. 我晚上统一发放.

2. 顺便留下我的Hotmail, haohuaalpha@hotmail.com, 搞开发的朋友有兴趣也有时间的时候大家可以随时交流.

3. 今天考虑给这个BLOG加个Google专栏, 个人对Google还是非常感兴趣的.