现在,网站上登录,发表评论。。。。。。。。是个输入框下都有请输入验证码,泡网的人啊,都输入过。如果你想了解验证码的作用,请先自己Google一下;如果你想制作自己的验证码程序,比如Asp(Asp.net)的,Php的,Jsp的。。。。。。。等等你也可以先Goolge一下。如果你想欣赏一下验证码长什么样的,那好,不用先Google了,看看我找得几个站点的验证码。

1.欣赏验证码

我见到的第一个验证码:8723,随机的一数字字符串,最原始的验证码,验证作用几乎为零,呵呵。

CSDN网站用户登录用的: ,GIF格式,目前常用的随机数字图片验证码。图片上的字符比较中规中矩,验证作用比上一个好。没有基本图形图像学知识的人,不可破!可惜读取它的程序,在CSDN使用它的第一天,好像就在论坛里发布了,真是可怜!

QQ网站用户登录用的: ,PNG格式,图片用的随机数字+随机大写英文字母,整个构图有点张扬,每刷新一次,每个字符还会变位置呢!有时候出来的图片,人眼都识别不了,厉害啊…

MS的hotmail申请时候的: ,BMP格式, 随机数字+随机大写英文字母+随机干扰像素+随机位置+???,这个就让你服气了吧……..不愧是MS老大啊.如果你看不清楚上边的字符了,还可以点下边链接,听语音的读取(注:没有随机背景噪声干扰的,只是TTS语音)。这个倒是很体贴用户!

Google的Gmail注册时候的: JPG格式,随机英文字母+随机颜色+随机位置+随机长度(?)+??,呵呵,看起来不错。。。

找了几个,都找累了,最后给大家贴一个很强的验证码字符,是GMAIL的:


 

(特别说明:这是Javaeye中别人发的,不要误认为是我碰到的,汗啊!!)

欣赏完了这些验证码,那这些验证码有什么用呢,不知道去Google的同学结果怎么样?

2.验证码作用分析


我Copy一段MS在Passport帮助中的话:

键入图片中的字符有助于确保是普通用户而不是自动化的程序在填写注册表单。


这一点很重要,因为攻击者会使用有害程序注册大量的 Web 服务帐户(如 Passport)。攻击者可以使用这些帐户为其他的用户制造麻烦,如发送垃圾邮件或通过同时反复登录多个帐户来延缓服务的速度。


在大多数情况下,自动注册程序不能识别此图片中的字符。


简单的说呢,就是防止攻击者编写程序,自动注册,重复登录暴力破解密码。。。。。。,

验证码实现流程:服务器端随机生成验证码字符串,保存在内存中,并写入图片,发送给浏览器端显示,浏览器端输入验证码图片上字符,然后提交服务器端,提交的字符和服务器端保存的该字符比较是否一致。一致就继续,否则返回提示。

攻击者编写的robot程序,很难识别验证码字符,顺利的完成自动注册,登录。。。。。。。。。而用户可以识别填写,所以这就实现了阻挡攻击的作用。

特别说明的是,其实robot制作者也可以去识别验证码的,所以我说第一种直接输出字符的验证效果几乎为零。而图片的字符识别,就是看图片上的干扰强度了。

就实际的效果来说,验证码只是增加攻击者的难度,而不可能完全的防止。

不过,无论怎么说,为了系统更安全,采用验证码也是一种措施,那么怎么编写验证码程序呢,相信Google一下,就有很多现成的代码吧。

 

3.程序原理


 通过上边的分析,特别是流程分析,相信写出代码是很容易的事情。比如目前流行的实现:

服务端文件名: imgcode.*

伪代码:

随机码生成à1.存入Session(“code”)

2.调用绘图函数或是直接写2进制图片格式,内存中生成图片


客户端文件名:login.htm

伪代码:

<form  name=”login” action=”check” >

<input type=”text” name=”checkcode” value=”” /><img src=”imgcode.*”/>

</form>

服务端文件名:checekcode.*

伪代码:

if 获取客户端checkcode的值=Session(“code”)

{

 ok

}

else

{

err

}

基本的实现就是这样了,其实就是验证码图片的生成部分,校验部分。为了加强防伪码的作用,关键的地方是加入干扰,图片生成。

4.验证码中图像技术讨论

 目前流行的WEB开发服务器端技术中,很多都有绘图的API函数,生成图片的代码也就很简单了,就不多提了。这里用没有内置绘图函数Asp,讨论根据已知图片格式,写入2进制数据,生成图片,先乱弹点图形图像的东西。

我们看下图:

  1111011111

1100011111

1111011111

1111011111

1111011111

1111011111

1111011111

1111011111

1111011111

1100000111

如果我们把上边的0和1的位置想成彩灯,0表示灯亮,1表示等灭,那么变成下图:

 

1111011111
1100011111
1111011111
1111011111
1111011111
1111011111
1111011111
1111011111
1111011111
1100000111
0所在位置组成了一个”1”字。如果在电子技术上,上图就可以看成一个10x10的点阵。同样的,显示屏也可以看作一个个的点组成。我们输出一组01的信号,明暗相间组成一个图像,就是最简单的黑白位图。简单的显示原理就是这样了。

假想有一个人在控制一组10*10的灯,他利用灯的明暗来显示一个“1”字,那么他用什么样的顺序依次开灯呢。将灯分成水平方向和竖直方向,从左下角的第一个开始,先左到右水平方向控制灯,一行完成后,再向上,依次进行,最后到达右上角结束。

   在电子技术上,我们把这样的动作成为行扫描和场扫描,对应描述的行频和场频(刷新频率)就是衡量显像管好坏的重要指标(跑题???)。如果我们用程序控制在屏幕的一块区域(图像大小)中按上边的扫描方法,根据这样一串2进制 字符“1111011111110001111111110111111111011111111101111111110111111111011111111101111111110111111100000111”,将0的位置用白色显示,其他的位置则是黑色,那么我们就输出了一个黑底白字的“1”的图像。当然,如果在windows中显示图像,还要遵循图像的格式标准,否则是无法被系统识别的,所以还要加上表示图像格式本身的信息(图像头)。

啰嗦了这么多,我们来看一个具体的生成BMP格式验证码的代码(本代码是根据网上的一个代码简化的,版权归原作者):

生成验证图片文件:checkcode.asp

% Call Com_CreatValidCode() Sub Com_CreatValidCode() ' 禁止缓存 Response.Expires = -9999 Response.AddHeader "Pragma","no-cache" Response.AddHeader "cache-ctrol","no-cache" Response.ContentType = "Image/BMP" Randomize Dim i, ii, iii Const cAmount = 36 ' 文字数量 Const cCode = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ' 颜色的数据(字符,背景) Dim vColorData(2) a=Rnd * 256 b=Rnd * 256 c=Rnd * 256 vColorData(0) = ChrB(a) & ChrB(b) & ChrB(c) 'vColorData(0) = ChrB(0) & ChrB(0) & ChrB(0) ' 蓝0,绿0,红0(黑色)0的位置颜色 vColorData(1) = ChrB(255) & ChrB(255) & ChrB(255) ' 蓝250,绿236,红211(白色) 1的位置颜色 ' 随机产生字符 Dim vCode(4), vCodes For i = 0 To 3 vCode(i) = Int(Rnd * cAmount) vCodes = vCodes & Mid(cCode, vCode(i) + 1, 1) Next session("CheckCode") = vCodes '记录入Session ' 字符的数据 Dim vNumberData(36) vNumberData(0) = "1110000111110111101111011110111101001011110100101111010010111101001011110111101111011110111110000111" vNumberData(1) = "1111011111110001111111110111111111011111111101111111110111111111011111111101111111110111111100000111" vNumberData(2) = "1110000111110111101111011110111111111011111111011111111011111111011111111011111111011110111100000011" vNumberData(3) = "1110000111110111101111011110111111110111111100111111111101111111111011110111101111011110111110000111" vNumberData(4) = "1111101111111110111111110011111110101111110110111111011011111100000011111110111111111011111111000011" vNumberData(5) = "1100000011110111111111011111111101000111110011101111111110111111111011110111101111011110111110000111" vNumberData(6) = "1111000111111011101111011111111101111111110100011111001110111101111011110111101111011110111110000111" vNumberData(7) = "1100000011110111011111011101111111101111111110111111110111111111011111111101111111110111111111011111" vNumberData(8) = "1110000111110111101111011110111101111011111000011111101101111101111011110111101111011110111110000111" vNumberData(9) = "1110001111110111011111011110111101111011110111001111100010111111111011111111101111011101111110001111" vNumberData(10) = "1111011111111101111111101011111110101111111010111111101011111100000111110111011111011101111000100011" vNumberData(11) = "1000000111110111101111011110111101110111110000111111011101111101111011110111101111011110111000000111" vNumberData(12) = "1110000011110111101110111110111011111111101111111110111111111011111111101111101111011101111110001111" vNumberData(13) = "1000001111110111011111011110111101111011110111101111011110111101111011110111101111011101111000001111" vNumberData(14) = "1000000111110111101111011011111101101111110000111111011011111101101111110111111111011110111000000111" vNumberData(15) = "1000000111110111101111011011111101101111110000111111011011111101101111110111111111011111111000111111" vNumberData(16) = "1110000111110111011110111101111011111111101111111110111111111011100011101111011111011101111110001111" vNumberData(17) = "1000100011110111011111011101111101110111110000011111011101111101110111110111011111011101111000100011" vNumberData(18) = "1100000111111101111111110111111111011111111101111111110111111111011111111101111111110111111100000111" vNumberData(19) = "1110000011111110111111111011111111101111111110111111111011111111101111111110111110111011111000011111" vNumberData(20) = "1000100011110111011111011011111101011111110001111111010111111101101111110110111111011101111000100011" vNumberData(21) = "1000111111110111111111011111111101111111110111111111011111111101111111110111111111011110111000000011" vNumberData(22) = "1000100011110010011111001001111100100111110101011111010101111101010111110101011111010101111001010011" vNumberData(23) = "1000100011110011011111001101111101010111110101011111010101111101100111110110011111011001111000110111" vNumberData(24) = "1110001111110111011110111110111011111011101111101110111110111011111011101111101111011101111110001111" vNumberData(25) = "1000000111110111101111011110111101111011110000011111011111111101111111110111111111011111111000111111" vNumberData(26) = "1110001111110111011110111110111011111011101111101110111110111011111011101001101111011001111110001011" vNumberData(27) = "1000001111110111011111011101111101110111110000111111010111111101101111110110111111011101111000110011" vNumberData(28) = "1110000011110111101111011110111101111111111001111111111001111111111011110111101111011110111100000111" vNumberData(29) = "1000000011101101101111110111111111011111111101111111110111111111011111111101111111110111111110001111" vNumberData(30) = "1000100011110111011111011101111101110111110111011111011101111101110111110111011111011101111110001111" vNumberData(31) = "1000100011110111011111011101111101110111111010111111101011111110101111111010111111110111111111011111" vNumberData(32) = "1001010011110101011111010101111101010111110101011111001001111110101111111010111111101011111110101111" vNumberData(33) = "1000100011110111011111101011111110101111111101111111110111111110101111111010111111011101111000100011" vNumberData(34) = "1000100011110111011111011101111110101111111010111111110111111111011111111101111111110111111110001111" vNumberData(35) = "1100000011110111011111111101111111101111111110111111110111111111011111111011111111101110111100000011" ' 输出图像文件头 Response.BinaryWrite ChrB(66) & ChrB(77) & ChrB(230) & ChrB(4) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(0) &_ ChrB(0) & ChrB(0) & ChrB(54) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(40) & ChrB(0) &_ ChrB(0) & ChrB(0) & ChrB(40) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(10) & ChrB(0) &_ ChrB(0) & ChrB(0) & ChrB(1) & ChrB(0) ' 输出图像信息头 Response.BinaryWrite ChrB(24) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(176) & ChrB(4) &_ ChrB(0) & ChrB(0) & ChrB(18) & ChrB(11) & ChrB(0) & ChrB(0) & ChrB(18) & ChrB(11) &_ ChrB(0) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(0) & ChrB(0) &_ ChrB(0) & ChrB(0) For i = 9 To 0 Step -1 ' 历经所有行 For ii = 0 To 3 ' 历经所有字 For iii = 1 To 10 ' 历经所有像素 ' 逐行、逐字、逐像素地输出图像数据 ,仔细分析这里。 If Rnd * 99 + 1 <3 Then ' 随机生成杂点 5是出现机率,可修改 Response.BinaryWrite vColorData(0) Else Response.BinaryWrite vColorData(Mid(vNumberData(vCode(ii)), i * 10 + iii, 1)) End If 'Response.BinaryWrite vColorData(Mid(vNumberData(vCode(ii)), i * 10 + iii, 1)) Next Next Next End Sub %>

显示验证码:showcode.htm

  <script language="javascript">

                var D=new Date();

                var hh=D.getHours();

                var mm=D.getMinutes();

                var ss=D.getSeconds();

document.write("<img src=checkcode_t.asp?time=" + hh + ":" + mm + ":" + ss + " width=120 height=20>");

</script>

上边的调用方法是CSDN论坛里的看到的,可以防止缓存,后退也能刷新,很好,所以一并借用了。。

上边代码的分析,还是大家自己完成吧,仅仅是个示范作用,根据我说的原理,再参考BMP图像的格式(Google一下),应该很容易。下边我们讨论一下增强验证图片作用,加入识别干扰的一些常见方法。

5.验证码中识别干扰技术

前边说了,验证码想要不被攻击者破解是不可能的,但是我们可以加入干扰,增加破解的难道。就好像CSDN的验证码,无干扰,很容易破解,而后边的几个,难道都加大了很多。实际中,干扰主要是分2类,一类是颜色,一类是形状位置。我们来看。

5.1图片中加入杂点像素。实现代码( 以上边代码为示范):

    If Rnd * 99 + 1 <5 Then ' 随机生成杂点 5是出现机率,可修改

     Response.BinaryWrite vColorData(0)

    Else

     Response.BinaryWrite vColorData(Mid(vNumberData(vCode(ii)), i * 10 + iii, 1))

    End If

5.2图片中的字符颜色是随机。这有2种,一种是每次整个字符颜色不同,一种是每个像素随机颜色随机变化。像素颜色随机实现代码:

a=Rnd * 256 

b=Rnd * 256

c=Rnd * 256

vColorData(0) = ChrB(a) & ChrB(b) & ChrB(c)

5.3图片杂点颜色随机。实现代码:

a=Rnd * 256 

b=Rnd * 256

c=Rnd * 256

vColorData(0) = ChrB(a) & ChrB(b) & ChrB(c)

If Rnd * 99 + 1 <5 Then ' 随机生成杂点 5是出现机率,可修改

        Response.BinaryWrite vColorData(0)

     Else

        Response.BinaryWrite vColorData(Mid(vNumberData(vCode(ii)), i * 10 + iii, 1))

     End If

5.4采用多种字体中的随机一种,字的形状变化,识别的难度更大。即是vNumberData(36,3)…….每次每个字符随机选择一种字体。代码省略。

5.5还有其他的很多办法,比如背景颜色改变,位置移动,长度改变等等。这里不再一一列举。总之是颜色和形状位置的随机改变。

6.结束

都不知道该怎么结束了,本来还想写破解的,还是算了,下次再说。写了上边些,自己都觉得说不清楚了。。。。。。还是强制结束吧。

希望自己的东西对写验证码的朋友有帮助,人人都可以写出自己的喜欢的验证码,而不是很多系统都一样。

错漏的地方很多,而且是很多网上的东西拼盘起来的,大家将就看看吧,同时对我引用过的东西的所有原作者表示感谢。下次还是少写这样长的东西啊。

posted @ 2008-08-19 23:25 一他他 阅读(11) | 评论 (0)编辑
     摘要: 精选系列: 1、Android http://code.google.com/intl/zh-CN/android/ Android 是用于移动设备的软件堆栈,包括操作系统、中间件和关键应用程序。它由开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)开发。全新建立 Android是为了使开发人员能够创建引人入胜的移动应用程序,这些程序可充分利用手机必备的所有功能。开发人员可以使用 An... 阅读全文
posted @ 2008-06-16 19:09 一他他 阅读(21) | 评论 (0)编辑
seo

王通:网站地图的制作和提交


SEO工作中,网站优化好之后,接下来要做的工作就是网站地图的制作和提交了,这一步工作也许有点复杂,但是对于你的网站被Google和yahoo的收录和更新是绝对有好处的,如何操作呢?

在做网站地图之前,我们需要先对Google和yahoo对网站地图格式的要求了解一下:

Google:要求使用XML格式的网站地图
Yahoo: 要求使用TXT格式的网站地图

一、网站地图的制作:

1、在线制作小型的网站地图:

如果你的网站的网页数量少于500个,那么建议你使用在线的网站地图制作工具:
http://www.seo.net.cn/sitemap/

打开这个网页,输入你的网址,然后点击开始,几分钟后,这个在线工具就会给你制作出XML   html    txt  三种不同格式的网站地图,然后你直接下载到本地电脑上。

2、使用工具制作中型网站地图:

用什么工具制作呢?
这里给你推荐一款非常好用的免费的网站地图制作软件:Site Map Builder

官方下载地址:http://www.sitemapbuilder.net/downloads/SiteMapBuilder.zip 

下载后,安装,安装好之后,就可以用来制作网站地图了。

这款软件虽然是英文的,操作也非常简单,打开软件后,输入你的网址,然后点击右边的按钮"Extract Links",然后软件就开始分析你网站的所有链接。

当分析结束之后,你点击左上角的按钮:File >> Save map as >.> 选择自己要保持的网站地图格式,然后保存到本地电脑的路径就OK了。

二、网站地图的提交:


向Google提交网站地图:

https://www.google.com/webmasters/tools/login?hl=zh_CN 

1、进入这个网址,用你的Google帐户登录,然后添加你的网址;

2、接下来按照Google的要求验证你网站,你可以选择html文件验证。

3、这时候,按照Google的要求,制作一个Google要求的文件名的html文件

4、用FTP讲这个html文件和你的xml格式的网站地图文件一起传到你网站的根目录

5、确认验证,回到控制台,添加你的地图文件就OK了。很快,Google就会根据网站地图文件来更新你的网站


向yahoo提交网站地图

英文yahoo提交网址:
http://search.yahoo.com/info/submit.html

首先用FTP讲你的网站地图传到网站的根目录。打开网址后,选择Submit Your Site for Free: 用yahoo帐户登录,然后输入你的网站地图的URL就可以了。

中文Yahoo的提交网址:
http://sitemap.cn.yahoo.com/mysites 

这个操作和Google的差不多,登录、验证网站、提交网址地图URL。

网站地图制作好,提交到搜索引擎,只是可以Google和yahoo快速的抓取和更新你的网站。想要让你的网站提高排名,还需要做其他一些工作,这在以后继续再交流。


本文版权所有,欢迎转摘,转摘请注明作者和出处!
http://www.xueseo.com/post/19.htm

作者:王通
首发:
SEO学习网

  --END

posted @ 2008-06-15 12:27 一他他 阅读(6) | 评论 (0)编辑

 

                                     反射之反思
                                     
Mike Repass
                                      http://msdn.microsoft.com/msdnmag/issues/07/06/CLRInsideOut/default.aspx?loc=en

清晰的组件化目标是否因在库间共享过多类型信息而落空?或许您需要高效的强类型化数据存储,但如果每次对象模型发展后都需要更新您的数据库架构,那会耗费很大成本,所以您更愿意在运行时推断出其类型架构吗?您需要交付能接受任意用户对象的组件,并以某种智能化的方式处理它们吗?您希望库的调方者能以编程方式向您说明它们的类型吗?

如果您发现自己在苦苦维持强类型化数据结构的同时,又冀望于最大化运行时灵活性,那么您大概会愿意考虑反射,以及它如何改善您的软件。在本专栏中,我将探讨 Microsoft® .NET Framework 中的 System.Reflection 命名空间,以及它如何为您的开发体验提供助益。我将从一些简单的示例开始,最后将讲述如何处理现实世界中的序列化情形。在此过程中,我会展示反射和 CodeDom 如何配合工作,以有效处理运行时数据。

在深入探究 System.Reflection 之前,我想先讨论一下一般的反射编程。首先,反射可定义为由一个编程系统提供的任何功能,此功能使程序员可以在无需提前了解其标识或正式结构的情况下检查和操作代码实体。这部分内容很多,我将逐一展开说明。

首先,反射提供了什么呢?您能用它做些什么呢?我倾向于将典型的以反射为中心的任务分为两类:检查和操作。检查需要分析对象和类型,以收集有关其定义和行为的结构化信息。除了一些基本规定之外,通常这是在事先不了解它们的情况下进行的。(例如,在 .NET Framework 中,任何东西都继承自 System.Object,并且一个对象类型的引用通常是反射的一般起点。)

操作利用通过检查收集到的信息动态地调用代码,创建已发现类型的新实例,或者甚至可以轻松地动态重新结构化类型和对象。需要指出的一个要点是,对于大多数系统,在运行时操作类型和对象,较之在源代码中静态地进行同等操作,会导致性能降低。由于反射的动态特性,因此这是个必要的取舍,不过有很多技巧和最佳做法可以优化反射的性能(有关优化使用反射的更多深入信息,请参见 msdn.microsoft.com/msdnmag/issues/05/07/Reflection)。

那么,什么是反射的目标呢?程序员实际检查和操作什么呢?在我对反射的定义中,我用了“代码实体”这个新术语,以强调一个事实:从程序员的角度来说,反射技术有时会使传统对象和类型之间的界限变得模糊。例如,一个典型的以反射为中心的任务可能是:

  1. 从对象 O 的句柄开始,并使用反射获得其相关定义(类型 T)的句柄。
  2. 检查类型 T,获得它的方法 M 的句柄。
  3. 调用另一个对象 O’(同样是类型 T)的方法 M。

 

请注意,我在从一个实例穿梭到它的底层类型,从这一类型到一个方法,之后又使用此方法的句柄在另一个实例上调用它 — 显然这是在源代码中使用传统的 C# 编程技术无法实现的。在下文中探讨 .NET Framework 的 System.Reflection 之后,我会再次通过一个具体的例子来解释这一情形。

某些编程语言本身可以通过语法提供反射,而另一些平台和框架(如 .NET Framework)则将其作为系统库。不管以何种方式提供反射,在给定情形下使用反射技术的可能性相当复杂。编程系统提供反射的能力取决于诸多因素:程序员很好地利用了编程语言的功能表达了他的概念吗?编译器是否在输出中嵌入足够的结构化信息(元数据),以方便日后的解读?有没有一个运行时子系统或主机解释器来消化这些元数据?平台库是否以对程序员有用的方式,展示此解释结果?

如果您头脑中想象的是一个复杂的、面向对象类型的系统,但在代码中却表现为简单的、C 语言风格的函数,而且没有正式的数据结构,那么显然您的程序不可能动态地推断出,某变量 v1 的指针指向某种类型 T 的对象实例。因为毕竟类型 T 是您头脑中的概念,它从未在您的编程语句中明确地出现。但如果您使用一种更为灵活的面向对象语言(如 C#)来表达程序的抽象结构,并直接引入类型 T 的概念,那么编译器就会把您的想法转换成某种日后可以通过合适的逻辑来理解的形式,就象公共语言运行时 (CLR) 或某种动态语言解释器所提供的一样。

反射完全是动态、运行时的技术吗?简单的说,不是这样。整个开发和执行周期中,很多时候反射对开发人员都可用且有用。一些编程语言通过独立编译器实现,这些编译器将高级代码直接转换成机器能够识别的指令。输出文件只包括编译过的输入,并且运行时没有用于接受不透明对象并动态分析其定义的支持逻辑。这正是许多传统 C 编译器的情形。因为在目标可执行文件中几乎没有支持逻辑,因此您无法完成太多动态反射,然而编译器会不时提供静态反射 — 例如,普遍运用的 typeof 运算符允许程序员在编译时检查类型标识。

另一种完全不同的情况是,解释性编程语言总是通过主进程获得执行(脚本语言通常属于此类)。由于程序的完整定义是可用的(作为输入源代码),并跟完整的语言实现结合在一起(作为解释器本身),因此所有支持自我分析所需的技术都到位了。这种动态语言频繁地提供全面反射功能,以及一组用于动态分析和操作程序的丰富工具。

.NET Framework CLR 和它的承载语言如 C# 属于中间形态。编译器用来把源代码转换成 IL 和元数据,后者与源代码相比虽属于较低级别或者较低“逻辑性”,但仍然保留了很多抽象结构和类型信息。一旦 CLR 启动和承载了此程序,基类库 (BCL) 的 System.Reflection 库便可以使用此信息,并返回关于对象类型、类型成员、成员签名等的信息。此外,它也可以支持调用,包括后期绑定调用。


.NET 中的反射

要在用 .NET Framework 编程时利用反射,您可以使用 System.Reflection 命名空间。此命名空间提供封装了很多运行时概念的类,例如程序集、模块、类型、方法、构造函数、字段和属性。图 1 中的表显示,System.Reflection 中的类如何与概念上运行时的对应项对应起来。

尽管很重要,不过 System.Reflection.Assembly 和 System.Reflection.Module 主要用于定位新代码并将其加载到运行时。本专栏中,我暂不讨论这些部分,并且假定所有相关代码都已经加载。

要检查和操作已加载代码,典型模式主要是 System.Type。通常,您从获得一个所关注运行时类别的 System.Type 实例开始(通过 Object.GetType)。接着您可以使用 System.Type 的各种方法,在 System.Reflection 中探索类型的定义并获得其它类的实例。例如,如果您对某特定方法感兴趣,并希望获得此方法的一个 System.Reflection.MethodInfo 实例(可能通过 Type.GetMethod)。同样,如果您对某字段感兴趣,并希望获得此字段的一个 System.Reflection.FieldInfo 实例(可能通过 Type.GetField)。

一旦获得所有必要的反射实例对象,即可根据需要遵循检查或操作的步骤继续。检查时,您在反射类中使用各种描述性属性,获得您需要的信息(这是通用类型吗?这是实例方法吗?)。操作时,您可以动态地调用并执行方法,通过调用构造函数创建新对象,等等。


检查类型和成员

让我们跳转到一些代码中,探索如何运用基本反射进行检查。我将集中讨论类型分析。从一个对象开始,我将检索它的类型,而后考察几个有意思的成员(请参见图 2)。

首先需要注意的是,在类定义中,乍看起来说明方法的篇幅比我预期的要多很多。这些额外的方法是从哪里来的呢?任何精通 .NET Framework 对象层次结构的人,都会识别从通用基类 Object 自身继承的这些方法。(事实上,我首先使用了 Object.GetType 检索其类型。)此外,您可以看到属性的 getter 函数。现在,如果您只需要 MyClass 自身显式定义的函数,该怎么办呢?换句话说,您如何隐藏继承的函数?或者您可能只需要显式定义的实例函数?

随便在线看看 MSDN®,就会发现大家都愿意使用 GetMethods 第二个重载方法,它接受 BindingFlags 参数。通过结合来自 BindingFlags 枚举中不同的值,您可以让函数仅返回所需的方法子集。替换 GetMethods 调用,代之以:

GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly |
BindingFlags.Public)

 

结果是,您得到以下输出(注意这里不存在静态帮助器函数和继承自 System.Object 的函数)。

Reflection Demo Example 1
Type Name: MyClass
Method Name: MyMethod1
Method Name: MyMethod2
Method Name: get_MyProperty
Property Name: MyProperty

 

如果您事先知道类型名称(完全限定)和成员,又该如何?您如何完成从枚举类型向检索类型的转换?图 3 中的示例显示了如何通过 Object.GetType 和 Type.GetMethod,使用描述类型信息的字符串文字,检索实际代码对应项。有了前两个示例中的代码,您已经有了能够实现基元类浏览器的基本组件。通过名称您可以找到一个运行时实体,然后枚举其各种相关属性。


动态调用代码

迄今为止,我已经获得运行时对象的句柄(如类型和方法),仅作描述用,例如输出它们的名称。但是如何做得更多呢?如何实际调用某个方法呢?图 4 显示了如何获得某类型成员的 MethodInfo,然后使用 MethodInfo.Invoke 实际动态调用此方法。

此例的几个要点是:首先,从一个 MyClass, mc1 实例检索一个 System.Type 实例,然后,从该类型检索一个 MethodInfo 实例。最后,当调用 MethodInfo 时,通过把它作为调用的第一个参数来传递,将其绑定到另一个 MyClass (mc2) 实例中。

前面讲过,对于您预期在源代码中见到的类型和对象使用之间的区别,这个示例使这种区别变得模糊。逻辑上,您检索了一个方法的句柄,然后调用该方法,就象它属于一个不同的对象一样。对于熟悉函数式编程语言的程序员来说,这可能轻而易举;但对于只熟悉 C# 的程序员来说,要分离对象实现和对象实例化,可能就不是那么直观了。


组合在一起

至此我已经探讨过检查和调用的基本原理,接下来我会用具体的例子把它们组合在一起。设想您希望交付一个库,带有必须处理对象的静态帮助器函数。但在设计的时候,您对这些对象的类型没有任何概念!这要看函数调用方的指示,看他希望如何从这些对象中提取有意义的信息。函数将接受一个对象集合,和一个方法的字符串描述符。然后它将遍历该集合,调用每个对象的方法,用一些函数聚合返回值(请参见图 5)。

就此例而言,我要声明一些约束条件。首先,字符串参数描述的方法(必须由每个对象的底层类型实现)不会接受任何参数,并将返回一个整数。代码将遍历对象集合,调用指定的方法,逐步计算出所有值的平均值。最后,因为这不是生产代码,在求和的时候我不用担心参数验证或整数溢出。

在浏览示例代码时,可以看到主函数与静态帮助器 ComputeAverage 之间的协议除了对象自身的通用基类之外,并不依赖任何类型信息。换句话说,您可以彻底改变正在传送的对象的类型和结构,但只要总是能使用字符串描述一个方法,且该方法返回整数,ComputeAverage 就可以正常工作!

需要注意的一个关键问题跟隐藏在最后这个例子中的 MethodInfo(一般反射)有关。注意,在 ComputeAverage 的 foreach 循环中,代码只从集合中的第一个对象中抓取一个 MethodInfo,然后绑定用于所有后续对象的调用。正如编码所示,它运行良好 — 这是 MethodInfo 缓存的一个简单例子。但此处有一个根本性的局限。MethodInfo 实例仅能由其检索对象同等层级类型的实例调用。因为传入了 IntReturner 和 SonOfIntReturner(继承自 IntReturner)的实例,才能这样运行。

在示例代码中,已经包含了名为 EnemyOfIntReturner 的类,它实现了与其他两个类相同的基本协议,但并没有共享任何常见共享类型。换句话说,该接口逻辑上等同,但在类型层级上没有重叠。要探讨 MethodInfo 在该情形下的使用,请尝试向集合添加其他对象,通过“new EnemyOfIntReturner(10)”得到一个实例,再次运行示例。您会遇到一个异常,指出 MethodInfo 不能用于调用指定的对象,因为它和获得 MethodInfo 时的原始类型完全无关(即使方法名称和基本协议是等同的)。要使您的代码达到生产水准,您需要做好遇到这一情形的准备。

一个可能的解决方案可以是通过自己分析所有传入对象的类型,保留对其共享的类型层级(如果有)的解释。如果下一对象的类型与任意已知类型层级相异,就需要获取和存储一个新的 MethodInfo。另一解决方案是捕获 TargetException,并重新获取一个 MethodInfo 实例。这里提到的两种解决方案都各有其优缺点。Joel Pobar 为本杂志 2007 五月期写过一篇优秀的文章,内容关于 MethodInfo 缓冲和我所极力推荐的反射性能。

希望此示例演示的向应用程序或框架中添加反射,可以为日后的自定义或可扩展性增加更多的灵活性。不可否认,较之本机编程语言中的同等逻辑,使用反射可能会有些繁琐。如果您感到对您或您的客户来说,向代码中添加基于反射的后期绑定过于麻烦(毕竟他们需要以某种方式在您的框架中说明他们的类型和代码),那么可能仅需要适度的灵活性以取得某种平衡。


序列化的高效类型处理

至此我们已通过若干示例讲述了 .NET 反射的基本原理,接下来让我们看一下现实世界中的情形。如果您的软件通过 Web 服务或其他进程外远程技术与其他系统进行交互,那么您很可能已经遇到序列化问题。序列化本质上是将活动的、占用内存的对象,转变成适合线上传输或磁盘存储的数据格式。

.NET Framework 中的 System.Xml.Serialization 命名空间提供了拥有 XmlSerializer 的强大序列化引擎,它可以使用任意托管对象,并将其转换成 XML(日后也可将 XML 数据转换回类型化的对象实例,这一过程称之为反序列化)。XmlSerializer 类是一种强大的、企业就绪的软件片断,如果您在项目中面临序列化问题,它将是您的首选。但为了教学目的,我们来探讨如何实现序列化(或者其他类似的运行时类型处理实例)。

设想情形:您正在交付一个框架,需要使用任意用户类型的对象实例,并将其转换成某种智能型数据格式。例如,假定有一个驻留内存的对象,类型为如下所示的 Address:

(pseudocode)
class Address
{
AddressID id;
String Street, City;
StateType State;
ZipCodeType ZipCode;
}

 

如何生成适当的数据表示形式以方便日后使用?或许一个简单的文本呈现将解决这一问题:

Address: 123
Street: 1 Microsoft Way
City: Redmond
State: WA
Zip: 98052

 

如果事先完全了解需要转换的正式数据类型(例如自己编写代码时),事情就变得非常简单:

foreach(Address a in AddressList)
{
Console.WriteLine(“Address:{0}”, a.ID);
Console.WriteLine(“\tStreet:{0}”, a.Street);
... // and so on
}

 

然而,如果预先不知道在运行时会遇到的数据类型,情况会变得十分有趣。您如何编写象这样的一般框架代码?

MyFramework.TranslateObject(object input, MyOutputWriter output)

 

首先,您需要决定哪些类型成员对序列化有用。可能的情况包括仅捕获特定类型的成员,例如基元系统类型,或提供一种机制以供类型作者说明哪些成员需要被序列化,例如在类型成员上使用自定义属性作为标记)。您仅可以捕获特定类型的成员,例如基元系统类型,或类型作者能够说明哪些成员需要被序列化(可能的方法是在类型成员上使用自定义属性作为标记)。

一旦记录清楚需要转换的数据结构成员,您接着需要做的是编写逻辑,从传入的对象枚举和检索它们。反射在这里担负了繁重的任务,让您既可以查询数据结构又可以查询数据值。

出于简单性考虑,我们来设计一个轻型转换引擎,得到一个对象,获取所有其公共属性值,通过直接调用 ToString 将它们转换成字符串,然后将这些值序列化。对于一个名为“input”的给定对象,算法大致如下:

  1. 调用 input.GetType 以检索 System.Type 实例,该实例描述了 input 的底层结构。
  2. 用 Type.GetProperties 和适当的 BindingFlags 参数,将公共属性作为 PropertyInfo 实例检索。
  3. 使用 PropertyInfo.Name 和 PropertyInfo.GetValue,将属性作为键-值对检索。
  4. 在每个值上调用 Object.ToString 将其(通过基本方式)转化为字符串格式。
  5. 将对象类型的名称和属性名称、字符串值的集合打包成正确的序列化格式。

 

这一算法明显简化了事情,同时也抓住了得到运行时数据结构,并将其转化为自描述型数据的要旨。但这里有一个问题:性能。之前提到,反射对于类型处理和值检索的成本都很高。本示例中,我在每个提供类型的实例中执行了完整的类型分析。

如果以某种方式可以捕获或保留您对于类型结构的理解,以便日后不费力地检索它,并有效处理该类型的新实例;换句话说,就是往前跳到示例算法中的步骤 #3?好消息是,利用 .NET Framework 中的功能,完全可能做到这一点。一旦您理解了类型的数据结构,便可以使用 CodeDom 动态生成绑定到该数据结构的代码。您可以生成一个帮助器程序集,其中包含帮助器类和引用了传入类型并直接访问其属性的方法(类似托管代码中的任何其他属性),因此类型检查只会对性能产生一次影响。

现在我将修正这一算法。新类型:

  1. 获得对应于该类型的 System.Type 实例。
  2. 使用各种 System.Type 访问器检索架构(或至少检索对序列化有用的架构子集),例如属性名称、字段名称等。
  3. 使用架构信息生成帮助器程序集(通过 CodeDom),该程序集与新类型相链接,并有效地处理实例。
  4. 在帮助器程序集中使用代码,提取实例数据。
  5. 根据需要序列化数据。

 

对于给定类型的所有传入数据,可以往前跳到步骤 #4,较之显式检查每一实例,这么做可以获得巨大的性能提升。

我开发了一个名为 SimpleSerialization 的基本序列化库,它用反射和 CodeDom(本专栏中可下载)实现了这一算法。主要组件是一个名为 SimpleSerializer 的类,是用户用一个 System.Type 实例构造所得。在构造函数中,新的 SimpleSerializer 实例会分析给定的类型,利用帮助器类生成一个临时程序集。该帮助器类会紧密绑定到给定的数据类型,而且对实例的处理方式就象自己在完全事先了解类型的情况下编写代码那样。

SimpleSerializer 类有如下布局:

class SimpleSerializer
{
public class SimpleSerializer(Type dataType);
public void Serialize(object input, SimpleDataWriter writer);
}

 

简单地令人惊叹!构造函数承担了最繁重的任务:它使用反射来分析类型结构,然后用 CodeDom 生成帮助器程序集。SimpleDataWriter 类只是用来阐明常见序列化模式的数据接收器。

要序列化一个简单的 Address 类实例,用下面的伪代码即可完成任务:

SimpleSerializer mySerializer = new SimpleSerializer(typeof(Address));
SimpleDataWriter writer = new SimpleDataWriter();
mySerializer.Serialize(addressInstance, writer);

 


结束

强烈建议您亲自试用一下示例代码,尤其是 SimpleSerialization 库。我在 SimpleSerializer 一些有趣的部分都添加了注释,希望能够有所帮助。当然,如果您需要在产品代码中进行严格的序列化,那么确实要依靠 .NET Framework 中提供的技术(例如 XmlSerializer)。但如果您发现在运行时需要使用任意类型并能高效处理它们,我希望您采用我的 SimpleSerialization 库作为自己的方案。

对 CLR 开发人员 Weitao Su(反射)和 Pete Sheill (CodeDom) 所提供的指导和反馈,在此深表谢意。

posted @ 2008-06-02 11:39 一他他 阅读(18) | 评论 (0)编辑

 

我眼中的C# 3.0

 

Written by Allen Lee

 

缘起

每次有新技术发布时,我们总能感受到两种截然不同的情绪:一种是恐惧和抵抗,伴随着这种情绪的还有诸如"C# 2.0用的挺好的,为什么要在C# 3.0搞到那么复杂?"或者"我还在使用C# 1.0呢?"等言辞;另一种则是兴奋和拥抱,伴随着这种情绪的还有诸如"原来这个问题在C# 3.0里可以这么简单!"等言辞。

最近我在公司内部做一个LINQ的系列讲座,在我为其中C# 3.0新特性这一讲准备演示文稿时,突然萌生了写下这篇文章的念头。语言的特性乃至其本身并没有对错之分,是否接受在很大程度上是一个感性问题,即你是否喜欢这样的做事方式,我并没有打算说服任何人接受C# 3.0和LINQ,写这篇文章也只是想和大家分享一下我自己的感受。

有一次我观看一个关于Expression Blend的培训视频,里面说了一句让我印象非常深刻的话:

I know how it works because I know why it works.

细细品味这句话,你会感受到它所要传达的信息:理解为何需要这个功能可以帮助你更好地理解如何使用这个功能,而这也正是我要在这篇文章里采用的表达方式。

 

你是如何创建属性的?

如果你长期使用C#,相信你不会对属性这个东西感到陌生。一般地,属性是对私有字段的一个简单包装,就像这样:

代码 1

使用属性而不是直接公开私有字段的一个好处就是在属性的获取访问器或设置访问器里加入额外的逻辑并不会为客户端代码带来麻烦,例如你想在设置标题的时候做一些额外的检查。但如果你只是简单地包装一下,像上面的代码那样,就会发现你其实多写了不少可以省略的代码。既然Title属性和m_Title私有字段对应,获取访问器就肯定是返回m_Title的值,而设置访问器也肯定是把值设到m_Title。再者,如果你只通过Title属性来访问这个数据,那么m_Title私有字段就会变得无足轻重,这样的话,为什么不交给编译器代劳呢?这个时候,C# 3.0的自动属性就可以派上用场了:

代码 2

编译器会为你创建一个私有字段,并让获取访问器和设置访问器指向这个私有字段。当然,如果有需要,例如要在获取访问器或设置访问器里加入额外的逻辑时,你随时可以对获取访问器和设置访问器进行展开。

 

你是如何初始化对象的?

现在,假设我们有这样一个类:

代码 3

你会怎样初始化它?一种做法是用Book的默认构造函数创建对象实例,然后分别为每个属性赋值:

代码 4

另一种做法是使用C# 3.0对象初始化器:

代码 5

乍看一下,C# 3.0的做法似乎没有让人感到任何优越感,现在,请你仔细观察一下,这两份代码分别包含多少个";"?代码4有5个";",意味着它用了5个语句进行初始化;而代码5只有1个";",意味着它只用了1个语句进行初始化。从词法的角度来看,如果此刻我只能接受一个表达式,那么代码4的做法就帮不上忙了。一个变通的方法是为Book类提供带参的构造函数,但这种方法也有弊端,用户可能只想在初始化时为部分属性提供数据,而我们又无法确切预知用户会提供哪些属性的组合,于是,我们可能要为用户提供足够多的构造函数重载,嗯,有点无聊,也有点多余。另一个变通的方法是提供接受最多参数的构造函数,如果用户为某个参数传递null,那么就忽略与之对应的属性,这个方法比较接近代码5的做法,不同的是,如果你的属性很多,而用户关心的只是很少一部分,就可能不得不输入很多null了。

现在,假设你要实例化一组Book对象,并把它们储存在一个集合里,你会怎么做?下面是通常的做法:

代码 6

如果结合使用C# 3.0的对象初始化器和集合初始化器,你就可以把代码简化为:

代码 7

集合里的每个元素通过","分割,结合对象初始化器使用,整个集合的结构显得比较明晰。字典的初始化也可以同样简单:

代码 8

说到这里,我相信你也能感觉到,C#似乎正在表达式化,以前需要很多条语句才能做到的事情,现在却可以用单个表达式描述出来,而这种理念也渗透在整个C# 3.0的氛围里。

 

你是如何把运算逻辑外包出去的?

假设我现在得到了一组Book的实例对象,你要对它们进行排序,那么你如何告诉它你要按价格来排序呢?

代码 9

在C# 1.0里,我们需要特意为它提供一个独立的方法:

代码 10

然后向Sort()方法传入所需委托的实例:

代码 11

这在C# 2.0里可以进一步简化为:

代码 12

如果使用C# 2.0的匿名方法,我们可以省去很多不必要的代码:

代码 13

此外,使用匿名方法,Sort()方法和你希望它用来比较两个Book实例对象的逻辑可以放在同一个地方;而使用独立的命名方法,包含这个逻辑的方法可能会由于整理代码而被挪到别的地方。这样,当你看到代码12时,为了了解它内部的实现,就不得不花一些精力去寻找Compare()方法了。当然,你可以争辩说,我们可以制定一个编码规范,使得Compare()方法必须紧贴在Sort()方法的下方。是的,你可以,但如果这个逻辑并不需要重用,那么使用匿名方法还是具有明显的优势的。如果这个逻辑需要重用,那么匿名方法就无能为力了。

现在,让我们来考察一下代码13,有没有发现匿名方法的表达方式还不够简练?我们知道,books集合里面只有Book的实例对象,所以Sort()方法传给我们两个参数的类型必定是Book,而Sort()方法期待的结果正是x.Price.CompareTo(y.Price)这个表达式的运算结果,至于delegate和return这样的字眼可以说在这里完全是多余的,那么为什么我们不直接这样表达呢:

代码 14

这就是C# 3.0引入的Lambda表达式语法。我见过一些人,他们通常强调尽可能简单,但若事情突然变得比他们预期的还要简单很多,他们就开始感到不适,甚至拒绝接受这种简单,其实即使事物的发展方向和你的前进方向相一致,但如果发展速度大大超越了你,仍然有可能引发你内心对失控的恐惧。我希望Lambda表达式语法不会让你感到太大的不适,当然我更希望你会喜欢上它。

Lambda表达式的理解其实可以很简单,就是"=>"左边的参数参与右边的表达式运算,而运算结果将会返回,这有点像化合反应,即两种或两种以上的物质(左边的参数)生成一种新物质(右边的表达式的运算结果),不同的是,Lambda可以不接收任何参数,也可以不返回任何结果。

"=>"右边除了可以放表达式之外,还可以放语句,像这样:

代码 15

我们把它称为Lambda语句(Lambda Statement),或许你已经发现,它和匿名方法相比只是不需要写delegate关键字和参数类型。

 

你是如何为对象扩展与之相关的功能的?

我一直在想,为什么String类没有提供一个Reverse()方法,把字符串翻转呢?我猜可能是因为这种操作没有什么现实意思,除非你要做一个文字游戏。实现Reverse()方法并不难,下面是其中一种做法:

代码 16

使用方法也非常简单:

代码 17

你甚至可以把Reverse()方法放到某个静态类里,例如Utils,这样,代码17就可以变成:

代码 18

在C# 3.0之前,你最多只能走到这里,而到了C# 3.0,你还可以使用扩展方法对它做进一步调整,使代码18变成:

代码 19

怎么样,看上去就像Reverse()方法是属于String的,而你所需要做的仅仅是在Reverse()方法的target参数前面加上"this"关键字:

代码 20

我们知道,计算机的底层世界并不知道什么是面向对象,而我们在对象里定义的实例方法都包含一个隐藏参数,这个参数就是指向当前对象实例的指针,C# 3.0的扩展方法在形式上模仿了这种做法,但由于扩展方法本质上并不属于与之相关的类,所以你无法在扩展方法里访问类内部的私有成员。

就上面的讨论来说,你可能认为,和代码18相比,代码19并没有太大的优势,那么为什么需要扩展方法呢?假设我们手头上有一堆书,我想找到最便宜的LINQ的书,使用标准查询运算符的话可以这样写:

代码 21

我们知道,Where()、OrderBy()和First()等都是扩展方法,如果C# 3.0不支持扩展方法,那么代码21就不得不写成这样了:

代码 22

代码21的可读性明显比代码22的高,也显得更自然,而此时我们只是使用了3个标准查询运算符,你可以想象一下,在没有扩展方法的支持下要表达更复杂的查询会是怎样一番情景?

 

你是如何表达你想要的东西的?

现在,假设我想找到最便宜的LINQ的书,使用C# 2.0的语法,我可能需要这样:

代码 23

虽然我已经使用了Array.IndexOf()方法、List<T>.Sort()方法和匿名函数来简化代码,但仍然无法掩盖一个事实,那就是我在讲述如何获取我想要的东西,而这也正是命令式编程(Imperative Programming)的核心思想。

如果使用C# 3.0的语法,情况将会大不一样:

代码 24

在这里,你表达了你想要的东西,而不是获取这些东西的具体步骤,这是声明式编程(Declarative Programming)的核心思想,这样做的好处是明显的,你的需求可以被重新解析并执行,必要时还可以对底层的实现进行优化,但由于你并不关心和牵扯到具体的实现上,所以那些优化并不会导致你修改代码。

命令式编程就像过程管理,你深入执行的细节,继而对整个过程的执行实施控制;而声明式编程则像目标管理(MBO),你制定目标,并把任务分配下去执行。代码23给人的感觉就是整个执行过程都非常的清楚,你可以对任何一个步骤进行修改或者调优;而代码24给人的感觉就是你除了说出你想要什么,你什么也不能做,这对于那些过程管理拥戴者来说可能是不可接受的,他们感到对事物失去了控制,无法建立安全感,因而产生了焦虑。曾经有人向我抱怨:如果你使用了LINQ,你就只能迫使自己相信它的实现是很好的。想想看,如果你的公司把饭堂业务承包给一个餐饮公司,你的公司可以插手别人如何招聘厨师、如何采购食物、如何烧菜烧饭吗?选择LINQ意味着你愿意把执行细节交给别人去处理,从而脱离这些细节,如果你根本无法放下对这些细节的控制,那么LINQ可能并不适合你。

很难说这两种编程方式孰优孰劣,因为在某些场合下,善于过程管理的管理者确实更能让事态朝正确的方向发展;而在另一些场合下,目标管理为实现者提供足够的自由度,更能激励他们积极地进行思考。管理界对于过程管理和目标管理孰优孰劣之争论似乎从来没有停过,更何况编程界对于命令式编程和声明式编程孰优孰劣之争论,我个人倒是更倾向于把这看成是找出更适合你自己的风格,而不是盲目听信别人的说法。语言到底是发挥积极作用还是消极作用在很大程度上是取决于使用者的,我们应该使用语言有利的一面来协助我们的工作,而不是使用其有害的一面来伤害自己和别人。

回到代码24,它把满足条件的书的所有信息都返回给我,如果我只需要书名和作者名字呢?我们知道,在面向对象的世界里,信息储存在对象里,于是我们不得不走到一个尴尬的境地,那就是我们要为此创建一个临时类:

代码 25

噩梦正式开始了,如果我需要书名和价格呢?如果我需要书名、作者和价格呢?……(读者可以自行补全这个列表)这个时候就轮到C# 3.0的匿名类型和隐式类型化变量出场了:

代码 26

因为匿名类型是由编译器自动生成的,而在你写代码的时候它还没有名字,所以你无法用这个类型来声明这个变量,此时"var"关键字就派上用场了。这个是"var"关键字的最初目的,但得益于类型推断系统,我们还可以使用"var"关键字声明任何本地变量,只要我们在声明的同时给予它初始化,否则编译器无法进行推断。曾经有人问我:如果我想返回代码26里的wanted7怎么办?我们知道,方法的返回值需要明确给出类型,而在我们写下代码26时,编译器还没有给查询表达式里的匿名类型取名。如果你真的要把它返回,你只能把方法的返回值类型定为IEnumerable<object>,因为我们只能确定匿名类型是object的后代,但这样一来,客户端代码的日子就不太好过了,因为除了通过反射来访问你的对象,它别无他选。如果你真的要把它返回,那就意味着你和客户端代码有共享这个对象的需求,此时恰当的做法应该是使用命名类型。另外,代码26里构建匿名类型时的"book.Title"是"Title = book.Title"的简写,当你省略"Title ="时,编译器会假定你希望匿名类型的这个属性的名字和Book.Title的一样。

匿名类型还有一个有趣的地方,它曾经是可变的(mutable),后来却变成不可变的(immutable),Sree《Immutable is, the new Anonymous Type》一文中给出了这个转变的解释。我们知道,在面向对象的世界里,对象封装并维护自身的状态,我们通过调用对象的方法所产生的副作用来影响对象的状态,而不可变则是函数式编程(Functional Programming)的核心特征,或许你已经感受到了,C# 3.0引入了大量函数式编程的东西,而函数式编程语言似乎也要风生水起,这究竟意味着什么呢?

 

前路在何方?

无论你是否承认,C# 3.0在表达上比它之前的版本要来的简单,但要获得这种简单,你必须先用很多东西武装自己的脑袋,这使我想起曾经在一本书里看到的一句话:

简单是由复杂来支撑的。

不同语言之间的相互渗透已经不再是什么新奇之事了,引入其它语言的功能有时候甚至可以看作是在战略上入侵对手的市场,这在某种程度上有点像金融业的混业经营。下一个版本的C#将会是怎样的呢?或许这个问题令你兴奋不已,你甚至希望现在就让C# Team看看你的创造力;或许这个问题令你痛心不已,你害怕自己无法适应下一波的变革,因为变革可能导致动荡,动荡可能带来失控,失控可能引发焦虑。不管怎样,该来的是无法回避的,或许现在先让我们看看Matthew Podwysocki的《What Is the Future of C# Anyways?》是否有一些启示……

posted @ 2008-06-02 11:25 一他他 阅读(12) | 评论 (0)编辑

1.2  系统分析

1.2.1  需求分析

对于信息网站来说,用户的访问量是至关重要的。如果网站的访问量很低,那么就很少有企业会要求为其提供有偿服务,也就没有利润可言了。因此信息网站必须为用户提供大量的、免费的、有价值的信息才能够吸引用户。为此,网站不仅要为企业提供各种有偿服务,还需要额外为用户提供大量的无偿服务。通过与企业的实际接触和沟通,确定网站应包括招聘信息、求职信息、培训信息、公寓信息、家教信息、车辆信息、物品求购、物品出售、求兑出兑、寻求合作、企业广告等服务。

通过实际调查,要求供求信息网具有以下功能:

þ        由于用户的计算机知识普遍偏低,因此要求系统具有良好的人机界面。

þ        方便的供求信息查询,支持多条件和模糊查询。

þ        前台与后台设计明确,并保证后台的安全性。

þ        供求信息显示格式清晰,达到一目了然的效果。

þ        用户不需要注册,便可免费发布供求信息。

þ        免费发布的供求信息,后台必须审核后才能正式发布,避免不良信息。

þ        由于供求信息数据量大,后台应该随时清理数据。

1.2.2  可行性分析

根据《GB8567-88计算机软件产品开发文件编制指南》中可行性分析的要求,制定可行性研究报告如下。

1.引言

þ        编写目的

为了给企业的决策层提供是否进行项目实施的参考依据,现以文件的形式分析项目的风险、项目需要的投资与效益。

þ        背景

××信息科技有限公司是一家以信息产业为主的高科技公司。公司为了扩展业务,需要一个CTC(消费者与消费者之间的交易平台)和BTC(企业为消费者提供的交易平台)业务平台,现需要委托其他公司开发一个提供供求信息的网站,项目名称为供求信息网。

2.可行性研究的前提

þ        要求

网站要求为用户提供求职信息、物品求购、培训信息、家教信息等服务,同时需为企业提供招聘信息、寻求合作和企业广告的服务。

þ        目标

网站的主要目标是为用户及时、准确地提供所需信息,为企业无偿和有偿提供服务。

þ        条件、假定和限制

项目需要在3个月内交付用户使用。系统分析人员需要3天内到位,用户需要5天时间确认需求分析文档。去除其中可能出现的问题,例如用户可能临时有事,占用8天时间确认需求分析。那么程序开发人员需要在2个月零20天的时间内进行系统设计、程序编码、系统测试、程序调试和网站部署工作。其间,还包括了员工每周的休息时间。

þ        评价尺度

根据用户的要求,项目主要以企业服务功能为主(毕竟企业需要向用户付费),因此对于企业的招聘、广告业务需要及时、准确地发布,并且能够对这些信息进行修改。此外,出于安全和国家法律方面的考虑,网站在遭受到黑客攻击时,应在10分钟内进行恢复;对于网站中涉及违反国家法律、法规的内容应能够删除。由于网站的业务量比较大,网站应能够承受同时5万人的点击。

3.投资及效益分析

þ        支出

由于网站的规模比较大,项目周期比较短,仅3个月,因此至少需要13人投入到其中。公司将为此支付11万元的工资及各种福利待遇。在项目安装及调试阶段,用户培训、员工出差等费用支出需要2万元。在项目维护阶段预计需要投入3万元的资金。累计项目投入需要16万元资金。

þ        收益

用户提供项目资金40万元。对于项目运行后进行的改动,采取协商的原则根据改动规模额外提供资金。因此从投资与收益的效益比上,公司可以获得24万元的利润。

项目完成后,会给公司提供资源储备,包括技术、经验的积累,其后再开发类似的项目时,可以极大地缩短项目开发周期。

4.结论

根据上面的分析,在技术上不会存在问题,因此项目延期的可能性很小。在效益上公司投入15个人、3个月的时间获利24万元,比较可观。在公司今后发展上,可以储备网站开发的经验和资源。因此认为该项目可以开发。

1.2.3  编写项目计划书

根据《GB8567-88计算机软件产品开发文件编制指南》中的项目开发计划要求,结合单位实际情况,设计项目计划书如下:

1.引言

þ        编写目的

为了保证项目开发人员按时保质地完成预定目标,更好地了解项目实际情况,按照合理的顺序开展工作,现以书面的形式将项目开发生命周期中的项目任务范围、项目团队组织结构、团队成员的工作责任、团队内外沟通协作方式、开发进度、检查项目工作等内容描述出来,作为项目相关人员之间的共识和约定、项目生命周期内的所有项目活动的行动基础。

þ        背景

供求信息网是由××信息科技有限公司委托我公司开发的大型信息网站,主要功能是为用户无偿提供求职信息、物品求购、培训信息、家教信息等服务,为企业提供招聘信息、寻求合作和企业广告等有偿服务。项目周期为3个月。项目背景规划如表1.1所示。

表1.1  项目背景规划

项 目 名 称

项目委托单位

任务提出者

项目承担部门

供求信息网

××信息科技有限公司

杨经理

研发部门

测试部门

集成部门

2.概述

þ        项目目标

项目目标应当符合SMART原则,把项目要完成的工作用清晰的语言描述出来。供求信息网的项目目标如下:

供求信息网主要针对两类人群,一类是用户,另一类是企业。对于用户,供求信息网需要提供求职信息、公寓信息、物品求购信息、家教信息、物品出售、车辆信息服务。对于企业,供求信息网需要提供寻求合作、企业广告、招聘信息、求兑出兑、培训信息等服务。项目实施后,能够为用户生活带来极大方便,提高企业知名度,为企业产品宣传节约大量成本。整个项目需要在3个月的时间内交付用户使用。

þ        产品目标

当今社会,信息就是资本,信息就是财富。一方面供求信息网能够为企业节省大量人力资源,企业不再需要大量的业务人员去跑市场,从而间接为企业节约了成本;另一方面,供求信息网能够收集大量供求信息,将会有大量用户访问网站,有助于提高企业形象。

þ        应交付成果

Ø         在项目开发完后,交付内容有编译后的供求信息网站、网站数据库文件、网站使用说明书。

Ø         将开发的供求信息网站发布到Internet上。

Ø         网站发布到Internet上后,进行网站无偿维护服务6个月,超过6个月进行网站有偿维护与服务。

þ        项目开发环境

操作系统为Windows XP或Windows 2003均可,使用集成开发工具Microsoft Visual Studio 2005,数据库采用SQL Server 2005,项目运行服务为Internet信息服务(IIS)管理器。

þ        项目验收方式与依据

项目验收分为内部验收和外部验收两种方式。在项目开发完成后,首先进行内部验收,由测试人员根据用户需求和项目目标进行验收。项目在通过内部验收后,交给客户进行验收,验收的主要依据为需求规格说明书。

3.项目团队组织

þ        组织结构

为了完成供求信息网的项目开发,公司组建了一个临时的项目团队,由公司副经理、项目经理、系统分析员、软件工程师、网页设计师和测试人员构成,如图1.1所示。

图1.1  项目团队组织结构

þ        人员分工

为了明确项目团队中每个人的任务分工,现制定人员分工表,如表1.2所示。

表1.2  人员分工

姓    名

技 术 水 平

所 属 部 门

角    色

工 作 描 述

杨某某

MBA

经理部

副经理

负责项目的审批、决策的实施

周某某

MBA

项目开发部

项目经理

负责项目的前期分析、策划、项目开发进度的跟踪、项目质量的检查

刘某某

高级系统分析员

项目开发部

系统分析员

负责系统功能分析、系统框架设计

张某某

中级系统分析员

项目开发部

系统分析员

负责系统功能分析、系统框架设计

赵某某

高级软件工程师

项目开发部

软件工程师

负责软件设计与编码

孙某某

高级软件工程师

项目开发部

软件工程师

负责软件设计与编码

李某某

中级软件工程师

项目开发部

软件工程师

负责软件设计与编码

周某某

初级软件工程师

项目开发部

软件工程师

负责软件编码

曲某某

初级软件工程师

项目开发部

软件工程师

负责软件编码

吕某某

高级美工设计师

设计部

网页设计师

负责网页风格的确定、网页图片的设计

夏某某

中级美工设计师

设计部

网页设计师

负责网页风格的确定、网页图片的设计

续表

姓    名

技 术 水 平

所 属 部 门

角    色

工 作 描 述

梁某某

中级系统测试工程师

项目开发部

测试人员

对软件进行测试、编写软件测试文档

江某某

初级系统测试工程师

项目开发部

测试人员

对软件进行测试、编写软件测试文档

posted @ 2008-05-23 17:15 一他他 阅读(13) | 评论 (0)编辑

在全球知识经济和信息化高速发展的今天,信息化是决定企业成败的关键因素,也是企业实现跨地区、跨行业、跨所有制,特别是跨国经营的重要前提。而电子商务作为一种崭新的商务运作模式,越来越受到企业的重视。本章通过开发一个流行的电子商务网站——供求信息网,介绍如何利用ASP.NET 2.0+SQL Server 2005快速开发一个电子商务平台。通过本章学习,你将学到:

:  供求信息网站开发的基本过程

:  如何进行需求分析和编写项目计划书

:  系统设计的方法

:  如何分析并设计数据库

:  如何设计公共类

:  主要功能模块的实现方法

:  网站编译与发布

:  SQL Server 2005技术

:  面向对象的开发思想

:  分层开发模式

1.1  开发背景

××信息科技有限公司是一家集数据通信、系统集成、电话增值服务于一体的高科技公司。公司为了扩大规模,增强企业的竞争力,决定向多元化发展,借助Internet在国内的快速发展,聚集部分资金投入网站建设,为企业和用户提供综合信息服务,以向企业提供有偿信息服务为盈利方式。例如,提供企业广告、发布招聘信息、寻求合作等服务方式。现需要委托其他单位开发一个信息网站。

posted @ 2008-05-23 17:14 一他他 阅读(13) |