SSHAjax页面缓存页面刷新问题ethen[2010年11月01日]
作者: 日期:2012-05-19
最近用SSH框架做个小应用,在页面上显示数据库的表数据,并且对每行数据都可以进行编辑和删除操作,编辑和删除提交后,利用Ajax发送请求到后台处理数据库的操作,并且更新页面的显示数据。现在问题就来了,删除提交后,页面由4条数据变为3条,但是如果按F5或手动刷新页面后,数据又变为4条,根本没变。问题出在哪里呢?
首先:查看后台数据库,发现表里的数据确实少了一条,即删除操作是成功的,不是数据库操作的原因,那是什么问题呢?
这时候自然地想到了缓存机制,因为应用的是SSH框架,对数据库的管理是通过Spring和Hibernate进行的,而Spring只是一个管理器而已,并非与数据库直接交流的,所以缓存机制是采用的Hibernate的缓存机制(hibernate缓存机制介绍见文章末尾),但是根据Hibernate缓存机制的描述,对程序和配置进行了检查之后,也相应的做了一些改动,但是结果并没有预想的那样,问题还是没有解决,这下彻底茫然了。
当然思考仍在继续,我的工作也没有白做,因为前两个步骤至少证明了一个问题,那就是问题不是出现在服务器端,既然如此,那么肯定是客户端的问题了。
直到这时,问题才初露端倪,客户端说到底就是一个浏览器,因为确定是缓存问题,那么首先就在页面上禁用缓存,设置方法如下:
1、在服务端加 header("Cache-Control: no-cache, must-revalidate");
2、在ajax发送请求前加上xmlHttpRequest.setRequestHeader("If-Modified-Since ","0");
3、在ajax发送请求前加上xmlHttpRequest.setRequestHeader("Cache-Control","n o-cache");
可能不止这几种,具体的做法就是禁止缓存,或者设置内容过期时间等。当然,这时候问题就解决了。这时候回过头来看一下,这页面缓存是什么引起的呢?因为每次删除操作提交后,都是通过Ajax发送请求从数据库里直接拿数据的,应该每次都是不同的呀,为什么会有缓存现象呢?难道是Ajax的原因,就在网上搜了下Ajax缓存,这下就明白了,原来根源是在这里,先把Ajax缓存的原理贴出来:
当Ajax第一次发送请求后,会把请求的URL和返回的响应结果保存在缓存内,当下一次调用Ajax发送相同的请求时,注意,这里相同的请求指的是URL完全相同,包括参数,浏览器就不会与服务器交互,而是直接从缓存中把数据取出来,这是为了提高页面的响应速度和用户体验。这种设计使客户端对一些静态页面内容的请求,比如图片,css文件,js脚本等,变得更加快捷,提高了页面的响应速度,也节省了网络通信资源。但是对于一些后台数据的提取时就需要做一些改变,否则虽然数据在后台已经发生改变,但是页面缓存中并没有改变,对于相同的URL,浏览器只是简单的从缓存中拿数据(有点罗嗦了)。问题找到了,怎么解决呢?其实办法很简单,只要让每次请求的URL不同就可以了。方法是在URL后面加一个参数,参数的值每次都不同,可以是一个随机数(Math.random()),也可以是当前时间(new Date().getTime()),只要满足条件即可。当然,以上这些只是针对Ajax发送的GET请求,如果你用POST方法发送请求,所有的工作都省了。但是你真的愿意这么做么?
附:hibernate缓存机制介绍(传送门:http://tenn.javaeye.com/blog/120559): 1:Hibernate缓存概述 首先在介绍Hibernate缓存之前,笔者在这里做一个小小的比喻,让大家先知道利用缓存的好处。 这个比喻设计的事物有四个,一个是消费者,一个是该消费者附近的电脑城,一个是联想笔记本,一个是联想公司。笔记本是现在普及的商品,消费者想要去买一台联想笔记本,大家想想看,是去附近的电脑城买得快?还是去联想公司买得快?..当然是在电脑城买得快咯,总不能跑到联想公司去买吧?在这里消费者被比做应用程序,电脑城被比做缓存,联想笔记本被比做数据,联想公司被比做数据库。正像我们比喻的那样,应用程序查找我们需要的数据是从缓存中找得快,还是去数据库找得快?答案应该就不用我讲了吧! 缓存是介于物理数据源与应用程序之间,是数据库数据在内存中的存放临时copy的容器,是其作用是为了减少应用程序对物理数据源访问的次数,从而提高了应用的运行性能。 Hibernate在进行读取数据的时候,根据缓存机制在相应的缓存中查询,如果在缓存中找到了需要的数据(我们把这称做“缓存命中”),则就直接把命中的数据作为结果加以利用,避免的了建立数据库查询的性能损耗。说白话点就是,数据放在缓存中,当应用程序还需要他们的时候,就不必再去查数据库了,根据缓存策略从内存中查找速度就会快很多了。 2:Hibernate缓存分类 Hibernate缓存我们通常分两类,一类称为一级缓存也叫内部缓存,另一类称为二级缓存。Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝,那么它们之间的区别是什么呢?为了理解二者的区别,需要深入理解持久化层的缓存的一个特性:缓存的范围。 缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。 (1) 事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式, 一级缓存就属于事务范围。 (2) 应用范围:缓存被应用范围内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于应用的生命周期,应用结束时,缓存也就结束了生命周期,二级缓存存在于应用范围。 (3) 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式,二级缓存也存在与应用范围。 注意:对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问它的速度不一定会比直接访问数据库数据的速度快多少,再加上集群范围还有数据同步的问题,所以应当慎用。 持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,还可以到应用范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询了。 3:Hibernate缓存运用与管理 本小节,我们来看看Hibernate的缓存管理,除了我们通常分的两类缓存外,笔者再介绍“查询缓存”,它依赖于二级缓存。 内部缓存:前面我们提到内部缓存是属于事物级缓存,在正常的情况下是由Hibernate自动维护的。当然在特殊的情况下需要我们进行手动维护,Hibernate就提供了以下几种方法供开发者选择: (1)Session.evict(XXX) 将某个特定的对象从内部缓存中清除,上述的XXX 为对象的实例名。使用此方法有两种适用情形,一是在特定的操作(如批量处理),需要及时释放对象占用的内存维持系统的稳定性,笔者的关于批处理的文章中就运用了此方法,有兴趣的朋友可关注IT168“国庆加油站”技术栏目。二是不希望当前Session继续运用此对象的状态变化来同步更新数据库。 (2)Session.clear() 清除缓存中的所有持久化对象。 二级缓存:在第2节的论述中我们知道,二级缓存涵盖了应用范围与集群范围。这里问题就来了,我们什么情况下要使用二级缓存?如果满足以下条件,则可以将其纳入二级缓存:(1)数据不会被第三放修改 (2)同一数据系统经常引用 (3)数据大小在可接受范围之内 (4)非关键数据,或不会被并发的数据 Hibernate本身并不提供二级缓存的产品化实现,而是为众多支持Hibernate的第三方缓存组件提供整和接口。笔者这里仅仅介绍现在主流的EHCache,它更具备良好的调度性能。 首先,Hibernate启用二级缓存,需要的在主配置文件hibernate.cfg.xml中配置以下参数(以EHCache为例子,使用Hibernate3 <hibernate-configuration> <session-factory> ………… <property name=”hibernate.cache.provider_class”> org.ehcache.hibernate.Provider </property> ………… </session-factory> </hibernate-configuration> 另外还需要对ehcache.xml进行配置,这是一个单独的xml文件,示例如下: ehcache.xml <defaultCache maxElementsInMemory="10000" //缓存中最大允许创建的对象数 eternal="false" //缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期 timeToIdleSeconds="120" //缓存数据钝化时间(设置对象在它过期之前的空闲时间) timeToLiveSeconds="120" //缓存数据的生存时间(设置对象在它过期之前的生存时间) overflowToDisk="true" //内存不足时,是否启用磁盘缓存 /> 然后呢,我们还需要在映射文件中指定的映射实体的缓存同步策略(以下只列出核心配置,以供大家参考): ………… <class name=”com.tenly.bean.Student”> <cache usage=”read-write”> ………… <set name=”classroom”……> <cache usage=”read-only”> ………… </set> </class> ………… 上面提到read-write、read-only是什么?这就是缓存的同步策略,下面我们来仔细的看看Hibernate提供的几钟缓存策略: (1) read-only 只读。对于不会发生改变的数据,可使用只读型缓存。 (2)nonstrict-read-write 不严格可读写缓存。如果应用程序对并发访问下的数据同步要求不是很严格的话,而且数据更新操作频率较低。采用本项,可获得良好的性能。 (3) read-write 对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题. (4)transactional(事物型) 在Hibernate中,事务型缓存必须运行在JTA事务环境中。 查询缓存:我们前面提到查询缓存(Query Cache)依赖二级缓存,这到底是怎么回事呢?我看看二级缓存策略的一般过程: (1) Hibernate进行条件查询的时候,总是发出一条select * from XXX where …(XXX为 表名,类似的语句下文统称Select SQL)这样的SQL语句查询数据库,一次获得所有的符合条件的数据对象。 (2) 把获得的所有数据对象根据ID放入到第二级缓存中。 (3) 当Hibernate根据ID访问数据对象的时候,首先从内部缓存中查找,如果在内部缓存中查不到就配置二级缓存,从二级缓存中查;如果还查不到,再查询数据库,把结果按照ID放入到缓存。 (4)添加数据、删除、更新操作时,同时更新二级缓存。这就是Hibernate做批处理的时候效率不高的原因,原来是要维护二级缓存消耗大量时间的缘故。 我们看到这个过程后,可以明显的发现什么?那就是Hibernate的二级缓存策略是针对ID查询的策略,和对象ID密切相关,那么对于条件查询就怎么适用了。对于这种情况的存在,Hibernate引入了“查询缓存”在一定程度上缓解这个问题。 那么我们先来看看我们为什么使用查询缓存?首先我们来思考一个问题,假如我们对数据表Student进行查询操作,查找age>20的所有学生信息,然后纳入二级缓存;第二次我们的查询条件变了,查找age>15的所有学生信息,显然第一次查询的结果完全满足第二次查询的条件,但并不是满足条件的全部数据。这样的话,我们就要再做一次查询得到全部数据才行。再想想,如果我们执行的是相同的条件语句,那么是不是可以利用之前的结果集呢? Hibernate就是为了解决这个问题的而引入Query Cache的。 查询缓存策略的一般过程如下: (1)Query Cache保存了之前查询的执行过的Select SQL,以及结果集等信息,组成一个Query Key。(2)当再次遇到查询请求的时候,就会根据Query Key 从Query Cache找,找到就返回。但 是两次查询之间,数据表发生数据变动的话,Hibernate就会自动清除Query Cache中对应的Query Key。 我们从查询缓存的策略中可以看出,Query Cache只是在特定的条件下才会发挥作用,而且要求相当严格: (1)完全相同的Select SQL重复执行。 (2)重复执行期间,Query Key对应的数据表不能有数据变动(比如添、删、改操作) 为了启用Query Cache,我们需要在hibernate.cfg.xml中进行配置,参考配置如下(只列出核心配置项): <hibernate-configuration> <session-factory> ………… <property name=”hibernate.cache.user_query_cache”>true</property> ………… </session-factory> </hibernate-configuration> 应用程序中必须在查询执行之前,将Query.Cacheable设置为true,而且每次都应该这样。比如: ……… Query query=session.createQuery(hql).setInteger(0.15); query.setCacheable(true); ………
首先:查看后台数据库,发现表里的数据确实少了一条,即删除操作是成功的,不是数据库操作的原因,那是什么问题呢?
这时候自然地想到了缓存机制,因为应用的是SSH框架,对数据库的管理是通过Spring和Hibernate进行的,而Spring只是一个管理器而已,并非与数据库直接交流的,所以缓存机制是采用的Hibernate的缓存机制(hibernate缓存机制介绍见文章末尾),但是根据Hibernate缓存机制的描述,对程序和配置进行了检查之后,也相应的做了一些改动,但是结果并没有预想的那样,问题还是没有解决,这下彻底茫然了。
当然思考仍在继续,我的工作也没有白做,因为前两个步骤至少证明了一个问题,那就是问题不是出现在服务器端,既然如此,那么肯定是客户端的问题了。
直到这时,问题才初露端倪,客户端说到底就是一个浏览器,因为确定是缓存问题,那么首先就在页面上禁用缓存,设置方法如下:
1、在服务端加 header("Cache-Control: no-cache, must-revalidate");
2、在ajax发送请求前加上xmlHttpRequest.setRequestHeader("If-Modified-Since ","0");
3、在ajax发送请求前加上xmlHttpRequest.setRequestHeader("Cache-Control","n o-cache");
可能不止这几种,具体的做法就是禁止缓存,或者设置内容过期时间等。当然,这时候问题就解决了。这时候回过头来看一下,这页面缓存是什么引起的呢?因为每次删除操作提交后,都是通过Ajax发送请求从数据库里直接拿数据的,应该每次都是不同的呀,为什么会有缓存现象呢?难道是Ajax的原因,就在网上搜了下Ajax缓存,这下就明白了,原来根源是在这里,先把Ajax缓存的原理贴出来:
当Ajax第一次发送请求后,会把请求的URL和返回的响应结果保存在缓存内,当下一次调用Ajax发送相同的请求时,注意,这里相同的请求指的是URL完全相同,包括参数,浏览器就不会与服务器交互,而是直接从缓存中把数据取出来,这是为了提高页面的响应速度和用户体验。这种设计使客户端对一些静态页面内容的请求,比如图片,css文件,js脚本等,变得更加快捷,提高了页面的响应速度,也节省了网络通信资源。但是对于一些后台数据的提取时就需要做一些改变,否则虽然数据在后台已经发生改变,但是页面缓存中并没有改变,对于相同的URL,浏览器只是简单的从缓存中拿数据(有点罗嗦了)。问题找到了,怎么解决呢?其实办法很简单,只要让每次请求的URL不同就可以了。方法是在URL后面加一个参数,参数的值每次都不同,可以是一个随机数(Math.random()),也可以是当前时间(new Date().getTime()),只要满足条件即可。当然,以上这些只是针对Ajax发送的GET请求,如果你用POST方法发送请求,所有的工作都省了。但是你真的愿意这么做么?
附:hibernate缓存机制介绍(传送门:http://tenn.javaeye.com/blog/120559): 1:Hibernate缓存概述 首先在介绍Hibernate缓存之前,笔者在这里做一个小小的比喻,让大家先知道利用缓存的好处。 这个比喻设计的事物有四个,一个是消费者,一个是该消费者附近的电脑城,一个是联想笔记本,一个是联想公司。笔记本是现在普及的商品,消费者想要去买一台联想笔记本,大家想想看,是去附近的电脑城买得快?还是去联想公司买得快?..当然是在电脑城买得快咯,总不能跑到联想公司去买吧?在这里消费者被比做应用程序,电脑城被比做缓存,联想笔记本被比做数据,联想公司被比做数据库。正像我们比喻的那样,应用程序查找我们需要的数据是从缓存中找得快,还是去数据库找得快?答案应该就不用我讲了吧! 缓存是介于物理数据源与应用程序之间,是数据库数据在内存中的存放临时copy的容器,是其作用是为了减少应用程序对物理数据源访问的次数,从而提高了应用的运行性能。 Hibernate在进行读取数据的时候,根据缓存机制在相应的缓存中查询,如果在缓存中找到了需要的数据(我们把这称做“缓存命中”),则就直接把命中的数据作为结果加以利用,避免的了建立数据库查询的性能损耗。说白话点就是,数据放在缓存中,当应用程序还需要他们的时候,就不必再去查数据库了,根据缓存策略从内存中查找速度就会快很多了。 2:Hibernate缓存分类 Hibernate缓存我们通常分两类,一类称为一级缓存也叫内部缓存,另一类称为二级缓存。Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝,那么它们之间的区别是什么呢?为了理解二者的区别,需要深入理解持久化层的缓存的一个特性:缓存的范围。 缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。 (1) 事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式, 一级缓存就属于事务范围。 (2) 应用范围:缓存被应用范围内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于应用的生命周期,应用结束时,缓存也就结束了生命周期,二级缓存存在于应用范围。 (3) 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式,二级缓存也存在与应用范围。 注意:对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问它的速度不一定会比直接访问数据库数据的速度快多少,再加上集群范围还有数据同步的问题,所以应当慎用。 持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,还可以到应用范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询了。 3:Hibernate缓存运用与管理 本小节,我们来看看Hibernate的缓存管理,除了我们通常分的两类缓存外,笔者再介绍“查询缓存”,它依赖于二级缓存。 内部缓存:前面我们提到内部缓存是属于事物级缓存,在正常的情况下是由Hibernate自动维护的。当然在特殊的情况下需要我们进行手动维护,Hibernate就提供了以下几种方法供开发者选择: (1)Session.evict(XXX) 将某个特定的对象从内部缓存中清除,上述的XXX 为对象的实例名。使用此方法有两种适用情形,一是在特定的操作(如批量处理),需要及时释放对象占用的内存维持系统的稳定性,笔者的关于批处理的文章中就运用了此方法,有兴趣的朋友可关注IT168“国庆加油站”技术栏目。二是不希望当前Session继续运用此对象的状态变化来同步更新数据库。 (2)Session.clear() 清除缓存中的所有持久化对象。 二级缓存:在第2节的论述中我们知道,二级缓存涵盖了应用范围与集群范围。这里问题就来了,我们什么情况下要使用二级缓存?如果满足以下条件,则可以将其纳入二级缓存:(1)数据不会被第三放修改 (2)同一数据系统经常引用 (3)数据大小在可接受范围之内 (4)非关键数据,或不会被并发的数据 Hibernate本身并不提供二级缓存的产品化实现,而是为众多支持Hibernate的第三方缓存组件提供整和接口。笔者这里仅仅介绍现在主流的EHCache,它更具备良好的调度性能。 首先,Hibernate启用二级缓存,需要的在主配置文件hibernate.cfg.xml中配置以下参数(以EHCache为例子,使用Hibernate3 <hibernate-configuration> <session-factory> ………… <property name=”hibernate.cache.provider_class”> org.ehcache.hibernate.Provider </property> ………… </session-factory> </hibernate-configuration> 另外还需要对ehcache.xml进行配置,这是一个单独的xml文件,示例如下: ehcache.xml <defaultCache maxElementsInMemory="10000" //缓存中最大允许创建的对象数 eternal="false" //缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期 timeToIdleSeconds="120" //缓存数据钝化时间(设置对象在它过期之前的空闲时间) timeToLiveSeconds="120" //缓存数据的生存时间(设置对象在它过期之前的生存时间) overflowToDisk="true" //内存不足时,是否启用磁盘缓存 /> 然后呢,我们还需要在映射文件中指定的映射实体的缓存同步策略(以下只列出核心配置,以供大家参考): ………… <class name=”com.tenly.bean.Student”> <cache usage=”read-write”> ………… <set name=”classroom”……> <cache usage=”read-only”> ………… </set> </class> ………… 上面提到read-write、read-only是什么?这就是缓存的同步策略,下面我们来仔细的看看Hibernate提供的几钟缓存策略: (1) read-only 只读。对于不会发生改变的数据,可使用只读型缓存。 (2)nonstrict-read-write 不严格可读写缓存。如果应用程序对并发访问下的数据同步要求不是很严格的话,而且数据更新操作频率较低。采用本项,可获得良好的性能。 (3) read-write 对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题. (4)transactional(事物型) 在Hibernate中,事务型缓存必须运行在JTA事务环境中。 查询缓存:我们前面提到查询缓存(Query Cache)依赖二级缓存,这到底是怎么回事呢?我看看二级缓存策略的一般过程: (1) Hibernate进行条件查询的时候,总是发出一条select * from XXX where …(XXX为 表名,类似的语句下文统称Select SQL)这样的SQL语句查询数据库,一次获得所有的符合条件的数据对象。 (2) 把获得的所有数据对象根据ID放入到第二级缓存中。 (3) 当Hibernate根据ID访问数据对象的时候,首先从内部缓存中查找,如果在内部缓存中查不到就配置二级缓存,从二级缓存中查;如果还查不到,再查询数据库,把结果按照ID放入到缓存。 (4)添加数据、删除、更新操作时,同时更新二级缓存。这就是Hibernate做批处理的时候效率不高的原因,原来是要维护二级缓存消耗大量时间的缘故。 我们看到这个过程后,可以明显的发现什么?那就是Hibernate的二级缓存策略是针对ID查询的策略,和对象ID密切相关,那么对于条件查询就怎么适用了。对于这种情况的存在,Hibernate引入了“查询缓存”在一定程度上缓解这个问题。 那么我们先来看看我们为什么使用查询缓存?首先我们来思考一个问题,假如我们对数据表Student进行查询操作,查找age>20的所有学生信息,然后纳入二级缓存;第二次我们的查询条件变了,查找age>15的所有学生信息,显然第一次查询的结果完全满足第二次查询的条件,但并不是满足条件的全部数据。这样的话,我们就要再做一次查询得到全部数据才行。再想想,如果我们执行的是相同的条件语句,那么是不是可以利用之前的结果集呢? Hibernate就是为了解决这个问题的而引入Query Cache的。 查询缓存策略的一般过程如下: (1)Query Cache保存了之前查询的执行过的Select SQL,以及结果集等信息,组成一个Query Key。(2)当再次遇到查询请求的时候,就会根据Query Key 从Query Cache找,找到就返回。但 是两次查询之间,数据表发生数据变动的话,Hibernate就会自动清除Query Cache中对应的Query Key。 我们从查询缓存的策略中可以看出,Query Cache只是在特定的条件下才会发挥作用,而且要求相当严格: (1)完全相同的Select SQL重复执行。 (2)重复执行期间,Query Key对应的数据表不能有数据变动(比如添、删、改操作) 为了启用Query Cache,我们需要在hibernate.cfg.xml中进行配置,参考配置如下(只列出核心配置项): <hibernate-configuration> <session-factory> ………… <property name=”hibernate.cache.user_query_cache”>true</property> ………… </session-factory> </hibernate-configuration> 应用程序中必须在查询执行之前,将Query.Cacheable设置为true,而且每次都应该这样。比如: ……… Query query=session.createQuery(hql).setInteger(0.15); query.setCacheable(true); ………
Tags: ajax
10个C#编程和VisualStudio使用技巧[2011年04月21日]
作者: 日期:2012-05-18
C#是一门伟大的编程语言,与C++和Java相比,它的语法更简单,相对来说更好入门。Visual Studio作为.Net平台上最重量级的IDE,也通过不断的更新为开发者带来更出色的开发体验。本文将介绍10个C#编程和Visual Studio IDE使用技巧。
C#是一门伟大的编程语言,与C++和Java相比,它的语法更简单,相对来说更好入门,经历10年的发展,C#已经成为编程语言领域强有力的竞争者,每一年我们都能看到它的进步,每一个新版本都加入了许多新特性,总的来说,作为一门编程语言,它没有让C#开发者社区失望。Visual Studio亦是如此,新版本的Visual Studio 2010所带来的新特性也让开发者们兴奋不已。本文下篇请点击这里。
对开场白没兴趣?好吧,我们直接切入正题,下面介绍10个C#编程和Visual Studio IDE使用技巧。
1、Environment.Newline
你是否知道这个属性是与平台无关的?允许你根据每个平台输出新的换行字符。 Console.WriteLine("My Tips On ,{0}C#", Environment.NewLine);
2、命名空间别名
你是否知道可以使用更短的别名代替长的命名空间?你是否遇到过需要限制完整的命名空间以避免产生歧义?看下面的代码示例,它是使用扩展的.NET Framework控件创建的一个通用库。 using System.Web.UI.WebControls;
using MyGenericLibrary.UserControls;
using System.Web.UI.WebControls;
usingmc=MyGenericLibrary.UserControls;
/*and then use, /*
mc.TextBoxtextbox=newmc.TextBox();
3、DebuggerBrowsable属性
每个C#开发人员应该都有过程序调试的经历,这个属性在调试期间控制对象行为的能力非常强大,在调试过程中它在一个小提示窗口中显示对象,它可以用于隐藏私有成员或在调试窗口中显示也是多余的成员,例如,当你调试类对象时,在调试窗口中你可以看到私有变量,这个时候你就可以使用[DebuggerBrowsable(DebuggerBrowsableState.Never)]属性来隐藏它们,下面是可见的代码。 public class MyClass
{
private string _id;
public string InternalID
{
get { return _id; }
set {_id=value; } } }
下面是使之隐藏的代码: [DebuggerBrowsable(DebuggerBrowsableState.Never)]
public class MyClass
{
private string _id;
public string InternalID
{
get { return _id; }
set {_id=value; } } }
4、DebuggerDisplay属性
这个属性可让具有可读描述的变量对象显示出来,它有助于提供团队其它成员未来阅读代码的效率,它的用法也是非常简单的,下面的代码示例显示了变量的值。 public class MyClass
{
[DebuggerDisplay("Value= {myVariable}")]
public stringmyVariable="mydisplay"; }
5、为项目创建虚拟目录
你可以强制每个开发人员在本地为项目创建一个同名的虚拟目录,这个来自Visual Studio IDE的技巧将有助于代码在多个C#开发人员的电脑之间同步。在项目名称上点击右键,选择“属性”,在“Web”选项卡中,选中“使用本地IIS Web服务器”选项,然后为其指定一个虚拟路径,如下图所示。
图 1 设置项目的本地虚拟目录路径
这样设置后,所有使用该项目文件的开发人员都会收到一个要求,在本地机器上创建一个同名的虚拟目录。
6、改变项目平台
你可以改变应用程序的生成目标平台,这里的平台指的是32位和64位环境,在项目名称上点击右键,选择“属性”,在“Build”选项卡中,选择需要的目标平台,如下图所示。
图 2 修改项目的目标平台
7、代码定义窗口
这个窗口允许你跳转到对象的定义,你可以按F12键快速跳转到对象的定义位置,在代码编辑器的任意对象上试试这个功能,相信一定不会让你失望的。此外,还有一个专门的代码定义窗口,当你按照Ctrl+W,D组合键时就会弹出一个代码定义窗口。 if (e.Item.ItemType== ListItemType.Item )
{
//Your code here.
}
如果你将光标停留在ListItemType上面,然后按下组合键,你将会看到如下图所示的一个窗口。
图 3 代码定义窗口
8、Null合并运算符
Null合并运算符允许你以很简洁的方式比较空值,它使用两个问号表示。例如,myfunction返回的值可能是一个空的整数值,在这种情况下,你可以使用合并运算符快速检查它是否为空,然后返回一个代替值。 intmyExpectedValueIfNull=10;
intexpectedValue=myfunction() ?? myExpectedValueIfNull
9、using语句快捷键
按下Ctrl+.会弹出一列可用的using语句,使用箭头键进行移动,按下回车键确认选择,如下图所示。
图 4 在代码编辑器中快速调出using语句
10、寻找恐怖的数据集合并错误根本原因
你是否遇到过无法找出数据集合并错误的原因?现在有办法了,使用try-catch将你的代码包围起来,最好是在异常处理块中观察特定代码的输出,可以准确捕捉到合并失败的原因。 StringBuilder errorMessages=newStringBuilder();
try {
DataSetdataSet1=populateDataSet(1);
DataSetdataSet2=populateDataSet(2);
dataset1.Merge(dataset2);
}
catch (System.Data.DataException de)
{
foreach (DataTable myTable in dataSet1.Tables)
{
foreach (DataRow myRow in myTable.GetErrors())
{
foreach (DataColumn myColumn in myRow.GetColumnsInError())
{
//loop through each column in the row that has caused the error
//during the bind and show it.
error Messages .Append(string.Format(
"Merge failed due to : {0}", myColumn.GetColumnError(myColumn)));
} } } }
小结 希望你能灵活运用这些C#编程和Visual Studio技巧,享受写代码的乐趣,如果你有其它技巧愿意和大家分享,欢迎在本文后面发表评论。 【51CTO独家特稿】如果你通过搜索引擎发现这篇文章的,我建议你先看看本系列的第一篇,这是本系列文章的第二篇,今天为大家带来更丰富的C#和Visual Studio编程技巧,一起来看看吧。
51CTO向您推荐:《10个C#编程和Visual Studio使用技巧》
1、DataTable.HasRows
它不属于任何框架,但通过扩展方法很容易模仿这样一个方法,它不会消除检查数据表对象是否为空或行数的原始代码,但它可以简化应用程序的代码,下面是一个代码片段: <CODE>
public static bool HasRows(this DataTable dataTable)
{
return dataTable.IsNull() ? false : (dataTable.Rows.Count>0);
}
public static bool IsNull(this object o)
{
return (o== null);
} To use:
If(dataTable.HasRows())
{ … } </CODE>
其它规则仍然和扩展方法相同。
2、ToTitleCase
这个方法可以将每个单词的首字母转换为大写,剩下的字母转换为小写,例如,“look below for a sample”将被转换为“Look Below For A Sample”,TextInfo是System.Globalization命名空间的一部分,但它存在以下问题:
当前的文化
如果输入字符串全部是大写
下面的扩展方法同时考虑了这两个缺陷。 <CODE>
public static string ToTitleCase(this string inputString)
{
return Thread.CurrentThread.CurrentCulture.TextInfo.
ToTitleCase((inputString ?? string.Empty).ToLower());
} </CODE>
3、显性和隐性接口实现
这很重要吗?是的,非常重要,你知道它们之间的语法差异吗?其实它们存在根本性的区别。类上的隐性接口实现默认是一个公共方法,在类的对象或接口上都可以访问。而类上的显性接口实现默认是一个私有方法,只能通过接口访问,不能通过类的对象访问。下面是示例代码: <CODE>
INTERFACE
public interface IMyInterface
{
void MyMethod(string myString);
}
CLASS THAT IMPLEMENTS THE INTERFACE IMPLICITLY
public MyImplicitClass: IMyInterface
{
public void MyMethod(string myString)
{ /// } }
CLASS THAT IMPLEMENTS THE INTERFACE EXPLICITLY
public MyExplicitClass: IMyInterface
{
void IMyInterface.MyMethod(string myString)
{ /// } }
MyImplicitClass instance would work with either the class or the Interface:
MyImplicitClassmyObject=newMyImplicitClass();
myObject.MyMethod("");
IMyInterfacemyObject=newMyImplicitClass();
myObject.MyMethod("");
MyExplicitClass would work only with the interface:
//The following line would not work.
MyExplicitClassmyObject=newMyExplicitClass();
myObject.MyMethod("");
//This will work
IMyInterfacemyObject=newMyExplicitClass();
myObject.MyMethod("");
</CODE>
4、Auto属性
它是替换包含一个公共,两个私有成员属性的最好办法。
按下两次Tab键(需要开启代码片段功能),一个Auto属性就创建好了,再按Tab键Auto属性取一个名字。下面这段代码 <CODE>
private double _total;
public double Total
{
get { return _total; }
set {_total=value; } } </CODE>
就变成了 <CODE>
public double Total { get; set; }
</CODE>
注意你仍然可以根据你的设计应用访问说明符,编译器应该会为你创建私有成员变量。 5、强大的Path.Combine
Path.Combine凭借强大的功能消除了尾斜线和路径相关的问题,简单易用,让路径字符串更连续,它包含一个字符串路径参数,如图1所示。
图 1 Path.Combine方法
你不用担心路径中的有效分隔符或空格,完全不用你处理路径合并时的字符串连接。
6、在类中编写“Override”方法的快速方法
在代码编辑器中输入override,按空格键,你将会看到一串基于类的可覆写方法,如图2所示。
图 2 可覆写方法列表
7、使用扩展的配置文件
感谢app.config(针对应用程序)和web.config配置文件,使我们可以处理复杂的应用程序级设置,但是我们仍然要处理不同环境设置面临的各种问题,这里指的是开发、测试和生产环境的设置。
我们不得不恢复到一个特定的环境以便进行分析、测试或调式部分代码,在这个过程中,每一次设置和调整都很乏味。
例如,每一次恢复可能都要重新设置ConnectionStrings(连接字符串),现在你可以通过外部文件引用使用ConfigSource属性来解决这个问题。例如,下面的代码引用了一个deveploment.config外部配置文件。 <connectionStringsconfigSource="configs\ development.config"/>
你还可以在AppSettings设置小节使用这个有用的属性。
8、克服String.Split方法的局限
String.Split是分隔字符串最理想的方法,但据我们所知,它也有一些限制,如不能使用“||”或“::”这样的字符,必须使用键盘上独一无二的单个字符作为分隔符,这个缺点可以使用RegEx库提供的Split方法来克服掉,下面的代码显示了使用RegEx Split分隔一个“||”分隔字符串。 <CODE>
stringdelimitedString ="String.Split || RegEx.Split");
string[] ouputString = System.Text.RegularExpressions.Regex.Split(
delimitedString,
, System.Text.RegularExpressions.Regex.Escape("||"));
</CODE>
9、元素的HTML代码视图和设计视图之间的快速切换(反之亦然)
在设计应用程序时,我们在IDE上花费的时间很多,大部分时间都耗在HTML内容和设计视图上,Visual Studio 2010提供了设计视图和HTML代码之间快速切换的功能。
如果你在HTML视图中,定位你想在设计视图中查看的元素,然后切换到设计视图,你想查看的元素应该处于选中状态,此外,属性窗口现在也应该显示的是选中元素的属性。
与此类似,当你在设计视图中,选中元素,然后切换到代码视图,你选中的元素对应的HTML代码应该是高亮状态。
10、快速搜索数据库中的数据
虽然数据表支持Find和Select方法选择行,但它们都没有DataView的方法好用,DataView提供了一个FindRows方法,它可以使用排序列上创建的索引,因此速度更快。
希望这些技巧可以帮助你节省宝贵的编程时间,赶快去试试吧!
原文标题:10 More C# Programming and Microsoft Visual Studio
C#是一门伟大的编程语言,与C++和Java相比,它的语法更简单,相对来说更好入门,经历10年的发展,C#已经成为编程语言领域强有力的竞争者,每一年我们都能看到它的进步,每一个新版本都加入了许多新特性,总的来说,作为一门编程语言,它没有让C#开发者社区失望。Visual Studio亦是如此,新版本的Visual Studio 2010所带来的新特性也让开发者们兴奋不已。本文下篇请点击这里。
对开场白没兴趣?好吧,我们直接切入正题,下面介绍10个C#编程和Visual Studio IDE使用技巧。
1、Environment.Newline
你是否知道这个属性是与平台无关的?允许你根据每个平台输出新的换行字符。 Console.WriteLine("My Tips On ,{0}C#", Environment.NewLine);
2、命名空间别名
你是否知道可以使用更短的别名代替长的命名空间?你是否遇到过需要限制完整的命名空间以避免产生歧义?看下面的代码示例,它是使用扩展的.NET Framework控件创建的一个通用库。 using System.Web.UI.WebControls;
using MyGenericLibrary.UserControls;
using System.Web.UI.WebControls;
usingmc=MyGenericLibrary.UserControls;
/*and then use, /*
mc.TextBoxtextbox=newmc.TextBox();
3、DebuggerBrowsable属性
每个C#开发人员应该都有过程序调试的经历,这个属性在调试期间控制对象行为的能力非常强大,在调试过程中它在一个小提示窗口中显示对象,它可以用于隐藏私有成员或在调试窗口中显示也是多余的成员,例如,当你调试类对象时,在调试窗口中你可以看到私有变量,这个时候你就可以使用[DebuggerBrowsable(DebuggerBrowsableState.Never)]属性来隐藏它们,下面是可见的代码。 public class MyClass
{
private string _id;
public string InternalID
{
get { return _id; }
set {_id=value; } } }
下面是使之隐藏的代码: [DebuggerBrowsable(DebuggerBrowsableState.Never)]
public class MyClass
{
private string _id;
public string InternalID
{
get { return _id; }
set {_id=value; } } }
4、DebuggerDisplay属性
这个属性可让具有可读描述的变量对象显示出来,它有助于提供团队其它成员未来阅读代码的效率,它的用法也是非常简单的,下面的代码示例显示了变量的值。 public class MyClass
{
[DebuggerDisplay("Value= {myVariable}")]
public stringmyVariable="mydisplay"; }
5、为项目创建虚拟目录
你可以强制每个开发人员在本地为项目创建一个同名的虚拟目录,这个来自Visual Studio IDE的技巧将有助于代码在多个C#开发人员的电脑之间同步。在项目名称上点击右键,选择“属性”,在“Web”选项卡中,选中“使用本地IIS Web服务器”选项,然后为其指定一个虚拟路径,如下图所示。
图 1 设置项目的本地虚拟目录路径
这样设置后,所有使用该项目文件的开发人员都会收到一个要求,在本地机器上创建一个同名的虚拟目录。
6、改变项目平台
你可以改变应用程序的生成目标平台,这里的平台指的是32位和64位环境,在项目名称上点击右键,选择“属性”,在“Build”选项卡中,选择需要的目标平台,如下图所示。
图 2 修改项目的目标平台
7、代码定义窗口
这个窗口允许你跳转到对象的定义,你可以按F12键快速跳转到对象的定义位置,在代码编辑器的任意对象上试试这个功能,相信一定不会让你失望的。此外,还有一个专门的代码定义窗口,当你按照Ctrl+W,D组合键时就会弹出一个代码定义窗口。 if (e.Item.ItemType== ListItemType.Item )
{
//Your code here.
}
如果你将光标停留在ListItemType上面,然后按下组合键,你将会看到如下图所示的一个窗口。
图 3 代码定义窗口
8、Null合并运算符
Null合并运算符允许你以很简洁的方式比较空值,它使用两个问号表示。例如,myfunction返回的值可能是一个空的整数值,在这种情况下,你可以使用合并运算符快速检查它是否为空,然后返回一个代替值。 intmyExpectedValueIfNull=10;
intexpectedValue=myfunction() ?? myExpectedValueIfNull
9、using语句快捷键
按下Ctrl+.会弹出一列可用的using语句,使用箭头键进行移动,按下回车键确认选择,如下图所示。
图 4 在代码编辑器中快速调出using语句
10、寻找恐怖的数据集合并错误根本原因
你是否遇到过无法找出数据集合并错误的原因?现在有办法了,使用try-catch将你的代码包围起来,最好是在异常处理块中观察特定代码的输出,可以准确捕捉到合并失败的原因。 StringBuilder errorMessages=newStringBuilder();
try {
DataSetdataSet1=populateDataSet(1);
DataSetdataSet2=populateDataSet(2);
dataset1.Merge(dataset2);
}
catch (System.Data.DataException de)
{
foreach (DataTable myTable in dataSet1.Tables)
{
foreach (DataRow myRow in myTable.GetErrors())
{
foreach (DataColumn myColumn in myRow.GetColumnsInError())
{
//loop through each column in the row that has caused the error
//during the bind and show it.
error Messages .Append(string.Format(
"Merge failed due to : {0}", myColumn.GetColumnError(myColumn)));
} } } }
小结 希望你能灵活运用这些C#编程和Visual Studio技巧,享受写代码的乐趣,如果你有其它技巧愿意和大家分享,欢迎在本文后面发表评论。 【51CTO独家特稿】如果你通过搜索引擎发现这篇文章的,我建议你先看看本系列的第一篇,这是本系列文章的第二篇,今天为大家带来更丰富的C#和Visual Studio编程技巧,一起来看看吧。
51CTO向您推荐:《10个C#编程和Visual Studio使用技巧》
1、DataTable.HasRows
它不属于任何框架,但通过扩展方法很容易模仿这样一个方法,它不会消除检查数据表对象是否为空或行数的原始代码,但它可以简化应用程序的代码,下面是一个代码片段: <CODE>
public static bool HasRows(this DataTable dataTable)
{
return dataTable.IsNull() ? false : (dataTable.Rows.Count>0);
}
public static bool IsNull(this object o)
{
return (o== null);
} To use:
If(dataTable.HasRows())
{ … } </CODE>
其它规则仍然和扩展方法相同。
2、ToTitleCase
这个方法可以将每个单词的首字母转换为大写,剩下的字母转换为小写,例如,“look below for a sample”将被转换为“Look Below For A Sample”,TextInfo是System.Globalization命名空间的一部分,但它存在以下问题:
当前的文化
如果输入字符串全部是大写
下面的扩展方法同时考虑了这两个缺陷。 <CODE>
public static string ToTitleCase(this string inputString)
{
return Thread.CurrentThread.CurrentCulture.TextInfo.
ToTitleCase((inputString ?? string.Empty).ToLower());
} </CODE>
3、显性和隐性接口实现
这很重要吗?是的,非常重要,你知道它们之间的语法差异吗?其实它们存在根本性的区别。类上的隐性接口实现默认是一个公共方法,在类的对象或接口上都可以访问。而类上的显性接口实现默认是一个私有方法,只能通过接口访问,不能通过类的对象访问。下面是示例代码: <CODE>
INTERFACE
public interface IMyInterface
{
void MyMethod(string myString);
}
CLASS THAT IMPLEMENTS THE INTERFACE IMPLICITLY
public MyImplicitClass: IMyInterface
{
public void MyMethod(string myString)
{ /// } }
CLASS THAT IMPLEMENTS THE INTERFACE EXPLICITLY
public MyExplicitClass: IMyInterface
{
void IMyInterface.MyMethod(string myString)
{ /// } }
MyImplicitClass instance would work with either the class or the Interface:
MyImplicitClassmyObject=newMyImplicitClass();
myObject.MyMethod("");
IMyInterfacemyObject=newMyImplicitClass();
myObject.MyMethod("");
MyExplicitClass would work only with the interface:
//The following line would not work.
MyExplicitClassmyObject=newMyExplicitClass();
myObject.MyMethod("");
//This will work
IMyInterfacemyObject=newMyExplicitClass();
myObject.MyMethod("");
</CODE>
4、Auto属性
它是替换包含一个公共,两个私有成员属性的最好办法。
按下两次Tab键(需要开启代码片段功能),一个Auto属性就创建好了,再按Tab键Auto属性取一个名字。下面这段代码 <CODE>
private double _total;
public double Total
{
get { return _total; }
set {_total=value; } } </CODE>
就变成了 <CODE>
public double Total { get; set; }
</CODE>
注意你仍然可以根据你的设计应用访问说明符,编译器应该会为你创建私有成员变量。 5、强大的Path.Combine
Path.Combine凭借强大的功能消除了尾斜线和路径相关的问题,简单易用,让路径字符串更连续,它包含一个字符串路径参数,如图1所示。
图 1 Path.Combine方法
你不用担心路径中的有效分隔符或空格,完全不用你处理路径合并时的字符串连接。
6、在类中编写“Override”方法的快速方法
在代码编辑器中输入override,按空格键,你将会看到一串基于类的可覆写方法,如图2所示。
图 2 可覆写方法列表
7、使用扩展的配置文件
感谢app.config(针对应用程序)和web.config配置文件,使我们可以处理复杂的应用程序级设置,但是我们仍然要处理不同环境设置面临的各种问题,这里指的是开发、测试和生产环境的设置。
我们不得不恢复到一个特定的环境以便进行分析、测试或调式部分代码,在这个过程中,每一次设置和调整都很乏味。
例如,每一次恢复可能都要重新设置ConnectionStrings(连接字符串),现在你可以通过外部文件引用使用ConfigSource属性来解决这个问题。例如,下面的代码引用了一个deveploment.config外部配置文件。 <connectionStringsconfigSource="configs\ development.config"/>
你还可以在AppSettings设置小节使用这个有用的属性。
8、克服String.Split方法的局限
String.Split是分隔字符串最理想的方法,但据我们所知,它也有一些限制,如不能使用“||”或“::”这样的字符,必须使用键盘上独一无二的单个字符作为分隔符,这个缺点可以使用RegEx库提供的Split方法来克服掉,下面的代码显示了使用RegEx Split分隔一个“||”分隔字符串。 <CODE>
stringdelimitedString ="String.Split || RegEx.Split");
string[] ouputString = System.Text.RegularExpressions.Regex.Split(
delimitedString,
, System.Text.RegularExpressions.Regex.Escape("||"));
</CODE>
9、元素的HTML代码视图和设计视图之间的快速切换(反之亦然)
在设计应用程序时,我们在IDE上花费的时间很多,大部分时间都耗在HTML内容和设计视图上,Visual Studio 2010提供了设计视图和HTML代码之间快速切换的功能。
如果你在HTML视图中,定位你想在设计视图中查看的元素,然后切换到设计视图,你想查看的元素应该处于选中状态,此外,属性窗口现在也应该显示的是选中元素的属性。
与此类似,当你在设计视图中,选中元素,然后切换到代码视图,你选中的元素对应的HTML代码应该是高亮状态。
10、快速搜索数据库中的数据
虽然数据表支持Find和Select方法选择行,但它们都没有DataView的方法好用,DataView提供了一个FindRows方法,它可以使用排序列上创建的索引,因此速度更快。
希望这些技巧可以帮助你节省宝贵的编程时间,赶快去试试吧!
原文标题:10 More C# Programming and Microsoft Visual Studio
Tags: C#
sqlserver2005:t-sql查询之物理查询处理-re笑清风[2010年07月04日]
作者: 日期:2012-05-15
Refresh-air
清风明月本无价 近水远山皆有情
记载自己在学习.NET的过程中遇到的点点滴滴.......
1.SqlServer执行两个主要的步骤来产生期望的查询结果:查询编译(query compilation)---生成查询计划和执行查询计划。
2.SqlServer2005中查询编译由三个步骤组成:分析、代数化以及查询优化,完成这些步骤后编译器把经过优化的查询计划保存到过程缓存(procedure cache)
3.编译过程
首先执行分析、绑定(聚合绑定和分组绑定)、优化(细微计划优化;简化(阶段0、阶段1、阶段2))
4.动态管理视图 sys.dm_exec_query_optimizer_info 提供了从SqlServer启动以来执行的所有优化的累积信息
5. 清空过程缓存 DBCC FREEPROCCACHE 删除过程缓存的内容 sys.dm_exec_query_optimizer_info SET NOCOUNT ON; USE Northwind; -- use your database name here DBCC FREEPROCCACHE; -- empty the procedure cache GO -- we will use tempdb..OptStats table to capture -- the information from several executions -- of sys.dm_exec_query_optimizer_info IF (OBJECT_ID('tempdb..OptStats') IS NOT NULL) Drop TABLE tempdb..OptStats; GO -- the purpose of this statement is -- to create the temporary table tempdb..OptStats Select 0 AS Run, * INTO tempdb..OptStats FROM sys.dm_exec_query_optimizer_info; GO -- this will populate the procedure cache -- with this statement's plan so that it will not -- generate any optimizer events when executed -- next time -- the following GO is intentional to ensure -- the query plan reuse will happen for the following -- Insert for its next invocation in this script GO Insert INTO tempdb..OptStats Select 1 AS Run, * FROM sys.dm_exec_query_optimizer_info; GO -- same reason as above; observe the "2" replaced "1" -- therefore we will have different plan GO Insert INTO tempdb..OptStats Select 2 AS Run, * FROM sys.dm_exec_query_optimizer_info; GO -- empty the temporary table TRUNCATE TABLE tempdb..OptStats GO -- store the "before run" information -- in the temporary table with the output -- of sys.dm_exec_query_optimizer_info -- with value "1" in the column Run GO Insert INTO tempdb..OptStats Select 1 AS Run, * FROM sys.dm_exec_query_optimizer_info; GO -- your statement or batch is executed here /*** the following is an example***/ Select C.CustomerID, COUNT(O.OrderID) AS NumOrders FROM dbo.Customers AS C LEFT OUTER JOIN dbo.Orders AS O ON C.CustomerID = O.CustomerID Where C.City = 'London' GROUP BY C.CustomerID HAVING COUNT(O.OrderID) > 5 orDER BY NumOrders; GO -- store the "after run" information -- in the temporary table with the output -- of sys.dm_exec_query_optimizer_info -- with value "2" in the column Run GO Insert INTO tempdb..OptStats Select 2 AS Run, * FROM sys.dm_exec_query_optimizer_info; GO -- extract all "events" that changed either -- the Occurrence or Value column value between -- the Runs 1 and 2 from the temporary table. -- Display the values of Occurrence and Value -- for all such events before (Run1Occurrence and -- Run1Value) and after (Run2Occurrence and -- Run2Value) executing your batch or query. -- This is the result set generated by the script. WITH X (Run,Counter, Occurrence, Value) AS ( Select * FROM tempdb..OptStats Where Run=1 ), Y (Run,Counter, Occurrence, Value) AS ( Select * FROM tempdb..OptStats Where Run=2 ) Select X.Counter, Y.Occurrence-X.Occurrence AS Occurrence, CASE (Y.Occurrence-X.Occurrence) WHEN 0 THEN (Y.Value*Y.Occurrence-X.Value*X.Occurrence) ELSE (Y.Value*Y.Occurrence-X.Value*X.Occurrence)/(Y.Occ urrence-X.Occurrence) END AS Value FROM X JOIN Y ON (X.Counter=Y.Counter AND (X.Occurrence<>Y.Occurrence or X.Value<>Y.Value)); GO -- drop the temporary table Drop TABLE tempdb..OptStats; GO
6.使用查询计划 Sqlserver2005 可以生产三种不同格式的显示计划:图形、文本和XML,`下图为生成不同格式的计划的命令:
N/A 在SSMS中显示估计的执行计划 在SSMS中显示实际的执行计划 SET SHOWPLAN_TEXT ON和SET SHOWPLAN_ALL ON 当执行计划时,数据的传递通常是从上到下从右到左的。缩进多的运算符生成的行供缩进少的运算符使用,后者再生成由下一级运算符使用的行,以此类推。
XML格式的显示计划
有两种XML格式的显示计划。一种是通过 SET SHOWPLAN_XML ON(编译批处理时生成的,为整个批处理生成一个XML文档)得到的,包含估计的执行计划,另一种是有SET STATISTICS XML ON (运行时产生的,为批处理中的每个语句生成单独的XML文档)得到的,包含运行时信息。
还有两种方法可以得到XML格式的计划:保存SSMS中显示的图形化显示计划;使用SQL Server Profiler。
SET STATISTICS XML ON/OFF
SET STATISTICS PROFILE ON/OFF
用SQL跟踪捕获显示计划
从过程缓存中提取显示计划
几个动态管理视图和函数、DBCC PROCCACHE、以及目录视图sys.syscacheobjects (不推荐)
sys.dm_exec_query_plan DMF以XML格式返回位于过程缓存的计划,其要求一个计划句柄作文唯一的参数,计划句柄是一个VARBINARY(64)类型的查询计划标识符,sys.dm_exec_query_stats DMF为当前过程缓存中的每个查询返回改标识符。‘
下面从查询为所有缓存的查询计划返回XML显示计划,如果批处理或存储过程包含多条SQL语句,则该视图将为每条语句包含一行:
select qplan.query_plan as a
from sys.dm_exec_query_stats as qs
cross apply sys.dm_exec_query_plan(qs.plan_handle) as qplan
更新计划
IUD计划包括2个阶段,第一阶段是读取数据,它通过生成用于描述更改的数据流来确定哪些行将被插入、更新、删除;第二个阶段把数据流中的更改应用到表。
查询优化器执行IUD语句时有两种不同的策略:每行维护(每一行同时维护索引和基表);每索引维护
spool运算符提供Halloween保护
清风明月本无价 近水远山皆有情
记载自己在学习.NET的过程中遇到的点点滴滴.......
1.SqlServer执行两个主要的步骤来产生期望的查询结果:查询编译(query compilation)---生成查询计划和执行查询计划。
2.SqlServer2005中查询编译由三个步骤组成:分析、代数化以及查询优化,完成这些步骤后编译器把经过优化的查询计划保存到过程缓存(procedure cache)
3.编译过程
首先执行分析、绑定(聚合绑定和分组绑定)、优化(细微计划优化;简化(阶段0、阶段1、阶段2))
4.动态管理视图 sys.dm_exec_query_optimizer_info 提供了从SqlServer启动以来执行的所有优化的累积信息
5. 清空过程缓存 DBCC FREEPROCCACHE 删除过程缓存的内容 sys.dm_exec_query_optimizer_info SET NOCOUNT ON; USE Northwind; -- use your database name here DBCC FREEPROCCACHE; -- empty the procedure cache GO -- we will use tempdb..OptStats table to capture -- the information from several executions -- of sys.dm_exec_query_optimizer_info IF (OBJECT_ID('tempdb..OptStats') IS NOT NULL) Drop TABLE tempdb..OptStats; GO -- the purpose of this statement is -- to create the temporary table tempdb..OptStats Select 0 AS Run, * INTO tempdb..OptStats FROM sys.dm_exec_query_optimizer_info; GO -- this will populate the procedure cache -- with this statement's plan so that it will not -- generate any optimizer events when executed -- next time -- the following GO is intentional to ensure -- the query plan reuse will happen for the following -- Insert for its next invocation in this script GO Insert INTO tempdb..OptStats Select 1 AS Run, * FROM sys.dm_exec_query_optimizer_info; GO -- same reason as above; observe the "2" replaced "1" -- therefore we will have different plan GO Insert INTO tempdb..OptStats Select 2 AS Run, * FROM sys.dm_exec_query_optimizer_info; GO -- empty the temporary table TRUNCATE TABLE tempdb..OptStats GO -- store the "before run" information -- in the temporary table with the output -- of sys.dm_exec_query_optimizer_info -- with value "1" in the column Run GO Insert INTO tempdb..OptStats Select 1 AS Run, * FROM sys.dm_exec_query_optimizer_info; GO -- your statement or batch is executed here /*** the following is an example***/ Select C.CustomerID, COUNT(O.OrderID) AS NumOrders FROM dbo.Customers AS C LEFT OUTER JOIN dbo.Orders AS O ON C.CustomerID = O.CustomerID Where C.City = 'London' GROUP BY C.CustomerID HAVING COUNT(O.OrderID) > 5 orDER BY NumOrders; GO -- store the "after run" information -- in the temporary table with the output -- of sys.dm_exec_query_optimizer_info -- with value "2" in the column Run GO Insert INTO tempdb..OptStats Select 2 AS Run, * FROM sys.dm_exec_query_optimizer_info; GO -- extract all "events" that changed either -- the Occurrence or Value column value between -- the Runs 1 and 2 from the temporary table. -- Display the values of Occurrence and Value -- for all such events before (Run1Occurrence and -- Run1Value) and after (Run2Occurrence and -- Run2Value) executing your batch or query. -- This is the result set generated by the script. WITH X (Run,Counter, Occurrence, Value) AS ( Select * FROM tempdb..OptStats Where Run=1 ), Y (Run,Counter, Occurrence, Value) AS ( Select * FROM tempdb..OptStats Where Run=2 ) Select X.Counter, Y.Occurrence-X.Occurrence AS Occurrence, CASE (Y.Occurrence-X.Occurrence) WHEN 0 THEN (Y.Value*Y.Occurrence-X.Value*X.Occurrence) ELSE (Y.Value*Y.Occurrence-X.Value*X.Occurrence)/(Y.Occ urrence-X.Occurrence) END AS Value FROM X JOIN Y ON (X.Counter=Y.Counter AND (X.Occurrence<>Y.Occurrence or X.Value<>Y.Value)); GO -- drop the temporary table Drop TABLE tempdb..OptStats; GO
6.使用查询计划 Sqlserver2005 可以生产三种不同格式的显示计划:图形、文本和XML,`下图为生成不同格式的计划的命令:
N/A 在SSMS中显示估计的执行计划 在SSMS中显示实际的执行计划 SET SHOWPLAN_TEXT ON和SET SHOWPLAN_ALL ON 当执行计划时,数据的传递通常是从上到下从右到左的。缩进多的运算符生成的行供缩进少的运算符使用,后者再生成由下一级运算符使用的行,以此类推。
XML格式的显示计划
有两种XML格式的显示计划。一种是通过 SET SHOWPLAN_XML ON(编译批处理时生成的,为整个批处理生成一个XML文档)得到的,包含估计的执行计划,另一种是有SET STATISTICS XML ON (运行时产生的,为批处理中的每个语句生成单独的XML文档)得到的,包含运行时信息。
还有两种方法可以得到XML格式的计划:保存SSMS中显示的图形化显示计划;使用SQL Server Profiler。
SET STATISTICS XML ON/OFF
SET STATISTICS PROFILE ON/OFF
用SQL跟踪捕获显示计划
从过程缓存中提取显示计划
几个动态管理视图和函数、DBCC PROCCACHE、以及目录视图sys.syscacheobjects (不推荐)
sys.dm_exec_query_plan DMF以XML格式返回位于过程缓存的计划,其要求一个计划句柄作文唯一的参数,计划句柄是一个VARBINARY(64)类型的查询计划标识符,sys.dm_exec_query_stats DMF为当前过程缓存中的每个查询返回改标识符。‘
下面从查询为所有缓存的查询计划返回XML显示计划,如果批处理或存储过程包含多条SQL语句,则该视图将为每条语句包含一行:
select qplan.query_plan as a
from sys.dm_exec_query_stats as qs
cross apply sys.dm_exec_query_plan(qs.plan_handle) as qplan
更新计划
IUD计划包括2个阶段,第一阶段是读取数据,它通过生成用于描述更改的数据流来确定哪些行将被插入、更新、删除;第二个阶段把数据流中的更改应用到表。
查询优化器执行IUD语句时有两种不同的策略:每行维护(每一行同时维护索引和基表);每索引维护
spool运算符提供Halloween保护
Tags: SQLServer
C#使用MySQLConnector/NET[2011年03月07日]
作者: 日期:2012-05-14
重要提醒:系统检测到您的帐号可能存在被盗风险,请尽快查看风险提示,并立即修改密码。 | 关闭
网易博客安全提醒:系统检测到您当前密码的安全性较低,为了您的账号安全,建议您适时修改密码 立即修改 | 关闭
http://blog.csdn.net/mlks_2008/archive/2010/11/25/ 6034141.aspx
使用MySQL Connector/NET
26.2.4.1. 前言
26.2.4.2. 使用MySQL Connector/NET连接到MySQL
26.2.4.3. 与预处理语句一起使用MySQL Connector/NET
26.2.4.4. 用MySQL Connector/NET访问存储程序
26.2.4.5. 用Connector/NET处理BLOB数据
26.2.4.6. 与Crystal Reports一起使用MySQL Connector/NET
26.2.4.7. 在MySQL Connector/NET中处理日期和时间信息
26.2.4.1. 前言
在本节中,介绍的Connector/NET的一些常用方式,包括BLOB处理,日期处理,以及与诸如Crystal Reports等常见工具一起使用Connector/NET的方法。
26.2.4.2. 使用MySQL Connector/NET连接到MySQL
26.2.4.2.1. 前言
26.2.4.2.2. 创建连接字符串
26.2.4.2.3. 打开连接
26.2.4.2.4. 处理连接错误
26.2.4.2.1. 前言
.NET应用程序和MySQL服务器之间的所有交互均是通过MySqlConnection对象传送的。在应用程序能够与服务器进行交互之前,必须获取、配置、并打开MySqlConnection对象。
即使在使用MySqlHelper类时,MySqlConnection对象也会被Helper类创建。
在本节中,介绍了使用MySqlConnection对象连接到MySQL的方法。
26.2.4.2.2. 创建连接字符串
MySqlConnection对象是使用连接字符串配置的。1个连接字符串包含服务器键/值对,由分号隔开。每个键/值对由等号连接。
下面给出了1个简单的连接字符串示例:
Server=127.0.0.1;Uid=root;Pwd=12345;Database=test; 在本例中,对MySqlConnection对象进行了配置,使用用户名“root”和密码“12345”与位于127.0.0.1的MySQL服务器相连。所有语句的默认数据库为测试数据库。
典型的选项如下(关于选项的完整清单,请参见API文档):
· Server:将要连接的MySQL实例的名称或网络地址。默认为本地主机。别名包括Host, Data Source, DataSource, Address, Addr和Network Address。
· Uid:连接时使用的MySQL用户账户。别名包括User Id, Username和User name。
· Pwd:MySQL账户的密码。也可以使用别名密码。
· Database:所有语句作用于的默认数据库。默认为mysql。也可以使用别名Initial Catalog。
· Port:MySQL用于监听连接的端口。默认为3306。将该值指定为“-1”将使用命名管道连接。
26.2.4.2.3. 打开连接
一旦创建了连接字符串,可使用它打开与MySQL服务器的连接。
下述代码用于创建MySqlConnection对象,指定连接字符串,并打开连接。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
string myConnectionString;
myConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn = new MySql.Data.MySqlClient.MySqlConnection();
conn.ConnectionString = myConnectionString;
conn.Open();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message);
}
你也可以将连接字符串传递给MySqlConnection类的构造函数:
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
string myConnectionString;
myConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn = new MySql.Data.MySqlClient.MySqlConnection(myConnectio nString);
conn.Open();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message);
}
一旦打开了连接,其他MySQL Connector/NET类也能使用该连接与MySQL服务器进行通信。
26.2.4.2.4. 处理连接错误
由于与外部服务器的连接不可预测,应为你的.NET应用程序添加错误处理功能,这点很重要。出现连接错误时,MySqlConnection类将返回1个MySqlException对象。该对象有两个在处理错误时十分有用的属性:
· Message:描述当前异常的消息。
· Number:MySQL错误编号。
处理错误时,可根据错误编号了解应用程序的响应。进行连接时最常见的两个错误编号如下:
· 0: 无法连接到服务器。
· 1045: 无效的用户名和/或密码。
在下面的代码中,介绍了根据实际错误改编应用程序的方法:
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
string myConnectionString;
myConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn = new MySql.Data.MySqlClient.MySqlConnection(myConnectio nString);
conn.Open();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
switch (ex.Number)
{
case 0:
MessageBox.Show(”Cannot connect to server. Contact administrator”);
case 1045:
MessageBox.Show(”Invalid username/password, please try again”);
}
}
26.2.4.3. 与预处理语句一起使用MySQL Connector/NET
26.2.4.3.1. 前言
26.2.4.3.2. 在MySQL Connector/NET中准备语句
26.2.4.3.1. 前言
从MySQL 4.1开始,能够与MySQL Connector/NET一起使用预处理语句。使用预处理语句能够现住改善多次执行的查询的性能。
对于多次执行的语句,预处理执行的速度快于直接执行,这是因为只需进行1次解析操作。在直接执行的情况下,每次执行时均将进行解析操作。预处理执行还能降低网络通信量,这是因为对于预处理语句的每次执行,仅需发送用于参数的数据。
预处理语句的另一优点是,它能使用二进制协议,这使得客户端和服务器间的数据传输更有效率。
26.2.4.3.2. 在MySQL Connector/NET中准备语句
为了准备好语句,需创建1个命令对象,并为查询设置.CommandText属性。
输入语句后,调用MySqlCommand对象的.Prepare方法。完成语句的准备后,为查询中的每个元素添加参数。
输入查询并输入参数后,使用.ExecuteNonQuery()、.ExecuteScalar()、或.ExecuteReader方法执行语句。
对于后续的执行操作,仅需更改参数值并再次调用执行方法,无需设置.CommandText属性或重新定义参数。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;MySql.Data.MySqlClient.MySqlCommand cmd; conn = new MySql.Data.MySqlClient.MySqlConnection();cmd = new MySql.Data.MySqlClient.MySqlCommand(); conn.ConnectionString = strConnection; try{ conn.Open(); cmd.Connection = conn; cmd.CommandText = “Insert INTO myTable VALUES(NULL, ?number, ?text)”; cmd.Prepare(); cmd.Parameters.Add(”?number”, 1); cmd.Parameters.Add(”?text”, “One”); for (int i=1; i <= 1000; i++) { cmd.Parameters["?number"].Value = i; cmd.Parameters["?text"].Value = “A string value”; cmd.ExecuteNonQuery(); }}catch (MySql.Data.MySqlClient.MySqlException ex){ MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message, “Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);}
26.2.4.4. 用MySQL Connector/NET访问存储程序
26.2.4.4.1. 前言
26.2.4.4.2. 从MySQL Connector/NET创建存储程序
26.2.4.4.3. 从MySQL Connector/NET调用存储程序
26.2.4.4.1. 前言
随着MySQL版本5的发布,MySQL服务器目前支持存储程序,它采用了SQL 2003存储程序的语法。
存储程序指的是能够保存在服务器上的一组SQL语句。 一旦完成了该操作,客户端无需再次发出单独语句,而仅需引用存储程序取而代之。
在下述情况下,存储程序尤其有用:
· 多个客户端应用程序是采用不同语言编写的或工作在不同平台上,但需执行相同的数据库操作。
· 安全性极其重要时。例如,对于所有共同操作,银行采用了存储程序。这样,就能提供一致且安全的环境,而且这类存储程序能够保证每次操作均具有恰当登录。在这类设置下,应用程序和用户无法直接访问数据库表,但能执行特定的存储程序。
MySQL Connector/NET支持通过MySqlCommand对象的存储程序调用。使用MySqlCommand.Parameters集,能够将数据传入和传出MySQL存储程序。
在本节中,未深度介绍创建存储程序方面的信息,要想了解这类信息,请参见MySQL参考手册的存储程序。
在MySQL Connector/NET安装的Samples目录下,可找到1个相应的示例,该示例演示了与MySQL Connector/NET一起使用存储程序的方法。
26.2.4.4.2. 从MySQL Connector/NET创建存储程序
可使用多种工具创建MySQL中的存储程序。首先,可使用mysql命令行客户端创建存储程序。其次,可使用MySQL Query Browser GUI客户端创建存储程序。最后,可使用MySqlCommand对象的.ExecuteNonQuery方法创建存储程序。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn.Open();
cmd.Connection = conn;
cmd.CommandText = “Create PROCEDURE add_emp(” +
“IN fname VARCHAR(20), IN lname VARCHAR(20), IN bday DATETIME, OUT empno INT) ” +
“BEGIN Insert INTO emp(first_name, last_name, birthdate) ” +
“VALUES(fname, lname, DATE(bday)); SET empno = LAST_Insert_ID(); END”;
cmd.ExecuteNonQuery();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message,
“Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
请注意,不同于命令行和GUI客户端,在MySQL Connector/NET中创建存储程序时不需要指定特殊的定界符。
26.2.4.4.3. 从MySQL Connector/NET调用存储程序
要想使用MySQL Connector/NET来调用存储程序,应创建1个MySqlCommand对象,并将存储程序名作为.CommandText属性传递。将.CommandType属性设置为CommandType.StoredProcedure。
命名了存储程序后,为存储程序中的每个参数创建1个MySqlCommand参数。用参数名和包含值的对象定义IN参数,用参数名和预计将返回的数据类型定义OUT参数。对于所有参数,均需定义参数方向。
定义完参数后,使用MySqlCommand.ExecuteNonQuery()方法调用存储程序。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn.Open();
cmd.Connection = conn;
cmd.CommandText = “add_emp”;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(”?lname”, “Jones”);
cmd.Parameters(”?lname”).Direction = ParameterDirection.Input;
cmd.Parameters.Add(”?fname”, “Tom”);
cmd.Parameters(”?fname”).Direction = ParameterDirection.Input;
cmd.Parameters.Add(”?bday”, DateTime.Parse(”12/13/1977 2:17:36 PM”));
cmd.Parameters(”?bday”).Direction = ParameterDirection.Input;
cmd.Parameters.Add(”?empno”, MySqlDbType.Int32);
cmd.Parameters(”?empno”).Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
MessageBox.Show(cmd.Parameters(”?empno”).Value);
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message,
“Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
一旦调用了存储程序,可使用MySqlConnector.Parameters集的.Value属性检索输出参数的值。
26.2.4.5. 用Connector/NET处理BLOB数据
26.2.4.5.1. 前言
26.2.4.5.2. 准备MySQL服务器
26.2.4.5.3. 将文件写入数据库
26.2.4.5.4. 将BLOB从数据库读取到磁盘上的文件
26.2.4.5.1. 前言
MySQL的1种用途是在BLOB列中保存二进制数据。MySQL支持4种不同的BLOB数据类型:TINYBLOB, BLOB, MEDIUMBLOB和LONGBLOB。
可使用Connector/NET访问保存在BLOB列中的数据,并能使用客户端代码对这类数据进行操作。使用Connector/NET和BLOB数据时,无特殊要求。
在本节中,给出了数个简单的代码示例,在MySQL Connector/NET安装的Samples目录下,可找到1个完整的示例应用程序。
26.2.4.5.2. 准备MySQL服务器
与BLOB数据一起使用MySQL的第1步是配置服务器。首先,让我们从创建要访问的表开始。在我的文件表中,通常有4列:1个具有恰当大小的AUTO_INCREMENT列(UNSIGNED SMALLINT),用于保存识别文件的主键;1个VARCHAR列,用于保存文件名;1个UNSIGNED MEDIUMINT列,用于保存文件的大小;以及1个用于保存文件本身的MEDIUMBLOB列。对于本例,我将使用下述表定义:
Create TABLE file(file_id SMALLINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,file_name VARCHAR(64) NOT NULL,file_size MEDIUMINT UNSIGNED NOT NULL,file MEDIUMBLOB NOT NULL);完成表的创建后,或许需要更改max_allowed_packet系统变量。该变量决定了能够发送给MySQL服务器的信息包(即单个行)大小。默认情况下,服务器能够接受来自客户端应用程序的信息包最大为1MB。如果不打算超过1MB,情况良好。如果打算在文件传输中超出1MB,必须增加该数值。
可以使用“MySQL系统管理员的启动变量”屏幕更改max_allowed_packet选项。在“联网”选项卡的“内存”部分,恰当调整“允许的最大值”选项。完成值的调整后,点击“应用更改”按钮,并使用“MySQL管理员”的“服务控制”屏幕重新启动服务器。也可以在my.cnf文件中直接调整该值(添加1行,max_allowed_packet=xxM),或在MySQL中使用SET max_allowed_packet=xxM。
设置max_allowed_packet时应保守些,这是因为传输BLOB数据需要一段时间。恰当地设置该值,使之与预期使用相符,并在必要时增大该值。
26.2.4.5.3. 将文件写入数据库
要想将文件写入数据库,需要将文件转换为字节数组,然后将字节数组用作Insert查询的参数。
在下述代码中,使用FileStream对象打开了1个文件,将其读入至字节数组,然后将其插入到文件表中:
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
string SQL;
UInt32 FileSize;
byte[] rawData;
FileStream fs;
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
fs = new FileStream(@”c:\image.png”, FileMode.Open, FileAccess.Read);
FileSize = fs.Length;
rawData = new byte[FileSize];
fs.Read(rawData, 0, FileSize);
fs.Close();
conn.Open();
SQL = “Insert INTO file VALUES(NULL, ?FileName, ?FileSize, ?File)”;
cmd.Connection = conn;
cmd.CommandText = SQL;
cmd.Parameters.Add(”?FileName”, strFileName);
cmd.Parameters.Add(”?FileSize”, FileSize);
cmd.Parameters.Add(”?File”, rawData);
cmd.ExecuteNonQuery();
MessageBox.Show(”File Inserted into database successfully!”,
“Success!”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
conn.Close();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message,
“Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
FileStream对象的“Read”方法可用于将文件加载到字节数组中,该字节数组的大小是根据FileStream对象的“Length”属性确定的。
将字节数组指定为MySqlCommand对象的参数后,调用ExecuteNonQuery方法,并将BLOB插入到文件表中。
26.2.4.5.4. 将BLOB从数据库读取到磁盘上的文件
一旦将文件加载到了文件表中,就能使用MySqlDataReader类来检索它。
在下述代码中,从文件表提取了1行,然后将数据装载到要写入至磁盘的FileStream对象。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
MySql.Data.MySqlClient.MySqlDataReader myData;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
string SQL;
UInt32 FileSize;
byte[] rawData;
FileStream fs;
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
SQL = “Select file_name, file_size, file FROM file”;
try
{
conn.Open();
cmd.Connection = conn;
cmd.CommandText = SQL;
myData = cmd.ExecuteReader();
if (! myData.HasRows)
throw new Exception(”There are no BLOBs to save”);
myData.Read();
FileSize = myData.GetUInt32(myData.GetOrdinal(”file_size”));
rawData = new byte[FileSize];
myData.GetBytes(myData.GetOrdinal(”file”), 0, rawData, 0, FileSize);
fs = new FileStream(@”C:\newfile.png”, FileMode.OpenOrCreate, FileAccess.Write);
fs.Write(rawData, 0, FileSize);
fs.Close();
MessageBox.Show(”File successfully written to disk!”,
“Success!”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
myData.Close();
conn.Close();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message,
“Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
连接后,文件表的内容将被加载到MySqlDataReader对象中。使用MySqlDataReader的GetBytes方法将BLOB加载到字节数组,然后使用FileStream对象将字节数据写入磁盘。
MySqlDataReader的GetOrdinal方法可用于确定命名列的整数索引。如果Select查询的列顺序发生变化,使用GetOrdinal方法能够防止错误。
26.2.4.6. 与Crystal Reports一起使用MySQL Connector/NET
26.2.4.6.1. 前言
26.2.4.6.2. 创建数据源
26.2.4.6.3. 创建报告
26.2.4.6.4. 显示报告
26.2.4.6.1. 前言
Crystal Reports是Windows应用程序开发人员用于通报文档生成的常用工具。在本节中,介绍了Crystal Reports XI与MySQL和Connector/NET一起使用的方法。
在MySQL Connector/NET安装的Samples目录的CrystalDemo子目录下,可找到完整的示例应用程序。
26.2.4.6.2. 创建数据源
在Crystal Reports中创建报告时,在设计报告时,有两个用于访问MySQL数据的选项。
第1个选项是,设计报告时,使用Connector/ODBC作为ADO数据源。你能够浏览数据库,并使用拖放式操作选择表和字段以创建报告。该方法的缺点是,必须在应用程序中执行额外操作以生成与报告预期的数据集匹配的数据集。
第2个选项是在VB.NET中创建数据集,并将其保存为XML格式。随后,该XML文件可被用于设计报告。在应用程序中显示报告时,它的表现相当良好,但设计时的通用性较差,这是因为在创建数据集时,必须选择所有的相关列。如果忘记选择了某一列,在能够将列添加到报告前,必须重新创建数据集。
使用下述代码,可根据查询操作创建数据集,并将其写入磁盘。
[C#]
DataSet myData = new DataSet();
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
MySql.Data.MySqlClient.MySqlDataAdapter myAdapter;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
myAdapter = new MySql.Data.MySqlClient.MySqlDataAdapter();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
cmd.CommandText = “Select city.name AS cityName, city.population AS CityPopulation, ” +
“country.name, country.population, country.continent ” +
“FROM country, city orDER BY country.continent, country.name”;
cmd.Connection = conn;
myAdapter.SelectCommand = cmd;
myAdapter.Fill(myData);
myData.WriteXml(@”C:\dataset.xml”, XmlWriteMode.WriteSchema);
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message, “Report could not be created”,
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
设计报告时,可将该代码生成的XML文件用作ADO.NET XML数据源。
如果你选择使用Connector/ODBC来设计报告,可从dev.mysql.com下载它。
26.2.4.6.3. 创建报告
对于大多数应用目的,标准的报告向导应能帮助你完成报告的最初创建。要想启动向导,打开Crystal Reports并从“文件”菜单选择“New > Standard Report”选项。
向导首先要求你提供数据源。如果你正使用Connector/ODBC作为数据源,选择数据源时,请使用OLE DB (ADO)树的“用于ODBC的OLEDB provider”选项,,而不是来自ODBC (RDO)的对应选项。如果你使用的是已保存的数据集,请选择ADO.NET (XML)选项,并浏览你保存的数据集。
在报告的创建过程中,剩余部分将由向导自动完成。
创建完报告后,选择“文件”菜单中的“Report Options…”菜单项。取消对“Save Data With Report”(与报告一起保存数据)选项的选择。这样,就能防止保存的数据干扰应用程序中的数据加载操作。
26.2.4.6.4. 显示报告
要想显示报告,首先用报告所需的数据填充数据集,然后加载报告,并将其与绑定到数据集。最后,将报告传递给crViewer控制,以便向用户显示它。
在显示报告的项目中,需要下述引用:
· CrytalDecisions.CrystalReports.Engine
· CrystalDecisions.ReportSource
· CrystalDecisions.Shared
· CrystalDecisions.Windows.Forms
在下述代码中,假定你使用数据集(用创建数据源中给出的代码保存的数据集)创建了报告,并在名为“myViewer”的表单上有1个crViewer控件。
[C#]
using CrystalDecisions.CrystalReports.Engine;
using System.Data;
using MySql.Data.MySqlClient;
ReportDocument myReport = new ReportDocument();
DataSet myData = new DataSet();
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
MySql.Data.MySqlClient.MySqlDataAdapter myAdapter;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
myAdapter = new MySql.Data.MySqlClient.MySqlDataAdapter();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
cmd.CommandText = “Select city.name AS cityName, city.population AS CityPopulation, ” +
“country.name, country.population, country.continent ” +
“FROM country, city orDER BY country.continent, country.name”;
cmd.Connection = conn;
myAdapter.SelectCommand = cmd;
myAdapter.Fill(myData);
myReport.Load(@”.\world_report.rpt”);
myReport.SetDataSource(myData);
myViewer.ReportSource = myReport;
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message, “Report could not be created”,
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
使用相同的查询(用于生成前面保存的数据集),可生成新的数据集。一旦填充了数据集,可使用ReportDocument加载报告文件,并将其与数据集绑定在一起。ReportDocument是作为crViewer的ReportSource而传递的。
使用Connector/ODBC从单个表创建报告时,采用了相同的方法。数据集替换报告中使用的表,并恰当显示报告。
如果报告是使用Connector/ODBC从多个表创建的,在我们的应用程序中必须创建具有多个表的数据集。这样,就能用数据集中的报告替换报告数据源中的各个表。
在我们的MySqlCommand对象中提供多条Select语句,通过该方式,用多个表填充数据集。这些Select语句基于SQL查询,如数据库菜单“Show SQL Query”选项中的“Crystal Reports”中显示的那样。假定有下述查询:
Select `country`.`Name`, `country`.`Continent`, `country`.`Population`, `city`.`Name`, `city`.`Population`FROM `world`.`country` `country` LEFT OUTER JOIN `world`.`city` `city` ON `country`.`Code`=`city`.`CountryCode`ORDER BY `country`.`Continent`, `country`.`Name`, `city`.`Name`该查询将被转换为两条Select查询,并以下述代码显示:
[C#]
using CrystalDecisions.CrystalReports.Engine;
using System.Data;
using MySql.Data.MySqlClient;
ReportDocument myReport = new ReportDocument();
DataSet myData = new DataSet();
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
MySql.Data.MySqlClient.MySqlDataAdapter myAdapter;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
myAdapter = new MySql.Data.MySqlClient.MySqlDataAdapter();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
cmd.CommandText = “Select name, population, countrycode FROM city orDER ” +
“BY countrycode, name; Select name, population, code, continent FROM ” +
“country orDER BY continent, name”;
cmd.Connection = conn;
myAdapter.SelectCommand = cmd;
myAdapter.Fill(myData);
myReport.Load(@”.\world_report.rpt”);
myReport.Database.Tables(0).SetDataSource(myData.T ables(0));
myReport.Database.Tables(1).SetDataSource(myData.T ables(1));
myViewer.ReportSource = myReport;
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message, “Report could not be created”,
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
应将Select语句按字母顺序排列,这点很重要,原因在于,这是报告希望其源表所具有的顺序。对于报告中的每个表,均需要一条SetDataSource语句。
该方法会导致性能问题,这是因为Crystal Reports必须在客户端一侧将表绑定在一起,与使用以前保存的数据集相比,速度较慢。
26.2.4.7. 在MySQL Connector/NET中处理日期和时间信息
26.2.4.7.1. 前言
26.2.4.7.2. 使用无效日期时的问题
26.2.4.7.3. 限制无效日期
26.2.4.7.4. 处理无效日期
26.2.4.7.5. 处理NULL日期
26.2.4.7.1. 前言
MySQL和.NET语言处理日期和时间信息的方式是不同的,MySQL允许使用无法由.NET数据类型表示的日期,如“0000-00-00 00:00:00”。如果处理步当,该差异会导致问题。
在本节中,介绍了使用MySQL Connector/NET时恰当处理日期和时间信息的方法。
26.2.4.7.2. 使用无效日期时的问题
对于使用无效日期的开发人员来说,数据处理方面的差异会导致问题。无效的MySQL日期无法被加载到.NET DateTime对象中,包括NULL日期。
由于该原因,不能用MySqlDataAdapter类的Fill方法填充.NET DataSet对象,这是因为无效日期会导致System.ArgumentOutOfRangeException异常。
26.2.4.7.3. 限制无效日期
对日期问题的最佳解决方案是,限制用户输入无效日期。这即可在客户端上进行,也可在服务器端进行。
在客户端上限制无效日期十分简单,即总使用.NET DateTime类来处理日期。DateTime类仅允许有效日期,从而确保了数据库中的值也是有效的。该方法的缺点是,在使用.NET和非.NET代码操作数据库的混合环境下不能使用它,这是因为各应用程序必须执行自己的日期验证。
MySQL 5.0.2和更高版本的用户可使用新的传统SQL模式来限制无效日期值。关于使用传统SQL模式的更多信息,请参见http://dev.mysql.com/doc/mysql/en/server-sql-mode. html。
26.2.4.7.4. 处理无效日期
强烈建议在你的.NET应用程序中应避免使用无效日期,尽管如此,也能tongguo MySqlDateTime数据类型使用无效日期。
MySqlDateTime数据类型支持MySQL服务器支持的相同日期值。MySQL Connector/NET的默认行为是,对有效的日期值返回1个.NET DateTime对象,对无效日期值返回错误。可以更改该默认方式,使MySQL Connector/NET为无效日期返回MySqlDateTime对象。
要想使MySQL Connector/NET为无效日期返回MySqlDateTime对象,可在连接字符串中添加下行:
Allow Zero Datetime=True 请注意,使用MySqlDateTime类仍会产生问题。下面介绍了一些已知问题:
1. 无效日期的数据绑定仍会导致错误(零日期0000-00-00看上去不存在该问题)。
2. ToString方法返回按标准MySQL格式进行格式处理的日期(例如,2005-02-23 08:50:25)。这与.NET DateTime类的ToString行为不同。
3. MySqlDateTime类支持NULL日期,但.NET DateTime类不支持NULL日期。如果未首先检查NULL,在试图将MySQLDateTime转换为DateTime时,会导致错误。
由于存在上述已知事宜,最佳建议仍是,在你的应用程序中仅使用有效日期。
26.2.4.7.5. 处理NULL日期
.NET DateTime数据类型不能处理NULL值。同样,在查询中为DateTime变量赋值时,必须首先检查值是否是NULL。
使用MySqlDataReader时,在赋值前,应使用.IsDBNull方法检查值是否为NULL:
[C#]
if (! myReader.IsDBNull(myReader.GetOrdinal(”mytime”)))
myTime = myReader.GetDateTime(myReader.GetOrdinal(”mytime”));
else
myTime = DateTime.MinValue;
NULL值能够在数据集中使用,也能将其绑定以构成控件,无需特殊处理。
http://www.80yunji.cn/2010/07/342
网易博客安全提醒:系统检测到您当前密码的安全性较低,为了您的账号安全,建议您适时修改密码 立即修改 | 关闭
http://blog.csdn.net/mlks_2008/archive/2010/11/25/ 6034141.aspx
使用MySQL Connector/NET
26.2.4.1. 前言
26.2.4.2. 使用MySQL Connector/NET连接到MySQL
26.2.4.3. 与预处理语句一起使用MySQL Connector/NET
26.2.4.4. 用MySQL Connector/NET访问存储程序
26.2.4.5. 用Connector/NET处理BLOB数据
26.2.4.6. 与Crystal Reports一起使用MySQL Connector/NET
26.2.4.7. 在MySQL Connector/NET中处理日期和时间信息
26.2.4.1. 前言
在本节中,介绍的Connector/NET的一些常用方式,包括BLOB处理,日期处理,以及与诸如Crystal Reports等常见工具一起使用Connector/NET的方法。
26.2.4.2. 使用MySQL Connector/NET连接到MySQL
26.2.4.2.1. 前言
26.2.4.2.2. 创建连接字符串
26.2.4.2.3. 打开连接
26.2.4.2.4. 处理连接错误
26.2.4.2.1. 前言
.NET应用程序和MySQL服务器之间的所有交互均是通过MySqlConnection对象传送的。在应用程序能够与服务器进行交互之前,必须获取、配置、并打开MySqlConnection对象。
即使在使用MySqlHelper类时,MySqlConnection对象也会被Helper类创建。
在本节中,介绍了使用MySqlConnection对象连接到MySQL的方法。
26.2.4.2.2. 创建连接字符串
MySqlConnection对象是使用连接字符串配置的。1个连接字符串包含服务器键/值对,由分号隔开。每个键/值对由等号连接。
下面给出了1个简单的连接字符串示例:
Server=127.0.0.1;Uid=root;Pwd=12345;Database=test; 在本例中,对MySqlConnection对象进行了配置,使用用户名“root”和密码“12345”与位于127.0.0.1的MySQL服务器相连。所有语句的默认数据库为测试数据库。
典型的选项如下(关于选项的完整清单,请参见API文档):
· Server:将要连接的MySQL实例的名称或网络地址。默认为本地主机。别名包括Host, Data Source, DataSource, Address, Addr和Network Address。
· Uid:连接时使用的MySQL用户账户。别名包括User Id, Username和User name。
· Pwd:MySQL账户的密码。也可以使用别名密码。
· Database:所有语句作用于的默认数据库。默认为mysql。也可以使用别名Initial Catalog。
· Port:MySQL用于监听连接的端口。默认为3306。将该值指定为“-1”将使用命名管道连接。
26.2.4.2.3. 打开连接
一旦创建了连接字符串,可使用它打开与MySQL服务器的连接。
下述代码用于创建MySqlConnection对象,指定连接字符串,并打开连接。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
string myConnectionString;
myConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn = new MySql.Data.MySqlClient.MySqlConnection();
conn.ConnectionString = myConnectionString;
conn.Open();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message);
}
你也可以将连接字符串传递给MySqlConnection类的构造函数:
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
string myConnectionString;
myConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn = new MySql.Data.MySqlClient.MySqlConnection(myConnectio nString);
conn.Open();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message);
}
一旦打开了连接,其他MySQL Connector/NET类也能使用该连接与MySQL服务器进行通信。
26.2.4.2.4. 处理连接错误
由于与外部服务器的连接不可预测,应为你的.NET应用程序添加错误处理功能,这点很重要。出现连接错误时,MySqlConnection类将返回1个MySqlException对象。该对象有两个在处理错误时十分有用的属性:
· Message:描述当前异常的消息。
· Number:MySQL错误编号。
处理错误时,可根据错误编号了解应用程序的响应。进行连接时最常见的两个错误编号如下:
· 0: 无法连接到服务器。
· 1045: 无效的用户名和/或密码。
在下面的代码中,介绍了根据实际错误改编应用程序的方法:
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
string myConnectionString;
myConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn = new MySql.Data.MySqlClient.MySqlConnection(myConnectio nString);
conn.Open();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
switch (ex.Number)
{
case 0:
MessageBox.Show(”Cannot connect to server. Contact administrator”);
case 1045:
MessageBox.Show(”Invalid username/password, please try again”);
}
}
26.2.4.3. 与预处理语句一起使用MySQL Connector/NET
26.2.4.3.1. 前言
26.2.4.3.2. 在MySQL Connector/NET中准备语句
26.2.4.3.1. 前言
从MySQL 4.1开始,能够与MySQL Connector/NET一起使用预处理语句。使用预处理语句能够现住改善多次执行的查询的性能。
对于多次执行的语句,预处理执行的速度快于直接执行,这是因为只需进行1次解析操作。在直接执行的情况下,每次执行时均将进行解析操作。预处理执行还能降低网络通信量,这是因为对于预处理语句的每次执行,仅需发送用于参数的数据。
预处理语句的另一优点是,它能使用二进制协议,这使得客户端和服务器间的数据传输更有效率。
26.2.4.3.2. 在MySQL Connector/NET中准备语句
为了准备好语句,需创建1个命令对象,并为查询设置.CommandText属性。
输入语句后,调用MySqlCommand对象的.Prepare方法。完成语句的准备后,为查询中的每个元素添加参数。
输入查询并输入参数后,使用.ExecuteNonQuery()、.ExecuteScalar()、或.ExecuteReader方法执行语句。
对于后续的执行操作,仅需更改参数值并再次调用执行方法,无需设置.CommandText属性或重新定义参数。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;MySql.Data.MySqlClient.MySqlCommand cmd; conn = new MySql.Data.MySqlClient.MySqlConnection();cmd = new MySql.Data.MySqlClient.MySqlCommand(); conn.ConnectionString = strConnection; try{ conn.Open(); cmd.Connection = conn; cmd.CommandText = “Insert INTO myTable VALUES(NULL, ?number, ?text)”; cmd.Prepare(); cmd.Parameters.Add(”?number”, 1); cmd.Parameters.Add(”?text”, “One”); for (int i=1; i <= 1000; i++) { cmd.Parameters["?number"].Value = i; cmd.Parameters["?text"].Value = “A string value”; cmd.ExecuteNonQuery(); }}catch (MySql.Data.MySqlClient.MySqlException ex){ MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message, “Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);}
26.2.4.4. 用MySQL Connector/NET访问存储程序
26.2.4.4.1. 前言
26.2.4.4.2. 从MySQL Connector/NET创建存储程序
26.2.4.4.3. 从MySQL Connector/NET调用存储程序
26.2.4.4.1. 前言
随着MySQL版本5的发布,MySQL服务器目前支持存储程序,它采用了SQL 2003存储程序的语法。
存储程序指的是能够保存在服务器上的一组SQL语句。 一旦完成了该操作,客户端无需再次发出单独语句,而仅需引用存储程序取而代之。
在下述情况下,存储程序尤其有用:
· 多个客户端应用程序是采用不同语言编写的或工作在不同平台上,但需执行相同的数据库操作。
· 安全性极其重要时。例如,对于所有共同操作,银行采用了存储程序。这样,就能提供一致且安全的环境,而且这类存储程序能够保证每次操作均具有恰当登录。在这类设置下,应用程序和用户无法直接访问数据库表,但能执行特定的存储程序。
MySQL Connector/NET支持通过MySqlCommand对象的存储程序调用。使用MySqlCommand.Parameters集,能够将数据传入和传出MySQL存储程序。
在本节中,未深度介绍创建存储程序方面的信息,要想了解这类信息,请参见MySQL参考手册的存储程序。
在MySQL Connector/NET安装的Samples目录下,可找到1个相应的示例,该示例演示了与MySQL Connector/NET一起使用存储程序的方法。
26.2.4.4.2. 从MySQL Connector/NET创建存储程序
可使用多种工具创建MySQL中的存储程序。首先,可使用mysql命令行客户端创建存储程序。其次,可使用MySQL Query Browser GUI客户端创建存储程序。最后,可使用MySqlCommand对象的.ExecuteNonQuery方法创建存储程序。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn.Open();
cmd.Connection = conn;
cmd.CommandText = “Create PROCEDURE add_emp(” +
“IN fname VARCHAR(20), IN lname VARCHAR(20), IN bday DATETIME, OUT empno INT) ” +
“BEGIN Insert INTO emp(first_name, last_name, birthdate) ” +
“VALUES(fname, lname, DATE(bday)); SET empno = LAST_Insert_ID(); END”;
cmd.ExecuteNonQuery();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message,
“Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
请注意,不同于命令行和GUI客户端,在MySQL Connector/NET中创建存储程序时不需要指定特殊的定界符。
26.2.4.4.3. 从MySQL Connector/NET调用存储程序
要想使用MySQL Connector/NET来调用存储程序,应创建1个MySqlCommand对象,并将存储程序名作为.CommandText属性传递。将.CommandType属性设置为CommandType.StoredProcedure。
命名了存储程序后,为存储程序中的每个参数创建1个MySqlCommand参数。用参数名和包含值的对象定义IN参数,用参数名和预计将返回的数据类型定义OUT参数。对于所有参数,均需定义参数方向。
定义完参数后,使用MySqlCommand.ExecuteNonQuery()方法调用存储程序。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
conn.Open();
cmd.Connection = conn;
cmd.CommandText = “add_emp”;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(”?lname”, “Jones”);
cmd.Parameters(”?lname”).Direction = ParameterDirection.Input;
cmd.Parameters.Add(”?fname”, “Tom”);
cmd.Parameters(”?fname”).Direction = ParameterDirection.Input;
cmd.Parameters.Add(”?bday”, DateTime.Parse(”12/13/1977 2:17:36 PM”));
cmd.Parameters(”?bday”).Direction = ParameterDirection.Input;
cmd.Parameters.Add(”?empno”, MySqlDbType.Int32);
cmd.Parameters(”?empno”).Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
MessageBox.Show(cmd.Parameters(”?empno”).Value);
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message,
“Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
一旦调用了存储程序,可使用MySqlConnector.Parameters集的.Value属性检索输出参数的值。
26.2.4.5. 用Connector/NET处理BLOB数据
26.2.4.5.1. 前言
26.2.4.5.2. 准备MySQL服务器
26.2.4.5.3. 将文件写入数据库
26.2.4.5.4. 将BLOB从数据库读取到磁盘上的文件
26.2.4.5.1. 前言
MySQL的1种用途是在BLOB列中保存二进制数据。MySQL支持4种不同的BLOB数据类型:TINYBLOB, BLOB, MEDIUMBLOB和LONGBLOB。
可使用Connector/NET访问保存在BLOB列中的数据,并能使用客户端代码对这类数据进行操作。使用Connector/NET和BLOB数据时,无特殊要求。
在本节中,给出了数个简单的代码示例,在MySQL Connector/NET安装的Samples目录下,可找到1个完整的示例应用程序。
26.2.4.5.2. 准备MySQL服务器
与BLOB数据一起使用MySQL的第1步是配置服务器。首先,让我们从创建要访问的表开始。在我的文件表中,通常有4列:1个具有恰当大小的AUTO_INCREMENT列(UNSIGNED SMALLINT),用于保存识别文件的主键;1个VARCHAR列,用于保存文件名;1个UNSIGNED MEDIUMINT列,用于保存文件的大小;以及1个用于保存文件本身的MEDIUMBLOB列。对于本例,我将使用下述表定义:
Create TABLE file(file_id SMALLINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,file_name VARCHAR(64) NOT NULL,file_size MEDIUMINT UNSIGNED NOT NULL,file MEDIUMBLOB NOT NULL);完成表的创建后,或许需要更改max_allowed_packet系统变量。该变量决定了能够发送给MySQL服务器的信息包(即单个行)大小。默认情况下,服务器能够接受来自客户端应用程序的信息包最大为1MB。如果不打算超过1MB,情况良好。如果打算在文件传输中超出1MB,必须增加该数值。
可以使用“MySQL系统管理员的启动变量”屏幕更改max_allowed_packet选项。在“联网”选项卡的“内存”部分,恰当调整“允许的最大值”选项。完成值的调整后,点击“应用更改”按钮,并使用“MySQL管理员”的“服务控制”屏幕重新启动服务器。也可以在my.cnf文件中直接调整该值(添加1行,max_allowed_packet=xxM),或在MySQL中使用SET max_allowed_packet=xxM。
设置max_allowed_packet时应保守些,这是因为传输BLOB数据需要一段时间。恰当地设置该值,使之与预期使用相符,并在必要时增大该值。
26.2.4.5.3. 将文件写入数据库
要想将文件写入数据库,需要将文件转换为字节数组,然后将字节数组用作Insert查询的参数。
在下述代码中,使用FileStream对象打开了1个文件,将其读入至字节数组,然后将其插入到文件表中:
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
string SQL;
UInt32 FileSize;
byte[] rawData;
FileStream fs;
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
fs = new FileStream(@”c:\image.png”, FileMode.Open, FileAccess.Read);
FileSize = fs.Length;
rawData = new byte[FileSize];
fs.Read(rawData, 0, FileSize);
fs.Close();
conn.Open();
SQL = “Insert INTO file VALUES(NULL, ?FileName, ?FileSize, ?File)”;
cmd.Connection = conn;
cmd.CommandText = SQL;
cmd.Parameters.Add(”?FileName”, strFileName);
cmd.Parameters.Add(”?FileSize”, FileSize);
cmd.Parameters.Add(”?File”, rawData);
cmd.ExecuteNonQuery();
MessageBox.Show(”File Inserted into database successfully!”,
“Success!”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
conn.Close();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message,
“Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
FileStream对象的“Read”方法可用于将文件加载到字节数组中,该字节数组的大小是根据FileStream对象的“Length”属性确定的。
将字节数组指定为MySqlCommand对象的参数后,调用ExecuteNonQuery方法,并将BLOB插入到文件表中。
26.2.4.5.4. 将BLOB从数据库读取到磁盘上的文件
一旦将文件加载到了文件表中,就能使用MySqlDataReader类来检索它。
在下述代码中,从文件表提取了1行,然后将数据装载到要写入至磁盘的FileStream对象。
[C#]
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
MySql.Data.MySqlClient.MySqlDataReader myData;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
string SQL;
UInt32 FileSize;
byte[] rawData;
FileStream fs;
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
SQL = “Select file_name, file_size, file FROM file”;
try
{
conn.Open();
cmd.Connection = conn;
cmd.CommandText = SQL;
myData = cmd.ExecuteReader();
if (! myData.HasRows)
throw new Exception(”There are no BLOBs to save”);
myData.Read();
FileSize = myData.GetUInt32(myData.GetOrdinal(”file_size”));
rawData = new byte[FileSize];
myData.GetBytes(myData.GetOrdinal(”file”), 0, rawData, 0, FileSize);
fs = new FileStream(@”C:\newfile.png”, FileMode.OpenOrCreate, FileAccess.Write);
fs.Write(rawData, 0, FileSize);
fs.Close();
MessageBox.Show(”File successfully written to disk!”,
“Success!”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
myData.Close();
conn.Close();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(”Error ” + ex.Number + ” has occurred: ” + ex.Message,
“Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
连接后,文件表的内容将被加载到MySqlDataReader对象中。使用MySqlDataReader的GetBytes方法将BLOB加载到字节数组,然后使用FileStream对象将字节数据写入磁盘。
MySqlDataReader的GetOrdinal方法可用于确定命名列的整数索引。如果Select查询的列顺序发生变化,使用GetOrdinal方法能够防止错误。
26.2.4.6. 与Crystal Reports一起使用MySQL Connector/NET
26.2.4.6.1. 前言
26.2.4.6.2. 创建数据源
26.2.4.6.3. 创建报告
26.2.4.6.4. 显示报告
26.2.4.6.1. 前言
Crystal Reports是Windows应用程序开发人员用于通报文档生成的常用工具。在本节中,介绍了Crystal Reports XI与MySQL和Connector/NET一起使用的方法。
在MySQL Connector/NET安装的Samples目录的CrystalDemo子目录下,可找到完整的示例应用程序。
26.2.4.6.2. 创建数据源
在Crystal Reports中创建报告时,在设计报告时,有两个用于访问MySQL数据的选项。
第1个选项是,设计报告时,使用Connector/ODBC作为ADO数据源。你能够浏览数据库,并使用拖放式操作选择表和字段以创建报告。该方法的缺点是,必须在应用程序中执行额外操作以生成与报告预期的数据集匹配的数据集。
第2个选项是在VB.NET中创建数据集,并将其保存为XML格式。随后,该XML文件可被用于设计报告。在应用程序中显示报告时,它的表现相当良好,但设计时的通用性较差,这是因为在创建数据集时,必须选择所有的相关列。如果忘记选择了某一列,在能够将列添加到报告前,必须重新创建数据集。
使用下述代码,可根据查询操作创建数据集,并将其写入磁盘。
[C#]
DataSet myData = new DataSet();
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
MySql.Data.MySqlClient.MySqlDataAdapter myAdapter;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
myAdapter = new MySql.Data.MySqlClient.MySqlDataAdapter();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
cmd.CommandText = “Select city.name AS cityName, city.population AS CityPopulation, ” +
“country.name, country.population, country.continent ” +
“FROM country, city orDER BY country.continent, country.name”;
cmd.Connection = conn;
myAdapter.SelectCommand = cmd;
myAdapter.Fill(myData);
myData.WriteXml(@”C:\dataset.xml”, XmlWriteMode.WriteSchema);
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message, “Report could not be created”,
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
设计报告时,可将该代码生成的XML文件用作ADO.NET XML数据源。
如果你选择使用Connector/ODBC来设计报告,可从dev.mysql.com下载它。
26.2.4.6.3. 创建报告
对于大多数应用目的,标准的报告向导应能帮助你完成报告的最初创建。要想启动向导,打开Crystal Reports并从“文件”菜单选择“New > Standard Report”选项。
向导首先要求你提供数据源。如果你正使用Connector/ODBC作为数据源,选择数据源时,请使用OLE DB (ADO)树的“用于ODBC的OLEDB provider”选项,,而不是来自ODBC (RDO)的对应选项。如果你使用的是已保存的数据集,请选择ADO.NET (XML)选项,并浏览你保存的数据集。
在报告的创建过程中,剩余部分将由向导自动完成。
创建完报告后,选择“文件”菜单中的“Report Options…”菜单项。取消对“Save Data With Report”(与报告一起保存数据)选项的选择。这样,就能防止保存的数据干扰应用程序中的数据加载操作。
26.2.4.6.4. 显示报告
要想显示报告,首先用报告所需的数据填充数据集,然后加载报告,并将其与绑定到数据集。最后,将报告传递给crViewer控制,以便向用户显示它。
在显示报告的项目中,需要下述引用:
· CrytalDecisions.CrystalReports.Engine
· CrystalDecisions.ReportSource
· CrystalDecisions.Shared
· CrystalDecisions.Windows.Forms
在下述代码中,假定你使用数据集(用创建数据源中给出的代码保存的数据集)创建了报告,并在名为“myViewer”的表单上有1个crViewer控件。
[C#]
using CrystalDecisions.CrystalReports.Engine;
using System.Data;
using MySql.Data.MySqlClient;
ReportDocument myReport = new ReportDocument();
DataSet myData = new DataSet();
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
MySql.Data.MySqlClient.MySqlDataAdapter myAdapter;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
myAdapter = new MySql.Data.MySqlClient.MySqlDataAdapter();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
cmd.CommandText = “Select city.name AS cityName, city.population AS CityPopulation, ” +
“country.name, country.population, country.continent ” +
“FROM country, city orDER BY country.continent, country.name”;
cmd.Connection = conn;
myAdapter.SelectCommand = cmd;
myAdapter.Fill(myData);
myReport.Load(@”.\world_report.rpt”);
myReport.SetDataSource(myData);
myViewer.ReportSource = myReport;
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message, “Report could not be created”,
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
使用相同的查询(用于生成前面保存的数据集),可生成新的数据集。一旦填充了数据集,可使用ReportDocument加载报告文件,并将其与数据集绑定在一起。ReportDocument是作为crViewer的ReportSource而传递的。
使用Connector/ODBC从单个表创建报告时,采用了相同的方法。数据集替换报告中使用的表,并恰当显示报告。
如果报告是使用Connector/ODBC从多个表创建的,在我们的应用程序中必须创建具有多个表的数据集。这样,就能用数据集中的报告替换报告数据源中的各个表。
在我们的MySqlCommand对象中提供多条Select语句,通过该方式,用多个表填充数据集。这些Select语句基于SQL查询,如数据库菜单“Show SQL Query”选项中的“Crystal Reports”中显示的那样。假定有下述查询:
Select `country`.`Name`, `country`.`Continent`, `country`.`Population`, `city`.`Name`, `city`.`Population`FROM `world`.`country` `country` LEFT OUTER JOIN `world`.`city` `city` ON `country`.`Code`=`city`.`CountryCode`ORDER BY `country`.`Continent`, `country`.`Name`, `city`.`Name`该查询将被转换为两条Select查询,并以下述代码显示:
[C#]
using CrystalDecisions.CrystalReports.Engine;
using System.Data;
using MySql.Data.MySqlClient;
ReportDocument myReport = new ReportDocument();
DataSet myData = new DataSet();
MySql.Data.MySqlClient.MySqlConnection conn;
MySql.Data.MySqlClient.MySqlCommand cmd;
MySql.Data.MySqlClient.MySqlDataAdapter myAdapter;
conn = new MySql.Data.MySqlClient.MySqlConnection();
cmd = new MySql.Data.MySqlClient.MySqlCommand();
myAdapter = new MySql.Data.MySqlClient.MySqlDataAdapter();
conn.ConnectionString = “server=127.0.0.1;uid=root;” +
“pwd=12345;database=test;”;
try
{
cmd.CommandText = “Select name, population, countrycode FROM city orDER ” +
“BY countrycode, name; Select name, population, code, continent FROM ” +
“country orDER BY continent, name”;
cmd.Connection = conn;
myAdapter.SelectCommand = cmd;
myAdapter.Fill(myData);
myReport.Load(@”.\world_report.rpt”);
myReport.Database.Tables(0).SetDataSource(myData.T ables(0));
myReport.Database.Tables(1).SetDataSource(myData.T ables(1));
myViewer.ReportSource = myReport;
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
MessageBox.Show(ex.Message, “Report could not be created”,
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
应将Select语句按字母顺序排列,这点很重要,原因在于,这是报告希望其源表所具有的顺序。对于报告中的每个表,均需要一条SetDataSource语句。
该方法会导致性能问题,这是因为Crystal Reports必须在客户端一侧将表绑定在一起,与使用以前保存的数据集相比,速度较慢。
26.2.4.7. 在MySQL Connector/NET中处理日期和时间信息
26.2.4.7.1. 前言
26.2.4.7.2. 使用无效日期时的问题
26.2.4.7.3. 限制无效日期
26.2.4.7.4. 处理无效日期
26.2.4.7.5. 处理NULL日期
26.2.4.7.1. 前言
MySQL和.NET语言处理日期和时间信息的方式是不同的,MySQL允许使用无法由.NET数据类型表示的日期,如“0000-00-00 00:00:00”。如果处理步当,该差异会导致问题。
在本节中,介绍了使用MySQL Connector/NET时恰当处理日期和时间信息的方法。
26.2.4.7.2. 使用无效日期时的问题
对于使用无效日期的开发人员来说,数据处理方面的差异会导致问题。无效的MySQL日期无法被加载到.NET DateTime对象中,包括NULL日期。
由于该原因,不能用MySqlDataAdapter类的Fill方法填充.NET DataSet对象,这是因为无效日期会导致System.ArgumentOutOfRangeException异常。
26.2.4.7.3. 限制无效日期
对日期问题的最佳解决方案是,限制用户输入无效日期。这即可在客户端上进行,也可在服务器端进行。
在客户端上限制无效日期十分简单,即总使用.NET DateTime类来处理日期。DateTime类仅允许有效日期,从而确保了数据库中的值也是有效的。该方法的缺点是,在使用.NET和非.NET代码操作数据库的混合环境下不能使用它,这是因为各应用程序必须执行自己的日期验证。
MySQL 5.0.2和更高版本的用户可使用新的传统SQL模式来限制无效日期值。关于使用传统SQL模式的更多信息,请参见http://dev.mysql.com/doc/mysql/en/server-sql-mode. html。
26.2.4.7.4. 处理无效日期
强烈建议在你的.NET应用程序中应避免使用无效日期,尽管如此,也能tongguo MySqlDateTime数据类型使用无效日期。
MySqlDateTime数据类型支持MySQL服务器支持的相同日期值。MySQL Connector/NET的默认行为是,对有效的日期值返回1个.NET DateTime对象,对无效日期值返回错误。可以更改该默认方式,使MySQL Connector/NET为无效日期返回MySqlDateTime对象。
要想使MySQL Connector/NET为无效日期返回MySqlDateTime对象,可在连接字符串中添加下行:
Allow Zero Datetime=True 请注意,使用MySqlDateTime类仍会产生问题。下面介绍了一些已知问题:
1. 无效日期的数据绑定仍会导致错误(零日期0000-00-00看上去不存在该问题)。
2. ToString方法返回按标准MySQL格式进行格式处理的日期(例如,2005-02-23 08:50:25)。这与.NET DateTime类的ToString行为不同。
3. MySqlDateTime类支持NULL日期,但.NET DateTime类不支持NULL日期。如果未首先检查NULL,在试图将MySQLDateTime转换为DateTime时,会导致错误。
由于存在上述已知事宜,最佳建议仍是,在你的应用程序中仅使用有效日期。
26.2.4.7.5. 处理NULL日期
.NET DateTime数据类型不能处理NULL值。同样,在查询中为DateTime变量赋值时,必须首先检查值是否是NULL。
使用MySqlDataReader时,在赋值前,应使用.IsDBNull方法检查值是否为NULL:
[C#]
if (! myReader.IsDBNull(myReader.GetOrdinal(”mytime”)))
myTime = myReader.GetDateTime(myReader.GetOrdinal(”mytime”));
else
myTime = DateTime.MinValue;
NULL值能够在数据集中使用,也能将其绑定以构成控件,无需特殊处理。
http://www.80yunji.cn/2010/07/342
Tags: C#
(C#/.net)[2011年06月17日]
作者: 日期:2012-05-12
妈妈说,好东西就要和大家分享,还好不是吃的,不然还真有点舍不得呢。。忽忽
下?**艺淼拿夥咽悠到坛蹋焯於蓟嵊懈拢褂械缱邮椋彩敲夥训模恍淮蠹夜刈
O(∩_∩)O~已经做了相关的分类,大家可以根据自己感爱好的方向浏览下载哦!
希看这些资料能对大家有所帮助哦!
C#/.NET:
高质量程序设计指南.C++/C语言(第三版)
http://www.hztraining.com/bbs/showtopic-1318.aspx
Visual C++权威剖析--MFC的原理、机制与开发实例(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1271.aspx
正则表达式进门经典(PDF电子书)
http://www.hztraining.com/bbs/showtopic-1160.aspx
基于.NET技术IM及.NET网络通讯(更新至16课时/共18课时)
http://www.hztraining.com/bbs/showtopic-1315.aspx
郝斌C语言自学教程(180集完整版)
http://www.hztraining.com/bbs/showtopic-1296.aspx
C#数据库编程-ado.net基础篇(更新第二讲/共七讲【压缩包】)
http://www.hztraining.com/bbs/showtopic-1229.aspx
C语言,好爽(第一期)(WMV)
http://www.hztraining.com/bbs/showtopic-1219.aspx
学用Visual C++ 6.0开发Active X控件 (文字版PDF)
http://www.hztraining.com/bbs/showtopic-1224.aspx
使用微软Visual Studio 2010开发网络应用(光盘镜像)
http://www.hztraining.com/bbs/showtopic-1213.aspx
visual c++黑客编程揭秘与防范 (影印版[PDF])
http://www.hztraining.com/bbs/showtopic-1200.aspx
C#4.0中文视频教程 (更新第20课/共20课[压缩包])
http://www.hztraining.com/bbs/showtopic-1191.aspx
鱼C工作室C程序设计 (MP4格式)
http://www.hztraining.com/bbs/showtopic-1194.aspx
程序员典躲之C#范例开发大全(视频+源代码)
http://www.hztraining.com/bbs/showtopic-694.aspx
邮件群发器的开发(.NET&C#方向)
http://www.hztraining.com/bbs/showtopic-624.aspx
C#企业级开发案例精解(PDF电子版)
http://www.hztraining.com/bbs/showtopic-442.aspx
零基础学Visual.c++(PDF电子书)
http://www.hztraining.com/bbs/showtopic-1183.aspx
Visual·C#·2008开发经验与技巧宝典(源代码)
http://www.hztraining.com/bbs/showtopic-670.aspx
免费网上商城网站
http://www.hztraining.com/bbs/showtopic-44.aspx
正则表达式视频
http://www.hztraining.com/bbs/showtopic-66.aspx
C#课程设计案例精编(随书光盘)
http://www.hztraining.com/bbs/showtopic-675.aspx
软件开发视频大讲堂·C#从进门到精通
http://www.hztraining.com/bbs/showtopic-444.aspx
视频教程.30天学通C#项目案例开发
http://www.hztraining.com/bbs/showtopic-534.aspx
微软技术开发之楚广明C#简明教程
http://www.hztraining.com/bbs/showtopic-565.aspx
C#笔试大全整理电子版
http://www.hztraining.com/bbs/showtopic-386.aspx
C#游戏编程(PDF电子书)
http://www.hztraining.com/bbs/showtopic-690.aspx
C#版本家庭理财项目实战案例+仿迅雷下载软件全程开发(winform应用版本)
http://www.hztraining.com/bbs/showtopic-172.aspx
ASP.NET新闻发布系统
http://www.hztraining.com/bbs/showtopic-173.aspx
ASP.NET MVC2 程序进门到精通(10章节版视频教程)
http://www.hztraining.com/bbs/showtopic-658.aspx
ASP.NET3.5从进门到精通视频教程
http://www.hztraining.com/bbs/showtopic-329.aspx
21天学通ASP.NET40小时多媒体语音视频教学DVD
http://www.hztraining.com/bbs/showtopic-184.aspx
天轰穿VS2005进门.Net2.0系列视频教程
http://www.hztraining.com/bbs/showtopic-175.aspx
详解GridView七十二般特技
http://www.hztraining.com/bbs/showtopic-167.aspx
C#和.NET2.0实战:平台、语言与框架(中文电子书)
http://www.hztraining.com/bbs/showtopic-660.aspx
微软插件运用:Silverlight4新功能教程
http://www.hztraining.com/bbs/showtopic-630.aspx
Asp.Net范例开发大全(含视频和源码)
http://www.hztraining.com/bbs/showtopic-693.aspx
ASP.NET开发核心编程基础视频教程
http://www.hztraining.com/bbs/showtopic-629.aspx
ASP.Net与Winform视频教程合集
http://www.hztraining.com/bbs/showtopic-329.aspx
NET口试笔试“葵花宝典”
http://www.hztraining.com/bbs/showtopic-371.aspx
一步一步学ASP.NET MVC中文电子书
http://www.hztraining.com/bbs/showtopic-404.aspx
ASP.NET应用与开发案例教程(高清PDF电子书)
http://www.hztraining.com/bbs/showtopic-554.aspx
ASP.NET信息治理系统开发实例导航(PDF版)
http://www.hztraining.com/bbs/showtopic-448.aspx
ASP.NET 2.0 XML 高级编程(第3版中文)(PDF电子书)
http://www.hztraining.com/bbs/showtopic-537.aspx
黑鹰ASP网页编程(VIP视频教程)
http://www.hztraining.com/bbs/showtopic-666.aspx
ASP从进门到精通视频课程
http://www.hztraining.com/bbs/showtopic-301.aspx
订单查询治理系统Silverlight4(送源码)
http://www.hztraining.com/bbs/showtopic-442.aspx
基于ASP.NET4.0、ExtJs技术构建酒店治理系统(送源码)
http://www.hztraining.com/bbs/showtopic-478.aspx
使用asp.net+NHibernate+Spring开发CMS系统(送源码)
http://www.hztraining.com/bbs/showtopic-482.aspx
JQuery/AjaX/Javascript/DIV+CSS:
Web编程进门经典(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1272.aspx
JavaScript网页殊效范例宝典(影印版PDF)
http://www.hztraining.com/bbs/showtopic-1236.aspx
彻底设计研究css(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1259.aspx
《JavaScript王者回来》(清楚版+高清版 双版本PDF电子书)
http://www.hztraining.com/bbs/showtopic-1188.aspx
JavaScript基础视频教程 (不断更新...)
http://www.hztraining.com/bbs/showtopic-1196.aspx
JavaScript&jsUnit&Ajax&jQuery教学视频 (WMV)
http://www.hztraining.com/bbs/showtopic-1221.aspx
JavaScript视频课程(更新至139课时)
http://www.hztraining.com/bbs/showtopic-1314.aspx
HTML与CSS进门经典(第7版)(PDF)
http://www.hztraining.com/bbs/showtopic-1216.aspx
jQuery攻略(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1210.aspx
JQuery版酷炫图书馆治理系统(+源码)
http://www.hztraining.com/bbs/showtopic-327.aspx
JQuery进门视频教程
http://www.hztraining.com/bbs/showtopic-61.aspx
AJAX进门课程教程
http://www.hztraining.com/bbs/showtopic-40.aspx
AJAX 专家“培”练营 视频
http://www.hztraining.com/bbs/showtopic-185.aspx
jQuery应用指导参考手册(CHM文档)
http://www.hztraining.com/bbs/showtopic-393.aspx
15天学会jQuery(PDF电子版)
http://www.hztraining.com/bbs/showtopic-426.aspx
JQuery:菜鸟到忍者(2010PDF电子书)
http://www.hztraining.com/bbs/showtopic-623.aspx
JQuery基础教程(中文高清PDF电子书)
http://www.hztraining.com/bbs/showtopic-622.aspx
AJAX进门课程教程
http://www.hztraining.com/bbs/showtopic-40.aspx
AJAX 专家“培”练营 视频
http://www.hztraining.com/bbs/showtopic-185.aspx
JavaScript视频教学课程(完整专题版)
http://www.hztraining.com/bbs/showtopic-545.aspx
JavaScript 脚本用户学习指南(CHM)
http://www.hztraining.com/bbs/showtopic-401.aspx
21天学通JavaScript视频教程(含源代码)
http://www.hztraining.com/bbs/showtopic-527.aspx
《精通Javascript+jQuery》视频教程
http://www.hztraining.com/bbs/showtopic-118.aspx
JavaScript视频教程【SWF+PDF】
http://www.hztraining.com/bbs/showtopic-377.aspx
CSS设计彻底研究视频教程(附源码+PPT)
http://www.hztraining.com/bbs/showtopic-347.aspx
精通CSS+DIV网页样式与布局免费视频
http://www.hztraining.com/bbs/showtopic-348.aspx
DIV + CSS 进门视频教程,免费资源 免费资源只用记事本编写代码演示
http://www.hztraining.com/bbs/showtopic-302.aspx
别具光芒:Div+Css网页布局与美化视频教程
http://www.hztraining.com/bbs/showtopic-428.aspx
网页设计:DIV+CSS应用视频教程(高洛峰)
http://www.hztraining.com/bbs/showtopic-597.aspx
CSS2样式表中文手册(CHM版)
http://www.hztraining.com/bbs/showtopic-392.aspx
SEO搜索引擎优化视频教程
http://www.hztraining.com/bbs/showtopic-585.aspx
网站优化手册:SEOBOOK(中文PDF版)
http://www.hztraining.com/bbs/showtopic-587.aspx
PHP/MySQL:
PHP 5 权威编程 (高清300dpi版+清楚版PDF)
http://www.hztraining.com/bbs/showtopic-1288.aspx
PHP专业实例开发(中文版PDF)
http://www.hztraining.com/bbs/showtopic-1298.aspx
PHP与MySQL程序设计(第3版) (高清中文PDF版)
http://www.hztraining.com/bbs/showtopic-1306.aspx
PHP Web 2.0 开发实战(高清楚中文PDF版)
http://www.hztraining.com/bbs/showtopic-1307.aspx
PHP开发典型模块大全(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1322.aspx
MySQL核心技术手册 第2版(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1231.aspx
细说PHP——LAMP兄弟连新版原创视频教程(更新至第96课)
http://www.hztraining.com/bbs/showtopic-1324.aspx
PHP实战(PHP in Action)(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1209.aspx
PHP技术内幕(PHP Black Book)(中文电子书)
http://www.hztraining.com/bbs/showtopic-678.aspx
PHP网络大讲堂.基础视频系列(15章节版)
http://www.hztraining.com/bbs/showtopic-471.aspx
PHP专业项目实例开发(中文版)(PDF电子书)
http://www.hztraining.com/bbs/showtopic-646.aspx
高清PDF电子书.搜索引擎优化高级编程(PHP版)
http://www.hztraining.com/bbs/showtopic-572.aspx
PHP高级程序设计:模式、高晓松 cf 寓言故事框架与测试(中文版)(高清PDF电子书)
http://www.hztraining.com/bbs/showtopic-610.aspx
PHP免费视频教程(台湾中原大学)
http://www.hztraining.com/bbs/showtopic-574.aspx
LAMP兄弟连2010新版PHP教学视频(高洛峰主讲)
http://www.hztraining.com/bbs/showtopic-485.aspx
PHP5.0WEB开发视频教程
http://www.hztraining.com/bbs/showtopic-345.aspx
LAMP兄弟连PHP进门视频教程
http://www.hztraining.com/bbs/showtopic-18.aspx
PHP新手进阶精品教程免费展示
http://www.hztraining.com/bbs/showtopic-397.aspx
PHP100视频教程DVD光盘-第二季
http://www.hztraining.com/bbs/showtopic-215.aspx
礼包放送PHP——LAMP兄弟连原创视频教程
http://www.hztraining.com/bbs/showtopic-216.aspx
网页制作与PHP语言应用(清楚PDF版)
http://www.hztraining.com/bbs/showtopic-421.aspx
PHP5.0WEB开发视频教程
http://www.hztraining.com/bbs/showtopic-345.aspx
Bruce Perens开源系列丛书:PHP 5 权威编程(高清中文电子书)
http://www.hztraining.com/bbs/showtopic-568.aspx
PHP+MySQL黑鹰基地视频教程
http://www.hztraining.com/bbs/showtopic-356.aspx
经典Mysql数据库视频教程
http://www.hztraining.com/bbs/showtopic-355.aspx
MySQL数据库中文参考手册(CHM)
http://www.hztraining.com/bbs/showtopic-400.aspx
MySQL Cookbook(第2版)(高清中文电子书)
http://www.hztraining.com/bbs/showtopic-573.aspx
Oracle11g数据库配置和治理教程(送PPT和源代码)
http://www.hztraining.com/bbs/showtopic-740.aspx
Csharp-Dotnet开发参考免费资源合集分享(C#/.net)
2011年04月07日 [推荐]减肥八大基础知识
(2.5)权瑜——三国头号极品CP
下?**艺淼拿夥咽悠到坛蹋焯於蓟嵊懈拢褂械缱邮椋彩敲夥训模恍淮蠹夜刈
O(∩_∩)O~已经做了相关的分类,大家可以根据自己感爱好的方向浏览下载哦!
希看这些资料能对大家有所帮助哦!
C#/.NET:
高质量程序设计指南.C++/C语言(第三版)
http://www.hztraining.com/bbs/showtopic-1318.aspx
Visual C++权威剖析--MFC的原理、机制与开发实例(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1271.aspx
正则表达式进门经典(PDF电子书)
http://www.hztraining.com/bbs/showtopic-1160.aspx
基于.NET技术IM及.NET网络通讯(更新至16课时/共18课时)
http://www.hztraining.com/bbs/showtopic-1315.aspx
郝斌C语言自学教程(180集完整版)
http://www.hztraining.com/bbs/showtopic-1296.aspx
C#数据库编程-ado.net基础篇(更新第二讲/共七讲【压缩包】)
http://www.hztraining.com/bbs/showtopic-1229.aspx
C语言,好爽(第一期)(WMV)
http://www.hztraining.com/bbs/showtopic-1219.aspx
学用Visual C++ 6.0开发Active X控件 (文字版PDF)
http://www.hztraining.com/bbs/showtopic-1224.aspx
使用微软Visual Studio 2010开发网络应用(光盘镜像)
http://www.hztraining.com/bbs/showtopic-1213.aspx
visual c++黑客编程揭秘与防范 (影印版[PDF])
http://www.hztraining.com/bbs/showtopic-1200.aspx
C#4.0中文视频教程 (更新第20课/共20课[压缩包])
http://www.hztraining.com/bbs/showtopic-1191.aspx
鱼C工作室C程序设计 (MP4格式)
http://www.hztraining.com/bbs/showtopic-1194.aspx
程序员典躲之C#范例开发大全(视频+源代码)
http://www.hztraining.com/bbs/showtopic-694.aspx
邮件群发器的开发(.NET&C#方向)
http://www.hztraining.com/bbs/showtopic-624.aspx
C#企业级开发案例精解(PDF电子版)
http://www.hztraining.com/bbs/showtopic-442.aspx
零基础学Visual.c++(PDF电子书)
http://www.hztraining.com/bbs/showtopic-1183.aspx
Visual·C#·2008开发经验与技巧宝典(源代码)
http://www.hztraining.com/bbs/showtopic-670.aspx
免费网上商城网站
http://www.hztraining.com/bbs/showtopic-44.aspx
正则表达式视频
http://www.hztraining.com/bbs/showtopic-66.aspx
C#课程设计案例精编(随书光盘)
http://www.hztraining.com/bbs/showtopic-675.aspx
软件开发视频大讲堂·C#从进门到精通
http://www.hztraining.com/bbs/showtopic-444.aspx
视频教程.30天学通C#项目案例开发
http://www.hztraining.com/bbs/showtopic-534.aspx
微软技术开发之楚广明C#简明教程
http://www.hztraining.com/bbs/showtopic-565.aspx
C#笔试大全整理电子版
http://www.hztraining.com/bbs/showtopic-386.aspx
C#游戏编程(PDF电子书)
http://www.hztraining.com/bbs/showtopic-690.aspx
C#版本家庭理财项目实战案例+仿迅雷下载软件全程开发(winform应用版本)
http://www.hztraining.com/bbs/showtopic-172.aspx
ASP.NET新闻发布系统
http://www.hztraining.com/bbs/showtopic-173.aspx
ASP.NET MVC2 程序进门到精通(10章节版视频教程)
http://www.hztraining.com/bbs/showtopic-658.aspx
ASP.NET3.5从进门到精通视频教程
http://www.hztraining.com/bbs/showtopic-329.aspx
21天学通ASP.NET40小时多媒体语音视频教学DVD
http://www.hztraining.com/bbs/showtopic-184.aspx
天轰穿VS2005进门.Net2.0系列视频教程
http://www.hztraining.com/bbs/showtopic-175.aspx
详解GridView七十二般特技
http://www.hztraining.com/bbs/showtopic-167.aspx
C#和.NET2.0实战:平台、语言与框架(中文电子书)
http://www.hztraining.com/bbs/showtopic-660.aspx
微软插件运用:Silverlight4新功能教程
http://www.hztraining.com/bbs/showtopic-630.aspx
Asp.Net范例开发大全(含视频和源码)
http://www.hztraining.com/bbs/showtopic-693.aspx
ASP.NET开发核心编程基础视频教程
http://www.hztraining.com/bbs/showtopic-629.aspx
ASP.Net与Winform视频教程合集
http://www.hztraining.com/bbs/showtopic-329.aspx
NET口试笔试“葵花宝典”
http://www.hztraining.com/bbs/showtopic-371.aspx
一步一步学ASP.NET MVC中文电子书
http://www.hztraining.com/bbs/showtopic-404.aspx
ASP.NET应用与开发案例教程(高清PDF电子书)
http://www.hztraining.com/bbs/showtopic-554.aspx
ASP.NET信息治理系统开发实例导航(PDF版)
http://www.hztraining.com/bbs/showtopic-448.aspx
ASP.NET 2.0 XML 高级编程(第3版中文)(PDF电子书)
http://www.hztraining.com/bbs/showtopic-537.aspx
黑鹰ASP网页编程(VIP视频教程)
http://www.hztraining.com/bbs/showtopic-666.aspx
ASP从进门到精通视频课程
http://www.hztraining.com/bbs/showtopic-301.aspx
订单查询治理系统Silverlight4(送源码)
http://www.hztraining.com/bbs/showtopic-442.aspx
基于ASP.NET4.0、ExtJs技术构建酒店治理系统(送源码)
http://www.hztraining.com/bbs/showtopic-478.aspx
使用asp.net+NHibernate+Spring开发CMS系统(送源码)
http://www.hztraining.com/bbs/showtopic-482.aspx
JQuery/AjaX/Javascript/DIV+CSS:
Web编程进门经典(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1272.aspx
JavaScript网页殊效范例宝典(影印版PDF)
http://www.hztraining.com/bbs/showtopic-1236.aspx
彻底设计研究css(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1259.aspx
《JavaScript王者回来》(清楚版+高清版 双版本PDF电子书)
http://www.hztraining.com/bbs/showtopic-1188.aspx
JavaScript基础视频教程 (不断更新...)
http://www.hztraining.com/bbs/showtopic-1196.aspx
JavaScript&jsUnit&Ajax&jQuery教学视频 (WMV)
http://www.hztraining.com/bbs/showtopic-1221.aspx
JavaScript视频课程(更新至139课时)
http://www.hztraining.com/bbs/showtopic-1314.aspx
HTML与CSS进门经典(第7版)(PDF)
http://www.hztraining.com/bbs/showtopic-1216.aspx
jQuery攻略(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1210.aspx
JQuery版酷炫图书馆治理系统(+源码)
http://www.hztraining.com/bbs/showtopic-327.aspx
JQuery进门视频教程
http://www.hztraining.com/bbs/showtopic-61.aspx
AJAX进门课程教程
http://www.hztraining.com/bbs/showtopic-40.aspx
AJAX 专家“培”练营 视频
http://www.hztraining.com/bbs/showtopic-185.aspx
jQuery应用指导参考手册(CHM文档)
http://www.hztraining.com/bbs/showtopic-393.aspx
15天学会jQuery(PDF电子版)
http://www.hztraining.com/bbs/showtopic-426.aspx
JQuery:菜鸟到忍者(2010PDF电子书)
http://www.hztraining.com/bbs/showtopic-623.aspx
JQuery基础教程(中文高清PDF电子书)
http://www.hztraining.com/bbs/showtopic-622.aspx
AJAX进门课程教程
http://www.hztraining.com/bbs/showtopic-40.aspx
AJAX 专家“培”练营 视频
http://www.hztraining.com/bbs/showtopic-185.aspx
JavaScript视频教学课程(完整专题版)
http://www.hztraining.com/bbs/showtopic-545.aspx
JavaScript 脚本用户学习指南(CHM)
http://www.hztraining.com/bbs/showtopic-401.aspx
21天学通JavaScript视频教程(含源代码)
http://www.hztraining.com/bbs/showtopic-527.aspx
《精通Javascript+jQuery》视频教程
http://www.hztraining.com/bbs/showtopic-118.aspx
JavaScript视频教程【SWF+PDF】
http://www.hztraining.com/bbs/showtopic-377.aspx
CSS设计彻底研究视频教程(附源码+PPT)
http://www.hztraining.com/bbs/showtopic-347.aspx
精通CSS+DIV网页样式与布局免费视频
http://www.hztraining.com/bbs/showtopic-348.aspx
DIV + CSS 进门视频教程,免费资源 免费资源只用记事本编写代码演示
http://www.hztraining.com/bbs/showtopic-302.aspx
别具光芒:Div+Css网页布局与美化视频教程
http://www.hztraining.com/bbs/showtopic-428.aspx
网页设计:DIV+CSS应用视频教程(高洛峰)
http://www.hztraining.com/bbs/showtopic-597.aspx
CSS2样式表中文手册(CHM版)
http://www.hztraining.com/bbs/showtopic-392.aspx
SEO搜索引擎优化视频教程
http://www.hztraining.com/bbs/showtopic-585.aspx
网站优化手册:SEOBOOK(中文PDF版)
http://www.hztraining.com/bbs/showtopic-587.aspx
PHP/MySQL:
PHP 5 权威编程 (高清300dpi版+清楚版PDF)
http://www.hztraining.com/bbs/showtopic-1288.aspx
PHP专业实例开发(中文版PDF)
http://www.hztraining.com/bbs/showtopic-1298.aspx
PHP与MySQL程序设计(第3版) (高清中文PDF版)
http://www.hztraining.com/bbs/showtopic-1306.aspx
PHP Web 2.0 开发实战(高清楚中文PDF版)
http://www.hztraining.com/bbs/showtopic-1307.aspx
PHP开发典型模块大全(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1322.aspx
MySQL核心技术手册 第2版(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1231.aspx
细说PHP——LAMP兄弟连新版原创视频教程(更新至第96课)
http://www.hztraining.com/bbs/showtopic-1324.aspx
PHP实战(PHP in Action)(扫描版PDF)
http://www.hztraining.com/bbs/showtopic-1209.aspx
PHP技术内幕(PHP Black Book)(中文电子书)
http://www.hztraining.com/bbs/showtopic-678.aspx
PHP网络大讲堂.基础视频系列(15章节版)
http://www.hztraining.com/bbs/showtopic-471.aspx
PHP专业项目实例开发(中文版)(PDF电子书)
http://www.hztraining.com/bbs/showtopic-646.aspx
高清PDF电子书.搜索引擎优化高级编程(PHP版)
http://www.hztraining.com/bbs/showtopic-572.aspx
PHP高级程序设计:模式、高晓松 cf 寓言故事框架与测试(中文版)(高清PDF电子书)
http://www.hztraining.com/bbs/showtopic-610.aspx
PHP免费视频教程(台湾中原大学)
http://www.hztraining.com/bbs/showtopic-574.aspx
LAMP兄弟连2010新版PHP教学视频(高洛峰主讲)
http://www.hztraining.com/bbs/showtopic-485.aspx
PHP5.0WEB开发视频教程
http://www.hztraining.com/bbs/showtopic-345.aspx
LAMP兄弟连PHP进门视频教程
http://www.hztraining.com/bbs/showtopic-18.aspx
PHP新手进阶精品教程免费展示
http://www.hztraining.com/bbs/showtopic-397.aspx
PHP100视频教程DVD光盘-第二季
http://www.hztraining.com/bbs/showtopic-215.aspx
礼包放送PHP——LAMP兄弟连原创视频教程
http://www.hztraining.com/bbs/showtopic-216.aspx
网页制作与PHP语言应用(清楚PDF版)
http://www.hztraining.com/bbs/showtopic-421.aspx
PHP5.0WEB开发视频教程
http://www.hztraining.com/bbs/showtopic-345.aspx
Bruce Perens开源系列丛书:PHP 5 权威编程(高清中文电子书)
http://www.hztraining.com/bbs/showtopic-568.aspx
PHP+MySQL黑鹰基地视频教程
http://www.hztraining.com/bbs/showtopic-356.aspx
经典Mysql数据库视频教程
http://www.hztraining.com/bbs/showtopic-355.aspx
MySQL数据库中文参考手册(CHM)
http://www.hztraining.com/bbs/showtopic-400.aspx
MySQL Cookbook(第2版)(高清中文电子书)
http://www.hztraining.com/bbs/showtopic-573.aspx
Oracle11g数据库配置和治理教程(送PPT和源代码)
http://www.hztraining.com/bbs/showtopic-740.aspx
Csharp-Dotnet开发参考免费资源合集分享(C#/.net)
2011年04月07日 [推荐]减肥八大基础知识
(2.5)权瑜——三国头号极品CP
Tags: C#
学习Java的小结[2011年11月19日]
作者: 日期:2012-05-12
1. 大多数程序员的首要任务就是用现有的对象解决自己的应用问题。
2. (1) 所有东西都是对象。可将对象想象成一种新型变量;它保存着数据,但可要求它对自身进行操作。理论上讲,可从要解决的问题身上提出所有概念性的组件,然后在程序中将其表达为一个对象。
(2) 程序是一大堆对象的组合;通过消息传递,各对象知道自己该做些什么。为了向对象发出请求,需向那个对象“发送一条消息”。更具体地讲,可将消息想象为一个调用请求,它调用的是从属于目标对象的一个子例程或函数。
(3) 每个对象都有自己的存储空间,可容纳其他对象。或者说,通过封装现有对象,可制作出新型对象。所以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。
(4) 每个对象都有一种类型。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。
(5) 同一类所有对象都能接收相同的消息。这实际是别有含义的一种说法,大家不久便能理解。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收形状消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括“圆”。这一特性称为对象的“可替换性”,是OOP最重要的概念之一。
3. 新建类的时候,首先应考虑“组织”对象;这样做显得更加简单和灵活。利用对象的组织,我们的设计可保持清爽。一旦需要用到继承,就会明显意识到这一点。
4. 我们把这叫作“创建一个成员对象”。新类可由任意数量和类型的其他对象构成。无论如何,只要新类达到了设计要求即可。这个概念叫作“组织”——在现有类的基础上组织一个新类。有时,我们也将组织称作“包含”关系,比如“一辆车包含了一个变速箱”。
5. 在面向对象的程序里,通常都要用到上溯造型技术。这是避免去调查准确类型的一个好办法。我们将这种把衍生类型当作它的基本类型处理的过程叫作“Upcasting”(上溯造型)。
6. 只需将对象放置在堆栈(有时也叫作自动或定域变量)或者静态存储区域即可. 第二个方法是在一个内存池中动态创建对象,该内存池亦叫“堆”或者“内存堆”。
7. 继续器”(Iterator),它属于一种对象,负责选择集合内的元素,并把它们提供给继承器的用户。
8. 单根结构意味着、所有东西归根结底都是一个对象”!所以容纳了Object的一个集合实际可以容纳任何东西。这使我们对它的重复使用变得非常简便。
9. String s;但这里创建的只是句柄,并不是对象。
10. 堆栈。驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。所以尽管有些Java数据要保存在堆栈里——特别是对象句柄,但Java对象并不放到其中。,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相关的代码即可。
11. 但在传递对象时,通常都是指传递指向对象的句柄。
12. 几乎所有运算符都只能操作“主类型”(Primitives)。唯一的例外是“=”、“==”和“!=”,它们能操作所有对象(也是对象易令人混淆的一个地方)。除此以外,String类支持“+”和“+=”。
13. 而==和!=比较的正好就是对象句柄。若想对比两个对象的实际内容是否相同,须使用所有对象都适用的特殊方法equals()。但这个方法不适用于“主类型”,那些类型直接使用==和!=即可。
14. 所以Java程序员不能象C++程序员那样设计自己的过载运算符。
15. 当我们说this的时候,都是指“这个对象”或者“当前对象”。而且它本身会产生当前对象的一个句柄。
16. 一个最直接的做法是在类内部定义变量的同时也为其赋值(注意在C++里不能这样做,尽管C++的新手们总“想”这样做)。
17. 在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那些变量仍会在调用任何方法之前得到初始化——甚至在构建器调用之前。但由于static值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。
Java允许我们将其他static初始化工作划分到类内一个特殊的“static构建从句”(有时也叫作“静态块”)里。它看起来象下面这个样子:
class Spoon {
static int i;
static {
i = 47;
}
18. 接口这样描述自己:“对于实现我的所有类,看起来都应该象我现在这个样子”。 “interface”(接口)关键字使抽象的概念更深入了一层。我们可将其想象为一个“纯”抽象类。
19. 利用private内部类,类设计人员可完全禁止其他人依赖类型编码,并可将具体的实施细节完全隐藏起来。除此以外,从客户程序员的角度来看,一个接口的范围没有意义的,因为他们不能访问不属于公共接口类的任何额外方法。内部类拥有对封装类所有元素的访问权限
20. 无论使用的数组属于什么类型,数组标识符实际都是指向真实对象的一个句柄。那些对象本身是在内存“堆”里创建的。堆对象既可“隐式”创建(即默认产生),亦可“显式”创建(即明确指定,用一个new表达式)。
21. 它也揭示出对象数组和基本数据类型数组在使用方法上几乎是完全一致的。唯一的差别在于对象数组容纳的是句柄,而基本数据类型数组容纳的是具体的数值。
22. 若能创建和访问一个基本数据类型数组,那么比起访问一个封装数据的集合,前者的效率会高出许多。
23. 由于类型信息不复存在,所以集合能肯定的唯一事情就是自己容纳的是指向一个对象的句柄。正式使用它之前,必须对其进行造型,使其具有正确的类型。
24. 反复器”(Iterator)的概念达到这个目的。它可以是一个对象,作用是遍历一系列对象,并选择那个序列中的每个对象,同时不让客户程序员知道或关注那个序列的基础结构。
25. Java的Enumeration(枚举,注释②)便是具有这些限制的一个反复器的例子。除下面这些外,不可再用它做其他任何事情:
(1) 用一个名为elements()的方法要求集合为我们提供一个Enumeration。我们首次调用它的nextElement()时,这个Enumeration会返回序列中的第一个元素。
(2) 用nextElement()获得下一个对象。
(3) 用hasMoreElements()检查序列中是否还有更多的对象。
26. Vector的用法很简单,这已在前面的例子中得到了证明。尽管我们大多数时候只需用addElement()插入对象,用elementAt()一次提取一个对象,并用elements()获得对序列的一个“枚举”。
27. 程序设计一个主要的目标就是“将发生变化的东西同保持不变的东西分隔开”。在这里,保持不变的代码是通用的排序算法,而每次使用时都要变化的是对象的实际比较方法。因此,我们不可将比较代码“硬编码”到多个不同的排序例程内,而是采用“回调”技术。利用回调,经常发生变化的那部分代码会封装到它自己的类内,而总是保持相同的代码则“回调”发生变化的代码。这样一来,不同的对象就可以表达不同的比较方式,同时向它们传递相同的排序代码。
28. 产生一个违例时,会发生几件事情。首先,按照与创建Java对象一样的方法创建违例对象:在内存“堆”里,使用new来创建。随后,停止当前执行路径(记住不可沿这条路径继续下去),然后从当前的环境中释放出违例对象的句柄。此时,违例控制机制会接管一切,并开始查找一个恰当的地方,用于继续程序的执行。这个恰当的地方便是“违例控制器”,它的职责是从问题中恢复,使程序要么尝试另一条执行路径,要么简单地继续。
29. 覆盖一个方法时,只能产生已在方法的基础类版本中定义的违例。这是一个重要的限制,因为它意味着与基础类协同工作的代码也会自动应用于从基础类衍生的任何对象
30. 构建器将对象置于一个安全的起始状态,但它可能执行一些操作——如打开一个文件。除非用户完成对象的使用,并调用一个特殊的清除方法,否则那些操作不会得到正确的清除。若从一个构建器内部“掷”出一个违例,这些清除行为也可能不会正确地发生。所有这些都意味着在编写构建器时,我们必须特别加以留意。
31. 9.8.1 违例准则
用违例做下面这些事情:
(1) 解决问题并再次调用造成违例的方法。
(2) 平息事态的发展,并在不重新尝试方法的前提下继续。
(3) 计算另一些结果,而不是希望方法产生的结果。
(4) 在当前环境中尽可能解决问题,以及将相同的违例重新“掷”出一个更高级的环境。
(5) 在当前环境中尽可能解决问题,以及将不同的违例重新“掷”出一个更高级的环境。
(6) 中止程序执行。
(7) 简化编码。若违例方案使事情变得更加复杂,那就会令人非常烦恼,不如不用。
(8) 使自己的库和程序变得更加安全。这既是一种“短期投资”(便于调试),也是一种“长期投资”(改善应用程序的健壮性)
32. 类型信息在运行期是如何表示的。这时要用到一个名为“Class对象”的特殊形式的对象,其中包含了与类有关的信息(有时也把它叫作“元类”)。事实上,我们要用Class对象创建属于某个类的全部“常规”或“普通”对象。
33. 在运行期,一旦我们想生成那个类的一个对象,用于执行程序的Java虚拟机(JVM)首先就会检查那个类型的Class对象是否已经载入。若尚未载入,JVM就会查找同名的.class文件,并将其载入。所以Java程序启动时并不是完全载入的,这一点与许多传统语言都不同。
一旦那个类型的Class对象进入内存,就用它创建那一类型的所有对象。
34. 可以采用第二种方式来产生Class对象的句柄:使用“类标记”。对上述程序来说,看起来就象下面这样:Gum.class;
35. 稍微总结一下:Java中的所有自变量或参数传递都是通过传递句柄进行的。也就是说,当我们传递“一个对象”时,实际传递的只是指向位于方法外部的那个对象的“一个句柄”。所以一旦要对那个句柄进行任何修改,便相当于修改外部对象。此外:
■参数传递过程中会自动产生别名问题
■不存在本地对象,只有本地句柄
■句柄有自己的作用域,而对象没有
■对象的“存在时间”在Java里不是个问题
■没有语言上的支持(如常量)可防止对象被修改(以避免别名的副作用)
36. 若决定制作一个本地副本,只需简单地使用clone()方法即可
37. 我们通常把这种情况叫作“简单复制”或者“浅层复制”,因为它只复制了一个对象的“表面”部分。实际对象除包含这个“表面”以外,还包括句柄指向的所有对象,以及那些对象又指向的其他所有对象,由此类推。这便是“对象网”或“对象关系网”的由来。
38. 克隆时要注意的两个关键问题是:几乎肯定要调用super.clone(),以及注意将克隆设为public。为使一个对象的克隆能力功成圆满,还需要做另一件事情:实现Cloneable接口。
39. 。==和!=运算符只是简单地对比句柄的内容。若句柄内的地址相同,就认为句柄指向同样的对象
40. 总之,如果希望一个类能够克隆,那么:
(1) 实现Cloneable接口
(2) 覆盖clone()
(3) 在自己的clone()中调用super.clone()
(4) 在自己的clone()中捕获违例
41. 假如想制作对象的一个本地副本,Java中的副本构建器便不是特别适合我们。
42. 事实上,多线程最主要的一个用途就是构建一个“反应灵敏”的用户界面。
43. run()属于那些会与程序中的其他线程“并发”或“同时”执行的代码.
44. 注意端口并不是机器上一个物理上存在的场所,而是一种软件抽象系统服务保留了使用端口1到端口1024的权力
45. “套接字”或者“插座”(Socket)也是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。
46. 有两个基于数据流的套接字类:ServerSocket,服务器用它“侦听”进入的连接;以及Socket,客户用它初始一次连接。
47. ServerSocket需要的只是一个端口编号,不需要IP地址(因为它就在这台机器上运行)。调用accept()时,方法会暂时陷入停顿状态(堵塞),直到某个客户尝试同它建立连接。
48. 范式的基本概念亦可看成是程序设计的基本概念:添加一层新的抽象!只要我们抽象了某些东西,就相当于隔离了特定的细节。而且这后面最引人注目的动机就是“将保持不变的东西身上发生的变化孤立出来”。
49. 这意味着需要找出造成系统改变的最重要的东西,或者换一个角度,找出付出代价最高、开销最大的那一部分。一旦发现了“领头变化”,就可以为自己定下一个焦点,围绕它展开自己的设计。
50. 范式并依据三个标准分类(所有标准都涉及那些可能发生变化的方面)。这三个标准是:
(1) 创建:对象的创建方式。这通常涉及对象创建细节的隔离,这样便不必依赖具体类型的对象,所以在新添一种对象类型时也不必改动代码。
(2) 结构:设计对象,满足特定的项目限制。这涉及对象与其他对象的连接方式,以保证系统内的改变不会影响到这些连接。
(3) 行为:对程序中特定类型的行动进行操纵的对象。这要求我们将希望采取的操作封装起来,比如解释一种语言、实现一个请求、在一个序列中遍历(就象在继承器中那样)或者实现一种算法。本章提供了“观察器”(Observer)和“访问器”(Visitor)的范式的例子
2. (1) 所有东西都是对象。可将对象想象成一种新型变量;它保存着数据,但可要求它对自身进行操作。理论上讲,可从要解决的问题身上提出所有概念性的组件,然后在程序中将其表达为一个对象。
(2) 程序是一大堆对象的组合;通过消息传递,各对象知道自己该做些什么。为了向对象发出请求,需向那个对象“发送一条消息”。更具体地讲,可将消息想象为一个调用请求,它调用的是从属于目标对象的一个子例程或函数。
(3) 每个对象都有自己的存储空间,可容纳其他对象。或者说,通过封装现有对象,可制作出新型对象。所以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。
(4) 每个对象都有一种类型。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。
(5) 同一类所有对象都能接收相同的消息。这实际是别有含义的一种说法,大家不久便能理解。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收形状消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括“圆”。这一特性称为对象的“可替换性”,是OOP最重要的概念之一。
3. 新建类的时候,首先应考虑“组织”对象;这样做显得更加简单和灵活。利用对象的组织,我们的设计可保持清爽。一旦需要用到继承,就会明显意识到这一点。
4. 我们把这叫作“创建一个成员对象”。新类可由任意数量和类型的其他对象构成。无论如何,只要新类达到了设计要求即可。这个概念叫作“组织”——在现有类的基础上组织一个新类。有时,我们也将组织称作“包含”关系,比如“一辆车包含了一个变速箱”。
5. 在面向对象的程序里,通常都要用到上溯造型技术。这是避免去调查准确类型的一个好办法。我们将这种把衍生类型当作它的基本类型处理的过程叫作“Upcasting”(上溯造型)。
6. 只需将对象放置在堆栈(有时也叫作自动或定域变量)或者静态存储区域即可. 第二个方法是在一个内存池中动态创建对象,该内存池亦叫“堆”或者“内存堆”。
7. 继续器”(Iterator),它属于一种对象,负责选择集合内的元素,并把它们提供给继承器的用户。
8. 单根结构意味着、所有东西归根结底都是一个对象”!所以容纳了Object的一个集合实际可以容纳任何东西。这使我们对它的重复使用变得非常简便。
9. String s;但这里创建的只是句柄,并不是对象。
10. 堆栈。驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。所以尽管有些Java数据要保存在堆栈里——特别是对象句柄,但Java对象并不放到其中。,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相关的代码即可。
11. 但在传递对象时,通常都是指传递指向对象的句柄。
12. 几乎所有运算符都只能操作“主类型”(Primitives)。唯一的例外是“=”、“==”和“!=”,它们能操作所有对象(也是对象易令人混淆的一个地方)。除此以外,String类支持“+”和“+=”。
13. 而==和!=比较的正好就是对象句柄。若想对比两个对象的实际内容是否相同,须使用所有对象都适用的特殊方法equals()。但这个方法不适用于“主类型”,那些类型直接使用==和!=即可。
14. 所以Java程序员不能象C++程序员那样设计自己的过载运算符。
15. 当我们说this的时候,都是指“这个对象”或者“当前对象”。而且它本身会产生当前对象的一个句柄。
16. 一个最直接的做法是在类内部定义变量的同时也为其赋值(注意在C++里不能这样做,尽管C++的新手们总“想”这样做)。
17. 在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那些变量仍会在调用任何方法之前得到初始化——甚至在构建器调用之前。但由于static值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。
Java允许我们将其他static初始化工作划分到类内一个特殊的“static构建从句”(有时也叫作“静态块”)里。它看起来象下面这个样子:
class Spoon {
static int i;
static {
i = 47;
}
18. 接口这样描述自己:“对于实现我的所有类,看起来都应该象我现在这个样子”。 “interface”(接口)关键字使抽象的概念更深入了一层。我们可将其想象为一个“纯”抽象类。
19. 利用private内部类,类设计人员可完全禁止其他人依赖类型编码,并可将具体的实施细节完全隐藏起来。除此以外,从客户程序员的角度来看,一个接口的范围没有意义的,因为他们不能访问不属于公共接口类的任何额外方法。内部类拥有对封装类所有元素的访问权限
20. 无论使用的数组属于什么类型,数组标识符实际都是指向真实对象的一个句柄。那些对象本身是在内存“堆”里创建的。堆对象既可“隐式”创建(即默认产生),亦可“显式”创建(即明确指定,用一个new表达式)。
21. 它也揭示出对象数组和基本数据类型数组在使用方法上几乎是完全一致的。唯一的差别在于对象数组容纳的是句柄,而基本数据类型数组容纳的是具体的数值。
22. 若能创建和访问一个基本数据类型数组,那么比起访问一个封装数据的集合,前者的效率会高出许多。
23. 由于类型信息不复存在,所以集合能肯定的唯一事情就是自己容纳的是指向一个对象的句柄。正式使用它之前,必须对其进行造型,使其具有正确的类型。
24. 反复器”(Iterator)的概念达到这个目的。它可以是一个对象,作用是遍历一系列对象,并选择那个序列中的每个对象,同时不让客户程序员知道或关注那个序列的基础结构。
25. Java的Enumeration(枚举,注释②)便是具有这些限制的一个反复器的例子。除下面这些外,不可再用它做其他任何事情:
(1) 用一个名为elements()的方法要求集合为我们提供一个Enumeration。我们首次调用它的nextElement()时,这个Enumeration会返回序列中的第一个元素。
(2) 用nextElement()获得下一个对象。
(3) 用hasMoreElements()检查序列中是否还有更多的对象。
26. Vector的用法很简单,这已在前面的例子中得到了证明。尽管我们大多数时候只需用addElement()插入对象,用elementAt()一次提取一个对象,并用elements()获得对序列的一个“枚举”。
27. 程序设计一个主要的目标就是“将发生变化的东西同保持不变的东西分隔开”。在这里,保持不变的代码是通用的排序算法,而每次使用时都要变化的是对象的实际比较方法。因此,我们不可将比较代码“硬编码”到多个不同的排序例程内,而是采用“回调”技术。利用回调,经常发生变化的那部分代码会封装到它自己的类内,而总是保持相同的代码则“回调”发生变化的代码。这样一来,不同的对象就可以表达不同的比较方式,同时向它们传递相同的排序代码。
28. 产生一个违例时,会发生几件事情。首先,按照与创建Java对象一样的方法创建违例对象:在内存“堆”里,使用new来创建。随后,停止当前执行路径(记住不可沿这条路径继续下去),然后从当前的环境中释放出违例对象的句柄。此时,违例控制机制会接管一切,并开始查找一个恰当的地方,用于继续程序的执行。这个恰当的地方便是“违例控制器”,它的职责是从问题中恢复,使程序要么尝试另一条执行路径,要么简单地继续。
29. 覆盖一个方法时,只能产生已在方法的基础类版本中定义的违例。这是一个重要的限制,因为它意味着与基础类协同工作的代码也会自动应用于从基础类衍生的任何对象
30. 构建器将对象置于一个安全的起始状态,但它可能执行一些操作——如打开一个文件。除非用户完成对象的使用,并调用一个特殊的清除方法,否则那些操作不会得到正确的清除。若从一个构建器内部“掷”出一个违例,这些清除行为也可能不会正确地发生。所有这些都意味着在编写构建器时,我们必须特别加以留意。
31. 9.8.1 违例准则
用违例做下面这些事情:
(1) 解决问题并再次调用造成违例的方法。
(2) 平息事态的发展,并在不重新尝试方法的前提下继续。
(3) 计算另一些结果,而不是希望方法产生的结果。
(4) 在当前环境中尽可能解决问题,以及将相同的违例重新“掷”出一个更高级的环境。
(5) 在当前环境中尽可能解决问题,以及将不同的违例重新“掷”出一个更高级的环境。
(6) 中止程序执行。
(7) 简化编码。若违例方案使事情变得更加复杂,那就会令人非常烦恼,不如不用。
(8) 使自己的库和程序变得更加安全。这既是一种“短期投资”(便于调试),也是一种“长期投资”(改善应用程序的健壮性)
32. 类型信息在运行期是如何表示的。这时要用到一个名为“Class对象”的特殊形式的对象,其中包含了与类有关的信息(有时也把它叫作“元类”)。事实上,我们要用Class对象创建属于某个类的全部“常规”或“普通”对象。
33. 在运行期,一旦我们想生成那个类的一个对象,用于执行程序的Java虚拟机(JVM)首先就会检查那个类型的Class对象是否已经载入。若尚未载入,JVM就会查找同名的.class文件,并将其载入。所以Java程序启动时并不是完全载入的,这一点与许多传统语言都不同。
一旦那个类型的Class对象进入内存,就用它创建那一类型的所有对象。
34. 可以采用第二种方式来产生Class对象的句柄:使用“类标记”。对上述程序来说,看起来就象下面这样:Gum.class;
35. 稍微总结一下:Java中的所有自变量或参数传递都是通过传递句柄进行的。也就是说,当我们传递“一个对象”时,实际传递的只是指向位于方法外部的那个对象的“一个句柄”。所以一旦要对那个句柄进行任何修改,便相当于修改外部对象。此外:
■参数传递过程中会自动产生别名问题
■不存在本地对象,只有本地句柄
■句柄有自己的作用域,而对象没有
■对象的“存在时间”在Java里不是个问题
■没有语言上的支持(如常量)可防止对象被修改(以避免别名的副作用)
36. 若决定制作一个本地副本,只需简单地使用clone()方法即可
37. 我们通常把这种情况叫作“简单复制”或者“浅层复制”,因为它只复制了一个对象的“表面”部分。实际对象除包含这个“表面”以外,还包括句柄指向的所有对象,以及那些对象又指向的其他所有对象,由此类推。这便是“对象网”或“对象关系网”的由来。
38. 克隆时要注意的两个关键问题是:几乎肯定要调用super.clone(),以及注意将克隆设为public。为使一个对象的克隆能力功成圆满,还需要做另一件事情:实现Cloneable接口。
39. 。==和!=运算符只是简单地对比句柄的内容。若句柄内的地址相同,就认为句柄指向同样的对象
40. 总之,如果希望一个类能够克隆,那么:
(1) 实现Cloneable接口
(2) 覆盖clone()
(3) 在自己的clone()中调用super.clone()
(4) 在自己的clone()中捕获违例
41. 假如想制作对象的一个本地副本,Java中的副本构建器便不是特别适合我们。
42. 事实上,多线程最主要的一个用途就是构建一个“反应灵敏”的用户界面。
43. run()属于那些会与程序中的其他线程“并发”或“同时”执行的代码.
44. 注意端口并不是机器上一个物理上存在的场所,而是一种软件抽象系统服务保留了使用端口1到端口1024的权力
45. “套接字”或者“插座”(Socket)也是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。
46. 有两个基于数据流的套接字类:ServerSocket,服务器用它“侦听”进入的连接;以及Socket,客户用它初始一次连接。
47. ServerSocket需要的只是一个端口编号,不需要IP地址(因为它就在这台机器上运行)。调用accept()时,方法会暂时陷入停顿状态(堵塞),直到某个客户尝试同它建立连接。
48. 范式的基本概念亦可看成是程序设计的基本概念:添加一层新的抽象!只要我们抽象了某些东西,就相当于隔离了特定的细节。而且这后面最引人注目的动机就是“将保持不变的东西身上发生的变化孤立出来”。
49. 这意味着需要找出造成系统改变的最重要的东西,或者换一个角度,找出付出代价最高、开销最大的那一部分。一旦发现了“领头变化”,就可以为自己定下一个焦点,围绕它展开自己的设计。
50. 范式并依据三个标准分类(所有标准都涉及那些可能发生变化的方面)。这三个标准是:
(1) 创建:对象的创建方式。这通常涉及对象创建细节的隔离,这样便不必依赖具体类型的对象,所以在新添一种对象类型时也不必改动代码。
(2) 结构:设计对象,满足特定的项目限制。这涉及对象与其他对象的连接方式,以保证系统内的改变不会影响到这些连接。
(3) 行为:对程序中特定类型的行动进行操纵的对象。这要求我们将希望采取的操作封装起来,比如解释一种语言、实现一个请求、在一个序列中遍历(就象在继承器中那样)或者实现一种算法。本章提供了“观察器”(Observer)和“访问器”(Visitor)的范式的例子
Tags: java
SQLServer2005连接jdbc数据驱动[2010年10月26日]
作者: 日期:2012-05-11
SQLServer2005: 1. 打开"SQLServer Configuration Manager" 找到“SqlServer2005 网络配置”选择“MSSQLSERVER 的协议”双击“TCP/IP”选项, 在“协议”选项卡中 启用协议;“IP地址”选项卡中 把所有TCP端口改为 1433 IP如有固定的则使用固定的IP,并书写正确IP, 如果没有则将其中的“IP1, IP2, IP3, ……, IPn”中有一个IP地址“127.0.0.1” 端口设置争取即可;
2. 重启“MSSQLSERVER”服务和 “SQL Server Browser”服务
3. 编写 Java 连接类 ConnectionManager.java
import java.sql.*;
public class ConnectionManager{
static Connection conn = null;
static Statement sta = null;
public static Connection OpenConnection(){
try{
Class.forName("com.microsoft.jdbc.sqlserver.SQLSer verDriver");
}catch(ClassNotFoundException ex){
ex.printStackTrace();
}
//建立连接
try{
conn = DriverManager.getConnection("jdbc:sqlserver://loca lhost:1433;DatabaseName=jspbbs","sqlloginname","sql loginpassword");
sta = conn.createStatement();
}catch(Exception ex){
ex.printStackTrace();
}
if(conn != null && sta != null){
System.out.println("ConnectionState is not null");
}
}
}
2. 重启“MSSQLSERVER”服务和 “SQL Server Browser”服务
3. 编写 Java 连接类 ConnectionManager.java
import java.sql.*;
public class ConnectionManager{
static Connection conn = null;
static Statement sta = null;
public static Connection OpenConnection(){
try{
Class.forName("com.microsoft.jdbc.sqlserver.SQLSer verDriver");
}catch(ClassNotFoundException ex){
ex.printStackTrace();
}
//建立连接
try{
conn = DriverManager.getConnection("jdbc:sqlserver://loca lhost:1433;DatabaseName=jspbbs","sqlloginname","sql loginpassword");
sta = conn.createStatement();
}catch(Exception ex){
ex.printStackTrace();
}
if(conn != null && sta != null){
System.out.println("ConnectionState is not null");
}
}
}
Tags: SQLServer
我的ajax服务端框架-安全问题,初始化设置,[2011年05月02日]
作者: 日期:2012-05-11
吾生也有涯,而知也无涯。以有涯随无涯,殆已。
返回到目录:晒晒我的Ajax服务端框架 我的Ajax服务端框架 - 安全问题
通过前面章节的示例代码,您会发现一个问题:那就是在JS中可以调用所有的C#的方法(理论上是可以调用任何一个程序集中的所有Public类的所有方法)。如果您认为这样做,有安全问题,那么可以订阅事件 OnAjaxCall 来过滤请求。FishWebLib提供的Handler或者Module都有这个事件,您可以统一处理。可参考以下代码: // Ajax调用的安全检查事件。 FishWebLib.Ajax.AjaxMethodV2Handler.OnAjaxCall += new FishWebLib.Ajax.AjaxCallCheckHandler(AjaxMethodV2Handler_OnAjaxCall); /// <summary> /// Ajax调用检查 /// </summary> /// <param name="e"></param> static void AjaxMethodV2Handler_OnAjaxCall(FishWebLib.Ajax.AjaxCallEventArgs e) { // ################################################## ################################################ // 在这里可以做一些在Ajax调用时的安全检查。 // ################################################## ################################################ // 如果经过您的检查逻辑,不允许一个调用请求,可以做如下处理: //e.IsAllowed = false; //e.DenyMessage = "请求的资源不允许访问。"; // 在本示例中,就不处理了。因为在另一个地方,我仍然有机会处理。 }
AjaxCallEventArgs的定义请见后文。
AjaxMethodV1Handler的安全检查示例 // Ajax调用的安全检查事件。 FishWebLib.Ajax.AjaxMethodV1Handler.OnAjaxCall += new FishWebLib.Ajax.AjaxCallCheckHandler(AjaxMethodV1Handler_OnAjaxCall); static void AjaxMethodV1Handler_OnAjaxCall(FishWebLib.Ajax.AjaxCallEventArgs e) { string fileName = System.IO.Path.GetFileNameWithoutExtension(e.context.Request.PhysicalPath); // 在这里,我将只检查要调用的类名是不是以Ajax开头,如果不是,则不允许调用。 if( fileName.StartsWith("Ajax", StringComparison.OrdinalIgnoreCase) == false ) { e.IsAllowed = false; e.DenyMessage = "不允许调用指定的类方法。"; } // 要调用的方法名中URL的查询字符串中,也可以检查到。这里就不检查了。 }
UserControlHandler 和 PageMethodModule 也有 OnAjaxCall 事件,可以按上面的方式来类似处理。
您也可以定义一个统一的安全检查方法,只要符合下面的委托定义即可: namespace FishWebLib.Ajax { /// <summary> /// AJAX调用发生时的委托类型 /// </summary> /// <param name="e">AjaxCallEventArgs类型的事件参数</param> public delegate void AjaxCallCheckHandler(AjaxCallEventArgs e); /// <summary> /// 发生AJAX调用时的事件参数 /// </summary> public sealed class AjaxCallEventArgs : System.EventArgs { /// <summary> /// 本次请求的HttpContext实例 /// </summary> public HttpContext context; /// <summary> /// 调用类型 /// </summary> public AjaxCallType AjaxCallType; /// <summary> /// 调用是否允许 /// </summary> public bool IsAllowed = true; /// <summary> /// 当设置IsAllowed=false时,可为本成员设置一个用于表示禁止访问的消息。 /// </summary> public string DenyMessage; /// <summary> /// 构造方法 /// </summary> /// <param name="cxt">HttpContext对象</param> /// <param name="type">Ajax调用类型</param> public AjaxCallEventArgs(HttpContext cxt, AjaxCallType type) { this.context = cxt; this.AjaxCallType = type; } } /// <summary> /// AJAX调用类型 /// </summary> public enum AjaxCallType { /// <summary> /// 调用C#方法,由AjaxMethodV1Handler引发 /// </summary> AjaxMethodV1, /// <summary> /// 调用用户控件,由UserControlHandler引发 /// </summary> UserControl, /// <summary> /// 调用页面方法,由PageMethodModule引发 /// </summary> PageMethod, /// <summary> /// 调用C#方法,由AjaxMethodV2Handler引发 /// </summary> AjaxMethodV2 } }
如果上面的处理方式仍不能满足要求,那么请创建自己的ashx处理器,实现您自定义的过滤检查,然后调用FishWebLib.Ajax.MethodExecutor中的以下方法: public static void ProcessRequest(HttpContext context, Type type, string method)
我的Ajax服务端框架 - 初始化设置
请参考以下代码:(在演示程序的AppHelper.cs中可以找到)
AjaxMethodV2Handler的初始化设置 /// <summary> /// 设置AjaxMethodV2Handler查找类型时的工作方式。 /// </summary> static void SetAjaxClassSearchMode() { // 这里先说明一下: // 当AjaxMethodV2Handler被Asp.net调用时,需要知道要调用哪个类型的哪个方法。 // 在AjaxMethodV2Handler的默认实现中,调用了AjaxClassSearchHelper.Parse, // 这个方法分析URL,并根据指定的类型查找模式,去查找指定的类型,并获取一个方法名称。 // ################################################## ################################################ // 这里,我们有二种选择: // ################################################## ################################################ // 1. 自己实现一个 ParseTypeMethodPairFromRequest 的委托并赋值给AjaxMethodV2Handler.ParseFunc,这样做有二个好处: // a. 可以实现自己认为更方便的URL,比如URL:/Classname/MethodName.ext // b. 可以检查指定的类型是否允许被Ajax调用。(###安全检查###) // 2. 保持默认的设置,但需要简单的2个配置AjaxClassSearchHelper。 //FishWebLib.Ajax.AjaxClassSearchHelper.Placeholde r = ; // 这个参数这里就不设置了,保持默认值。 FishWebLib.Ajax.AjaxClassSearchHelper.ClassNameSearchPattern = new string[] { // 建议将全部供Ajax调用的类,放在一个命名空间下,这样也可以保证在客户端的JS不至于可以调用所有的类型 // 还可以像Asp.net MVC那样,为所有允许Ajax调用的类,取一个后缀,或者前缀也是可行的。 typeof(MyLab.AjaxService.AjaxOrder).AssemblyQualifiedName.Replace("AjaxOrder", "{0}") }; // ################################################## ###########################################3 // 在本网站中,我们选择第一种方法,但为了方便,我将仍借助于第二种方法来简化实现。 FishWebLib.Ajax.AjaxMethodV2Handler.ParseFunc = MyParseTypeMethodPairFromRequest; } /// <summary> /// 根据当前请求获取要调用的类型及方法名 /// </summary> /// <param name="context"></param> /// <returns></returns> static FishWebLib.Ajax.TypeMethodPair MyParseTypeMethodPairFromRequest(HttpContext context) { // 使用FishWebLib提供的方法,简化实现。 FishWebLib.Ajax.TypeMethodPair result = FishWebLib.Ajax.AjaxClassSearchHelper.Parse(context); if( result != null ) { // 在这里,我还可以检查将要调用的类型和方法是否是允许的。 // 这里的规则很简单:如果不是Ajax开头的类型,将不允许访问 if( result.Type.Name.StartsWith("Ajax") == false ) { // 转向另一个方法的调用,或者返回 null 也是表示禁止访问。 result = new FishWebLib.Ajax.TypeMethodPair(typeof(AppHelper), "DenyAjaxAccess"); } } return result; } /// <summary> /// 禁止Ajax访问时要调用的方法 /// </summary> /// <returns></returns> public static string DenyAjaxAccess() { return "请求的资源不允许访问。"; }
AjaxMethodV1Handler的初始化设置 // 注意:下面的配置指定AjaxMethodV1Handler查找类型的程序集范围。 FishWebLib.Ajax.AjaxMethodV1Handler.AjaxAssemblyName = typeof(AjaxTestClass).Assembly.ToString();
统一的异常处理 // 设置Ajax 调用时的异常事件,处理异常。 FishWebLib.Ajax.AjaxExceptionHelper.OnAjaxInvokeException += new FishWebLib.Ajax.AjaxExceptionAction(AjaxExceptionHelper_OnAjaxInvokeException); /// <summary> /// Ajax异常处理 /// </summary> /// <param name="e"></param> static void AjaxExceptionHelper_OnAjaxInvokeException(FishWebL ib.Ajax.AjaxExceptionEventArgs e) { // 指示异常已经过处理 e.ExceptionHandled = true; // 异常的处理方式也很简单:把异常写入到响应流,并保存异常。 e.context.Response.Write(e.Exception.GetBaseException().Message); SafeLogException(e.Exception); }
我的Ajax服务端框架 - 实现原理
本文将分别介绍FishWebLib提供的三个Handler及一个Module的实现原理。
1. AjaxMethodV1Handler
AjaxMethodV1Handler的主要实现代码如下: public void ProcessRequest(HttpContext context) { if( string.IsNullOrEmpty(AjaxAssemblyName) ) { AjaxCallChecker.WriteSimpleMessage(context, SR.AjaxAssemblyNameIsNull); return; } if( AjaxCallChecker.RaiseCheckEvent(context, OnAjaxCall, AjaxCallType.AjaxMethodV1) == false ) return; string className = System.IO.Path.GetFileNameWithoutExtension(context.Request.PhysicalPath); Type type = TypeManager.GetTypeByName(string.Concat(className, ", ", AjaxAssemblyName)); if( type == null ) { AjaxExceptionHelper.ProcessException(context, new Exception(string.Format(SR.TypeNotFound, className))); return; } MethodExecutor.ProcessRequest(context, type); }
从以上代码可以看出,处理器非常简单:根据要请求的文件名,去掉扩展名,当成类名,然后与参数AjaxAssemblyName合并,得到一个类名的完全限定形式, 最后获取要调用类的具体类型,然后把请求交给MethodExecutor.ProcessRequest()来处理,在那里将会从URL的查询字符串中读取参数method, 就可以得到要调用的方法名。有了类型与方法名后,就可以唯一确定一个方法了,最后只需要去调用就可以了。
至于如何调用方法,如何给方法的参数赋值,最后如何处理返回值给客户端,就属于框架本身的事情了。
所有的这一切,对于客户端来说,更是透明的。这些透明的实现也就是框架的意义了。
2. AjaxMethodV2Handler
AjaxMethodV2Handler的主要实现代码如下: private static ParseTypeMethodPairFromRequest s_ParseFunc = AjaxClassSearchHelper.Parse; public void ProcessRequest(HttpContext context) { if( AjaxCallChecker.RaiseCheckEvent(context, OnAjaxCall, AjaxCallType.AjaxMethodV2) == false ) return; TypeMethodPair pair = s_ParseFunc(context); if( pair == null ) { AjaxExceptionHelper.ProcessException(context, new Exception(SR.InvalidRequest)); return; } MethodExecutor.ProcessRequest(context, pair.Type, pair.Method); }
如果比较AjaxMethodV1Handler的实现,可以发现,AjaxMethodV2Handler更简单。最终也是把请求交给MethodExecutor.ProcessRequest()来处理。 其实,这二个处理器与PageMethodModule的实现是比较类似的:获取一个类型和一个方法名,扔给MethodExecutor就完事了。
只是AjaxMethodV2Handler把“获取类型和方法名”的过程交给委托的实现来处理了。
默认的实现使用了:AjaxClassSearchHelper.Parse ,它能拆分这种形式的URL: class.method.xx
AjaxClassSearchHelper定义了二个数据成员: /// <summary> /// 在URL中用于分隔类名和方法名的特殊字符,默认值:'.' /// </summary> public static char Placeholder = '.'; /// <summary> /// 用于搜索类类型的搜索模式字符串数组。搜索模式通常是一个类型的完全限定字符串中将类名改成{0} /// </summary> public static string[] ClassNameSearchPattern = null;
可以这样设置ClassNameSearchPattern: FishWebLib.Ajax.AjaxClassSearchHelper.ClassNameSearchPattern = new string[] { typeof(MyLab.AjaxService.AjaxOrder).AssemblyQualifiedName.Replace("AjaxOrder", "{0}") };
3. UserControlHandler UserControlHandler的主要实现代码如下: public void ProcessRequest(HttpContext context) { if( AjaxCallChecker.RaiseCheckEvent(context, OnAjaxCall, AjaxCallType.UserControl) == false ) return; string filePath = context.Request.AppRelativeCurrentExecutionFilePath; // 这里不检查指定的用户控件是否存在,如果不存在Asp.net会告诉调用方的。 UcExecutor.ProcessRequest(context, filePath, true); }
从代码可以看出:请求最后是由UcExecutor来处理的。
所以,也可以不使用这个处理器,而是将请求交给C#方法来处理,获取数据后,再去调用UcExecutor.ProcessRequest(),这种做法是符合MVC的设计思想的。
4. PageMethodModule
PageMethodModule的实现与前二个处理器类似,不一样的地方在于它是以Module的形式存在的。
为了能够调用MethodExecutor.ProcessRequest(),它也需要知道一个类型和一个方法名。 有了请求页面地址,就可以知道当前在请求哪个页面,自然也就能获取一个类型了,方法名可以通过从FORM中获取, PageMethodModule会尝试读取FORM中键名为"AjaxPageMethod"对应的值,如果找到,后面的事情就如前面所说的那样处理了。
当然,为了性能,PageMethodModule只会处理POST请求。如果没有从FROM找到方法名,也会忽略本次请求。
返回到目录:晒晒我的Ajax服务端框架
点击此处进入示例展示及下载页面
返回到目录:晒晒我的Ajax服务端框架 我的Ajax服务端框架 - 安全问题
通过前面章节的示例代码,您会发现一个问题:那就是在JS中可以调用所有的C#的方法(理论上是可以调用任何一个程序集中的所有Public类的所有方法)。如果您认为这样做,有安全问题,那么可以订阅事件 OnAjaxCall 来过滤请求。FishWebLib提供的Handler或者Module都有这个事件,您可以统一处理。可参考以下代码: // Ajax调用的安全检查事件。 FishWebLib.Ajax.AjaxMethodV2Handler.OnAjaxCall += new FishWebLib.Ajax.AjaxCallCheckHandler(AjaxMethodV2Handler_OnAjaxCall); /// <summary> /// Ajax调用检查 /// </summary> /// <param name="e"></param> static void AjaxMethodV2Handler_OnAjaxCall(FishWebLib.Ajax.AjaxCallEventArgs e) { // ################################################## ################################################ // 在这里可以做一些在Ajax调用时的安全检查。 // ################################################## ################################################ // 如果经过您的检查逻辑,不允许一个调用请求,可以做如下处理: //e.IsAllowed = false; //e.DenyMessage = "请求的资源不允许访问。"; // 在本示例中,就不处理了。因为在另一个地方,我仍然有机会处理。 }
AjaxCallEventArgs的定义请见后文。
AjaxMethodV1Handler的安全检查示例 // Ajax调用的安全检查事件。 FishWebLib.Ajax.AjaxMethodV1Handler.OnAjaxCall += new FishWebLib.Ajax.AjaxCallCheckHandler(AjaxMethodV1Handler_OnAjaxCall); static void AjaxMethodV1Handler_OnAjaxCall(FishWebLib.Ajax.AjaxCallEventArgs e) { string fileName = System.IO.Path.GetFileNameWithoutExtension(e.context.Request.PhysicalPath); // 在这里,我将只检查要调用的类名是不是以Ajax开头,如果不是,则不允许调用。 if( fileName.StartsWith("Ajax", StringComparison.OrdinalIgnoreCase) == false ) { e.IsAllowed = false; e.DenyMessage = "不允许调用指定的类方法。"; } // 要调用的方法名中URL的查询字符串中,也可以检查到。这里就不检查了。 }
UserControlHandler 和 PageMethodModule 也有 OnAjaxCall 事件,可以按上面的方式来类似处理。
您也可以定义一个统一的安全检查方法,只要符合下面的委托定义即可: namespace FishWebLib.Ajax { /// <summary> /// AJAX调用发生时的委托类型 /// </summary> /// <param name="e">AjaxCallEventArgs类型的事件参数</param> public delegate void AjaxCallCheckHandler(AjaxCallEventArgs e); /// <summary> /// 发生AJAX调用时的事件参数 /// </summary> public sealed class AjaxCallEventArgs : System.EventArgs { /// <summary> /// 本次请求的HttpContext实例 /// </summary> public HttpContext context; /// <summary> /// 调用类型 /// </summary> public AjaxCallType AjaxCallType; /// <summary> /// 调用是否允许 /// </summary> public bool IsAllowed = true; /// <summary> /// 当设置IsAllowed=false时,可为本成员设置一个用于表示禁止访问的消息。 /// </summary> public string DenyMessage; /// <summary> /// 构造方法 /// </summary> /// <param name="cxt">HttpContext对象</param> /// <param name="type">Ajax调用类型</param> public AjaxCallEventArgs(HttpContext cxt, AjaxCallType type) { this.context = cxt; this.AjaxCallType = type; } } /// <summary> /// AJAX调用类型 /// </summary> public enum AjaxCallType { /// <summary> /// 调用C#方法,由AjaxMethodV1Handler引发 /// </summary> AjaxMethodV1, /// <summary> /// 调用用户控件,由UserControlHandler引发 /// </summary> UserControl, /// <summary> /// 调用页面方法,由PageMethodModule引发 /// </summary> PageMethod, /// <summary> /// 调用C#方法,由AjaxMethodV2Handler引发 /// </summary> AjaxMethodV2 } }
如果上面的处理方式仍不能满足要求,那么请创建自己的ashx处理器,实现您自定义的过滤检查,然后调用FishWebLib.Ajax.MethodExecutor中的以下方法: public static void ProcessRequest(HttpContext context, Type type, string method)
我的Ajax服务端框架 - 初始化设置
请参考以下代码:(在演示程序的AppHelper.cs中可以找到)
AjaxMethodV2Handler的初始化设置 /// <summary> /// 设置AjaxMethodV2Handler查找类型时的工作方式。 /// </summary> static void SetAjaxClassSearchMode() { // 这里先说明一下: // 当AjaxMethodV2Handler被Asp.net调用时,需要知道要调用哪个类型的哪个方法。 // 在AjaxMethodV2Handler的默认实现中,调用了AjaxClassSearchHelper.Parse, // 这个方法分析URL,并根据指定的类型查找模式,去查找指定的类型,并获取一个方法名称。 // ################################################## ################################################ // 这里,我们有二种选择: // ################################################## ################################################ // 1. 自己实现一个 ParseTypeMethodPairFromRequest 的委托并赋值给AjaxMethodV2Handler.ParseFunc,这样做有二个好处: // a. 可以实现自己认为更方便的URL,比如URL:/Classname/MethodName.ext // b. 可以检查指定的类型是否允许被Ajax调用。(###安全检查###) // 2. 保持默认的设置,但需要简单的2个配置AjaxClassSearchHelper。 //FishWebLib.Ajax.AjaxClassSearchHelper.Placeholde r = ; // 这个参数这里就不设置了,保持默认值。 FishWebLib.Ajax.AjaxClassSearchHelper.ClassNameSearchPattern = new string[] { // 建议将全部供Ajax调用的类,放在一个命名空间下,这样也可以保证在客户端的JS不至于可以调用所有的类型 // 还可以像Asp.net MVC那样,为所有允许Ajax调用的类,取一个后缀,或者前缀也是可行的。 typeof(MyLab.AjaxService.AjaxOrder).AssemblyQualifiedName.Replace("AjaxOrder", "{0}") }; // ################################################## ###########################################3 // 在本网站中,我们选择第一种方法,但为了方便,我将仍借助于第二种方法来简化实现。 FishWebLib.Ajax.AjaxMethodV2Handler.ParseFunc = MyParseTypeMethodPairFromRequest; } /// <summary> /// 根据当前请求获取要调用的类型及方法名 /// </summary> /// <param name="context"></param> /// <returns></returns> static FishWebLib.Ajax.TypeMethodPair MyParseTypeMethodPairFromRequest(HttpContext context) { // 使用FishWebLib提供的方法,简化实现。 FishWebLib.Ajax.TypeMethodPair result = FishWebLib.Ajax.AjaxClassSearchHelper.Parse(context); if( result != null ) { // 在这里,我还可以检查将要调用的类型和方法是否是允许的。 // 这里的规则很简单:如果不是Ajax开头的类型,将不允许访问 if( result.Type.Name.StartsWith("Ajax") == false ) { // 转向另一个方法的调用,或者返回 null 也是表示禁止访问。 result = new FishWebLib.Ajax.TypeMethodPair(typeof(AppHelper), "DenyAjaxAccess"); } } return result; } /// <summary> /// 禁止Ajax访问时要调用的方法 /// </summary> /// <returns></returns> public static string DenyAjaxAccess() { return "请求的资源不允许访问。"; }
AjaxMethodV1Handler的初始化设置 // 注意:下面的配置指定AjaxMethodV1Handler查找类型的程序集范围。 FishWebLib.Ajax.AjaxMethodV1Handler.AjaxAssemblyName = typeof(AjaxTestClass).Assembly.ToString();
统一的异常处理 // 设置Ajax 调用时的异常事件,处理异常。 FishWebLib.Ajax.AjaxExceptionHelper.OnAjaxInvokeException += new FishWebLib.Ajax.AjaxExceptionAction(AjaxExceptionHelper_OnAjaxInvokeException); /// <summary> /// Ajax异常处理 /// </summary> /// <param name="e"></param> static void AjaxExceptionHelper_OnAjaxInvokeException(FishWebL ib.Ajax.AjaxExceptionEventArgs e) { // 指示异常已经过处理 e.ExceptionHandled = true; // 异常的处理方式也很简单:把异常写入到响应流,并保存异常。 e.context.Response.Write(e.Exception.GetBaseException().Message); SafeLogException(e.Exception); }
我的Ajax服务端框架 - 实现原理
本文将分别介绍FishWebLib提供的三个Handler及一个Module的实现原理。
1. AjaxMethodV1Handler
AjaxMethodV1Handler的主要实现代码如下: public void ProcessRequest(HttpContext context) { if( string.IsNullOrEmpty(AjaxAssemblyName) ) { AjaxCallChecker.WriteSimpleMessage(context, SR.AjaxAssemblyNameIsNull); return; } if( AjaxCallChecker.RaiseCheckEvent(context, OnAjaxCall, AjaxCallType.AjaxMethodV1) == false ) return; string className = System.IO.Path.GetFileNameWithoutExtension(context.Request.PhysicalPath); Type type = TypeManager.GetTypeByName(string.Concat(className, ", ", AjaxAssemblyName)); if( type == null ) { AjaxExceptionHelper.ProcessException(context, new Exception(string.Format(SR.TypeNotFound, className))); return; } MethodExecutor.ProcessRequest(context, type); }
从以上代码可以看出,处理器非常简单:根据要请求的文件名,去掉扩展名,当成类名,然后与参数AjaxAssemblyName合并,得到一个类名的完全限定形式, 最后获取要调用类的具体类型,然后把请求交给MethodExecutor.ProcessRequest()来处理,在那里将会从URL的查询字符串中读取参数method, 就可以得到要调用的方法名。有了类型与方法名后,就可以唯一确定一个方法了,最后只需要去调用就可以了。
至于如何调用方法,如何给方法的参数赋值,最后如何处理返回值给客户端,就属于框架本身的事情了。
所有的这一切,对于客户端来说,更是透明的。这些透明的实现也就是框架的意义了。
2. AjaxMethodV2Handler
AjaxMethodV2Handler的主要实现代码如下: private static ParseTypeMethodPairFromRequest s_ParseFunc = AjaxClassSearchHelper.Parse; public void ProcessRequest(HttpContext context) { if( AjaxCallChecker.RaiseCheckEvent(context, OnAjaxCall, AjaxCallType.AjaxMethodV2) == false ) return; TypeMethodPair pair = s_ParseFunc(context); if( pair == null ) { AjaxExceptionHelper.ProcessException(context, new Exception(SR.InvalidRequest)); return; } MethodExecutor.ProcessRequest(context, pair.Type, pair.Method); }
如果比较AjaxMethodV1Handler的实现,可以发现,AjaxMethodV2Handler更简单。最终也是把请求交给MethodExecutor.ProcessRequest()来处理。 其实,这二个处理器与PageMethodModule的实现是比较类似的:获取一个类型和一个方法名,扔给MethodExecutor就完事了。
只是AjaxMethodV2Handler把“获取类型和方法名”的过程交给委托的实现来处理了。
默认的实现使用了:AjaxClassSearchHelper.Parse ,它能拆分这种形式的URL: class.method.xx
AjaxClassSearchHelper定义了二个数据成员: /// <summary> /// 在URL中用于分隔类名和方法名的特殊字符,默认值:'.' /// </summary> public static char Placeholder = '.'; /// <summary> /// 用于搜索类类型的搜索模式字符串数组。搜索模式通常是一个类型的完全限定字符串中将类名改成{0} /// </summary> public static string[] ClassNameSearchPattern = null;
可以这样设置ClassNameSearchPattern: FishWebLib.Ajax.AjaxClassSearchHelper.ClassNameSearchPattern = new string[] { typeof(MyLab.AjaxService.AjaxOrder).AssemblyQualifiedName.Replace("AjaxOrder", "{0}") };
3. UserControlHandler UserControlHandler的主要实现代码如下: public void ProcessRequest(HttpContext context) { if( AjaxCallChecker.RaiseCheckEvent(context, OnAjaxCall, AjaxCallType.UserControl) == false ) return; string filePath = context.Request.AppRelativeCurrentExecutionFilePath; // 这里不检查指定的用户控件是否存在,如果不存在Asp.net会告诉调用方的。 UcExecutor.ProcessRequest(context, filePath, true); }
从代码可以看出:请求最后是由UcExecutor来处理的。
所以,也可以不使用这个处理器,而是将请求交给C#方法来处理,获取数据后,再去调用UcExecutor.ProcessRequest(),这种做法是符合MVC的设计思想的。
4. PageMethodModule
PageMethodModule的实现与前二个处理器类似,不一样的地方在于它是以Module的形式存在的。
为了能够调用MethodExecutor.ProcessRequest(),它也需要知道一个类型和一个方法名。 有了请求页面地址,就可以知道当前在请求哪个页面,自然也就能获取一个类型了,方法名可以通过从FORM中获取, PageMethodModule会尝试读取FORM中键名为"AjaxPageMethod"对应的值,如果找到,后面的事情就如前面所说的那样处理了。
当然,为了性能,PageMethodModule只会处理POST请求。如果没有从FROM找到方法名,也会忽略本次请求。
返回到目录:晒晒我的Ajax服务端框架
点击此处进入示例展示及下载页面
Tags: ajax
C#4.0和VS2010新特性(一)[2011年09月30日]
作者: 日期:2012-05-11
VS2010被认为将是续写Visual Studio 6 的扛鼎之作。整个IDE不仅是使用了WPF重构,而且使用了最新的NET Framework 4作为强大的后援支撑。从上至下可圈可点。下面我们就来看一看VS2010在哪些方面引人注目——
1)WPF重构界面:整 个VS2010 IDE全部使用WPF重构,因此与Win7外观紧密集成,而且实现了先前所有NET版本所不能实现的一些功能——比如代码的无极缩放(打开一个项目应该可 以看到左下角的显示比率,默认100%;这样您不必切换字体大小了,可以手动输入百分比,可以下拉选择,当然更可以直接Ctrl+鼠标滚轮快捷方式进行调 整)。
2)快速搜索:
I)如果想寻找某个类(方法等)在何处调用,直接选中这个方法(类名),IDE会自动在当前打开的文档中使用淡紫色圈出所有的这个类(方法)名称。
II)快捷键“Ctrl+逗号”呼出搜索类和方法框,直接输入类名(不区分大小写,可以使用Pascal输入形式)自动列出所有的类和方法、属性等。
3)架构体系查看:
要想知道某个项目究竟有哪些文件,它们之间的调用关系等,在VS2010易如反掌——您所要做的只是打开架构浏览器(位于View菜单下的 Architecture Explorer),然后就可以通过点击Solution View或者Class View查看当前项目(或者整个解决方案)的类、方法等。还可以通过文本框输入进行检索(点击一个漏斗图标,检索方式同“快速检索”)。您更可以使用 Ctrl+A的方式选中全部的类(方法),点击“Architecture Explorer”左边第一个按钮,自动创建生成关联图。
当然,你想要知道某个方法在哪些地方被调用了,可以在该方法上右键,选择“Call Hierarchy”(显示层次关系)即可。
4)第三方程序的测试:
您可以在完全不知道第三方的程序情况下对其测试,这个是一个重大的突破。首先您创建一个Test Project,右键加入Coded UI Test文件,打开后选中“Record actions(录制行为)”那个选项,然后打开一个第三方的程序(比如画图板等),你随随便便做一些操作(在此之前务必按下右下角的录制动作的按钮), 然后等到完毕之后再次点击那个停止记录的按钮,然后点击右边那个“Generate Codes”(生成代码)就可以生成代码,您可以对这些代码进行调试了。
5)可选参数和命名话参数(C#):
早些时候如果你想省略某些函数的参数,您不得不定义多次重载该函数以便获得这些函数的不同参数形式。在VB.NET中自带参数省略的功能,但是C# 的程序员只能望尘莫及。现在不必了!C#也完全可以这么做,为您少些诸多重载函数打开方便之门。比如:DoTask (string taskName, bool Repeat=false) {……},但是可缺省参数必须在最后定义,例子中把Repeat移到taskName前是绝对不允许的,而且缺省参数的赋值必须是const类型(要不是写死的,要么是const变量,不能是其它的)。
与此同时,VS2010中还支持乱序给参数赋值——什么意思?如果某个函数有多个参数,你只要(函数名:数值)这种方式,您就可以随心所欲给任何函数参数赋值了。
假如有一个接口 6)协变和反变(Co-variant & Crop-variant)
这是VS2010新增的一个内容,用于在编译的时候确认是否允许不同类型的泛型接口之间是否存在转换的问题。
为了了解“协变”和“反变”的概念,我们先看一个例子:
假设我们定义了一个接口和若干类:
class Father
{
public virtual void Say()
{
Console.WriteLine("Father");
}
}
class Son : Father
{
public override void Say()
{
Console.WriteLine("Son");
}
}
class Son2 : Father
{
public override void Say()
{
Console.WriteLine("Son2");
}
}
Interface InAndOut<T, V>
{
void Input(V value);
T Output();
}
class Program<T,V>:InAndOut<T,V>
{
private object value = default(V);
public T Output()
{
return (T)value;
}
public void Input(V tv)
{
value = tv;
}
}
又假如我们已经实例化两个接口的实例:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son, Father> iobj2 = new Program < Son, Father >();
现在我们令:iobj1= iobj2,可行吗?
乍一看似乎可行——为什么呢?因为联想到右边的两个子类Son会被自动隐式转化成其父类Father。就好像是Father f = new Son()一样可以(注意:我们把子类隐式转化成父类成为“协变”,反之成为“反变”)。而且,根据接口定义,输入方向是接受一个Son(实际存储在iobj2中,被隐式转成Father),输出的时候还是存储的Son被隐式转化成Father输出。
这种思考逻辑固然没有错,但是它有一个前提条件——即从iobj1输入方向看,必须是Son到Father,输出的话也必须是Son到Father!但是 泛型仅仅是一个定义,谁能够保证在类中Father或者Son一定是输入(或者是输出)参数呢?如果我们改变成以下的形式呢?
class Program<T,V>:InAndOut<T,V>
{
private object value = default(T);
public V Output()
{
return (V) value;
}
public void Input(T tv) { value = tv; } } 这样就出现了问题——首先,iobj1指向iobj2(接受一个Father的参数,此时如果我的Father输入的是Son2,那么实际转化到Son的时候就发生异常了;同样地,输出因为是Son2不能转化成Son,因此发生异常)。
这个问题就是因为输入输出方向不明确所导致的。如果我们强制是一开始就给出的输入(输出方向)。即V只能作为输入,T只能作为输出就可以了。
推而广之,假设存在两个泛型T和V,假设V:T(V是T的子类)。那么泛型接口之间转换的一般规则是:输出类型是父类,“输出”一般是协变;反之,输入类型是子类,一般是反变。约束这种输入输出泛型的规则就是:输出线的接口加关键词out,输入加in。如下所示:
Interface InAndOut<out T, in V>
{
void Input(V value);
T Output();
}
那么你输入以下代码,就可以输出结果:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son, Father> iobj2 = new Program < Son, Father >();
这种规则本质上是编译器的行为理解。但是不一定就是正确结果,考察下列例子:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son2, Father> iobj2 = new Program < Son2, Father >();
这一段代码照样可以通过编译,但是运行仍旧报异常。为什么呢?因为“输入端”和“输出端”尽管都符合了隐式转换的条件,但是你注意:把一个Son对象存储到iobj2的时候,iboj2的输出要求是Son2,而不是Son! 因此要保证运行正确,必须做到这样:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son, Father> iobj2 = new Program < Son, Father >();
输入端接受Son,能够隐式转成蓝色的Father,蓝色Father存储的子类对象同样必须可以转化成Son(即一个协变的东西必须能够支持其反变;反之,一个反变的泛型必须支持其协变泛型,这就是著名的“协变反变类型”,简称“协-反变类型”)。
证明:(红色的Son开始):隐式转化成Father(协变),然后蓝色的Father(其中存储Son)强制转化成绿色的Son(反变)。
同理,(黑色的Father开始):黑色Father返回的内容(存储Son)可以强制转化成蓝色Father的内容(反变),同时可以隐式转化成绿色Son(协变)。我觉得可以使用对角线规则验证(猜想,对于任意的泛型A,B,C,D):
InAndOut<A,B>
(B既可以转成D,也可以转成C;输出A包含的内容
可以转化成D,也可以转化成C)
InAndOut<C, D> VS2010之所以那么强大,究其原因是其背后有着强大的C#4.0作为后台支撑。和以往的所有版本相比,C#4.0的动态性大大增强——dynamic就是一个非常明显的例子:
(一)dynamic初探:
以前因为某些特殊原因,需要动态的调用外部类(假设这个类是实现了某个带有参数的接口函数的),通常我们只能用反射了。示例代码如下:
Assembly asm = Assembly.LoadFile(“xxxxx”)
asm.CreateInstance("MyAssembly.ClassName").GetType ().InvokeMember("Say", BindingFlags.InvokeMethod, null, asm.CreateInstance("MyAssembly.ClassName "), new string[] { "aaa" });
这里顺便简略说一下反射流程:首先通过绝对路径加载某个NET的dll文件,然后创建该assembly中某个class的instance(该 class必须有无参构造函数),获取其类型之后动态调用其函数Say,“BindingFlags.InvokeMethod”表明是一个普通类方 法,“null”的地方是传递一个参数名的,和指明最后的string[]中的一串values内容一一匹配的……可见使用反射调用函数是很痛苦的一件事 情。
现在呢?您根本不需要那么麻烦了!因为C#的dynamic会为您做好一切的,下面就是见证奇迹的时刻——
Assembly asm = Assembly.LoadFile("xxxxx");
dynamic dfun = asm.CreateInstance("MyAssembly.ClassName");
dfun.Say("Hello!");
注意到咖啡色的代码了么——什么?dynamic竟然可以智能感知出动态加载的那个类的方法Say?其实不然:当你按下这个点的时候,IDE是没有智能感 知的,但是如果你知道这个类是有这个方法(因为接口给了其一个契约,必须实现接口中的方法;而接口的方法是公开的),你就可以完全不理会智能感知,照样 写,照样编译通过运行。神奇吧!
看到这里,你就不会认为dynamic和var是“差不多”的概念了(var无非是根据赋值的类型编译器自己判断;且var不能作为函数返回值类型,但是dynamic可以)。
或许有人会疑问:dynamic可以完全替代类似像简单工厂、抽象工厂一类的东西了咯?我的理解是——不对!从上面的定义中可以得知:dynamic必须首先获取对象实例,然后动态反射是它做的事情;如果完全取代反射,实例也获取不到,如何反射呢?真是“巧妇难为无米之炊”啊!
说道dynamic可以作为返回值,下面给出一个例子:
class DynamicClass
{
public int Num1 { get; set; }
public int Num2 { get; set; }
public DynamicClass(int n1, int n2)
{
Num1 = n1;
Num2 = n2;
}
public dynamic DynamicAction
{ get; set; }
}
主函数注意咖啡色部分:
static void Main(string[] args)
{
DynamicClass t = new DynamicClass(1, 2);
t.DynamicAction = new Func<int, int, double>((x, y) => x + y);
Console.WriteLine(t.DynamicAction.Invoke(t.Num1,t.Num2));
}
道理很简单:因为dynamic类型可以赋值任何东西(包括匿名委托),所以我创建了一个匿名委托给它。然后调用计算结果(匿名委托的调用使用Invoke,可以省略)。
但是……dynamic不仅仅可以动态反射类方法和属性,还可以“空中楼阁”般动态地去创建一个类方法和属性,并且赋值,相信吗?这是第二话。
(二)神奇的ExpandoObject类和自定义动态类扩展:
dynamic在第一话中已经展示它动态根据赋值类型直接自动完成反射的强大功能。现在又是一个新奇迹的诞生——
static void Main(string[] args)
{
dynamic d = new ExpandoObject();
d.Name = "ServiceBoy";
d.Action = Func<string>(()=>d.Name;);
Console.WriteLine(d.Action());
}
初看这个代码只是简单的读写Name属性,毫无稀奇可言。但是你注意哦——你到MSDN——或者你索性new ExpandoObject().Name 试试看,有Name和Action这个属性吗?——没有啊,真的没有!嘿,奇了怪了,既然没有,为什么你可以凭空“捏造出一个属性”,而且可以给属性赋 值,并且读取属性内容呢?
俗话说的好——天下没有白给的食——微软这个类意在向我们揭露一个惊天的大秘密,那就是你可以自定义dynamic类,让这个类跟随你的要求动态的改变自 己(比如增加一个新属性等)。我们可以参照MSDN,给出一个自定义的ExpandoObject:
public class SimpleDynamic : DynamicObject
{
Dictionary<string, object> Properties = new Dictionary<string, object>();
Dictionary<string, object[]> Methods = new Dictionary<string, object[]>();
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
if (!Methods.Keys.Contains(binder.Name)) { Methods.Add(binder.Name, null); } if (args != null) { Methods[binder.Name] = args; } StringBuilder sbu = new StringBuilder(); foreach (var item in args) { sbu.Append(item); } result = sbu.ToString(); return true; } public override bool TrySetMember(SetMemberBinder binder, object value) { if (!Properties.Keys.Contains(binder.Name)) { Properties.Add(binder.Name, value.ToString()); } return true; } public override bool TryGetMember(GetMemberBinder binder, out object result) { return Properties.TryGetValue(binder.Name, out result); } } 首先说明这个例子的作用:随意增加不重复的属性并赋值(取值),并且让你随意创建或者调用(带参或无参)函数进行输入(输出)。 分析一下这个类的主要特点:
一般地,任何一个类——如果需要动态为自身添加属性、方法等的,就必须实现IDynamicObjectProvidor接口或者是 DynamicObject虚类(之所以用虚类的原因是“各取所需”的缘故,DynamicObject类都通过虚方法virtual去“实现”了接口中 所有的方法,只要继承了这个类,读者可以根据需要“任意”动态覆盖你要的方法)。这里介绍三个最常见的方法: 如果需要支持动态创建写属性,必须覆盖TrySetMember,其方法介绍如下:
参数名称 作用说明 binder:SetMemberBinder类型 用于获取动态创建赋值属性的时候“属性名”等一些常见信息(如示例中Name获取动态赋值的那个属性)。 value:object类型 用于获取设置动态属性的那个值。 如果需要支持动态创建读属性,必须覆盖TryGetMember,其参数作用和TrySetMember大致相当,只是反作用(用于获取 某个已有属性的内容,并且反向传递给object作为输出结果,注意TryGetMember的value是一个out类型)。同时,这个函数多出一个 result类型,用于返回已有属性的存储的值(NULL抛出异常,被认为是错误的)。
如果需要动态调用函数并输出结果,必须覆盖TryInvokeMember方法,此函数比较复杂:
参数名称 作用说明 binder:InvokeMemberBinder类型 用于获取动态创建函数时候一些与函数相关的属性:
(比如Name是函数名,其中还有一个CallInfo内嵌属性,您还可以获得ArgumentNames(C#4.0中最新的可选参数的名称,通过其ArgumentNameCount获取可选参数名的总个数))。 Args:object[]类型 获取动态给函数赋的值。 result:object类型 返回动态函数执行的结果,Null表示异常。 根据以上表格,对照不难读懂我的示例代码——现在假设你是这样调用的:
1)
dynamic d = new SimpleDynamic();
d.Name = “Serviceboy”;
Console.WriteLine(d.Name);
首先创建了一个d的动态类型,然后当赋值给Name的时候,因为Name是属性,所以触发了“TrySetMember”函数,该函数自动检查是否 已经存在这个属性名,如果不存在,则将其添加进入一个Dictionary中并将对应赋予的值传递进去保存起来。当使用输出的时候,同样 地,TryGetMember被触发,系统检测是否预先创建过这个值,如果没有,则抛出异常;存在的话,取出对应的存储value并返回给系统。
2)
dynamic d = new SimpleDynamic();
Console.WriteLine(d.Say(“Hello!”));
首先创建了一个d的动态类型,当动态创建一个方法的时候,系统检测是否包含这个方法名,不包含将添加这个方法名到Dictionary保存,接着检查参数 是否为空,不为空把参数赋值给那个函数名作为Key的Dictionary中保存,最后使用StringBuilder串起来赋值给result作为输 出。
下面给出一个比较复杂的例子——自定义的XML创建器(仿Jeffery Zhao):
public class XmlCreator : DynamicObject
{
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
//如果除了第一个节点是字符串,后面都是XElement对象,表明此节点是父节点
if (args[1] is XElement)
{
XElement root = new XElement(args[0].ToString());
//把子节点添加到父节点
for (int i = 1; i < args.Length; ++i)
{
root.Add(args[i]);
}
result = root;
}
//否则是子节点
else
{
//拷贝所有属性到数组:
string[] attributes = new string[binder.CallInfo.ArgumentNames.Count];
for (int i = 0; i < binder.CallInfo.ArgumentNames.Count; i++)
{
attributes[i] = binder.CallInfo.ArgumentNames[i];
}
//拷贝所有属性值到数组:
string[] values = new string[args.Length - 1];
for (int i = 1; i < args.Length; ++i)
{
values[i - 1] = args[i].ToString();
}
XElement subelement = new XElement(args[0].ToString());
//属性名称获取
for (int i = 0; i < attributes.Length; ++i)
{
subelement.SetAttributeValue(attributes[i], values[i]);
}
result = subelement;
}
return result != null;
}
}
该函数功能是:输出任意同时带有属性的节点,同时可以嵌套——比如:
dynamic xmlCreator = new XmlCreator();
XElement ele = xmlCreator.CreateElement(“Books”,
xmlCreator(“Book”,name:C#,price:100.50) ); 大家可以自己想一想是怎么一个原理哦。
1)WPF重构界面:整 个VS2010 IDE全部使用WPF重构,因此与Win7外观紧密集成,而且实现了先前所有NET版本所不能实现的一些功能——比如代码的无极缩放(打开一个项目应该可 以看到左下角的显示比率,默认100%;这样您不必切换字体大小了,可以手动输入百分比,可以下拉选择,当然更可以直接Ctrl+鼠标滚轮快捷方式进行调 整)。
2)快速搜索:
I)如果想寻找某个类(方法等)在何处调用,直接选中这个方法(类名),IDE会自动在当前打开的文档中使用淡紫色圈出所有的这个类(方法)名称。
II)快捷键“Ctrl+逗号”呼出搜索类和方法框,直接输入类名(不区分大小写,可以使用Pascal输入形式)自动列出所有的类和方法、属性等。
3)架构体系查看:
要想知道某个项目究竟有哪些文件,它们之间的调用关系等,在VS2010易如反掌——您所要做的只是打开架构浏览器(位于View菜单下的 Architecture Explorer),然后就可以通过点击Solution View或者Class View查看当前项目(或者整个解决方案)的类、方法等。还可以通过文本框输入进行检索(点击一个漏斗图标,检索方式同“快速检索”)。您更可以使用 Ctrl+A的方式选中全部的类(方法),点击“Architecture Explorer”左边第一个按钮,自动创建生成关联图。
当然,你想要知道某个方法在哪些地方被调用了,可以在该方法上右键,选择“Call Hierarchy”(显示层次关系)即可。
4)第三方程序的测试:
您可以在完全不知道第三方的程序情况下对其测试,这个是一个重大的突破。首先您创建一个Test Project,右键加入Coded UI Test文件,打开后选中“Record actions(录制行为)”那个选项,然后打开一个第三方的程序(比如画图板等),你随随便便做一些操作(在此之前务必按下右下角的录制动作的按钮), 然后等到完毕之后再次点击那个停止记录的按钮,然后点击右边那个“Generate Codes”(生成代码)就可以生成代码,您可以对这些代码进行调试了。
5)可选参数和命名话参数(C#):
早些时候如果你想省略某些函数的参数,您不得不定义多次重载该函数以便获得这些函数的不同参数形式。在VB.NET中自带参数省略的功能,但是C# 的程序员只能望尘莫及。现在不必了!C#也完全可以这么做,为您少些诸多重载函数打开方便之门。比如:DoTask (string taskName, bool Repeat=false) {……},但是可缺省参数必须在最后定义,例子中把Repeat移到taskName前是绝对不允许的,而且缺省参数的赋值必须是const类型(要不是写死的,要么是const变量,不能是其它的)。
与此同时,VS2010中还支持乱序给参数赋值——什么意思?如果某个函数有多个参数,你只要(函数名:数值)这种方式,您就可以随心所欲给任何函数参数赋值了。
假如有一个接口 6)协变和反变(Co-variant & Crop-variant)
这是VS2010新增的一个内容,用于在编译的时候确认是否允许不同类型的泛型接口之间是否存在转换的问题。
为了了解“协变”和“反变”的概念,我们先看一个例子:
假设我们定义了一个接口和若干类:
class Father
{
public virtual void Say()
{
Console.WriteLine("Father");
}
}
class Son : Father
{
public override void Say()
{
Console.WriteLine("Son");
}
}
class Son2 : Father
{
public override void Say()
{
Console.WriteLine("Son2");
}
}
Interface InAndOut<T, V>
{
void Input(V value);
T Output();
}
class Program<T,V>:InAndOut<T,V>
{
private object value = default(V);
public T Output()
{
return (T)value;
}
public void Input(V tv)
{
value = tv;
}
}
又假如我们已经实例化两个接口的实例:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son, Father> iobj2 = new Program < Son, Father >();
现在我们令:iobj1= iobj2,可行吗?
乍一看似乎可行——为什么呢?因为联想到右边的两个子类Son会被自动隐式转化成其父类Father。就好像是Father f = new Son()一样可以(注意:我们把子类隐式转化成父类成为“协变”,反之成为“反变”)。而且,根据接口定义,输入方向是接受一个Son(实际存储在iobj2中,被隐式转成Father),输出的时候还是存储的Son被隐式转化成Father输出。
这种思考逻辑固然没有错,但是它有一个前提条件——即从iobj1输入方向看,必须是Son到Father,输出的话也必须是Son到Father!但是 泛型仅仅是一个定义,谁能够保证在类中Father或者Son一定是输入(或者是输出)参数呢?如果我们改变成以下的形式呢?
class Program<T,V>:InAndOut<T,V>
{
private object value = default(T);
public V Output()
{
return (V) value;
}
public void Input(T tv) { value = tv; } } 这样就出现了问题——首先,iobj1指向iobj2(接受一个Father的参数,此时如果我的Father输入的是Son2,那么实际转化到Son的时候就发生异常了;同样地,输出因为是Son2不能转化成Son,因此发生异常)。
这个问题就是因为输入输出方向不明确所导致的。如果我们强制是一开始就给出的输入(输出方向)。即V只能作为输入,T只能作为输出就可以了。
推而广之,假设存在两个泛型T和V,假设V:T(V是T的子类)。那么泛型接口之间转换的一般规则是:输出类型是父类,“输出”一般是协变;反之,输入类型是子类,一般是反变。约束这种输入输出泛型的规则就是:输出线的接口加关键词out,输入加in。如下所示:
Interface InAndOut<out T, in V>
{
void Input(V value);
T Output();
}
那么你输入以下代码,就可以输出结果:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son, Father> iobj2 = new Program < Son, Father >();
这种规则本质上是编译器的行为理解。但是不一定就是正确结果,考察下列例子:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son2, Father> iobj2 = new Program < Son2, Father >();
这一段代码照样可以通过编译,但是运行仍旧报异常。为什么呢?因为“输入端”和“输出端”尽管都符合了隐式转换的条件,但是你注意:把一个Son对象存储到iobj2的时候,iboj2的输出要求是Son2,而不是Son! 因此要保证运行正确,必须做到这样:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son, Father> iobj2 = new Program < Son, Father >();
输入端接受Son,能够隐式转成蓝色的Father,蓝色Father存储的子类对象同样必须可以转化成Son(即一个协变的东西必须能够支持其反变;反之,一个反变的泛型必须支持其协变泛型,这就是著名的“协变反变类型”,简称“协-反变类型”)。
证明:(红色的Son开始):隐式转化成Father(协变),然后蓝色的Father(其中存储Son)强制转化成绿色的Son(反变)。
同理,(黑色的Father开始):黑色Father返回的内容(存储Son)可以强制转化成蓝色Father的内容(反变),同时可以隐式转化成绿色Son(协变)。我觉得可以使用对角线规则验证(猜想,对于任意的泛型A,B,C,D):
InAndOut<A,B>
(B既可以转成D,也可以转成C;输出A包含的内容
可以转化成D,也可以转化成C)
InAndOut<C, D> VS2010之所以那么强大,究其原因是其背后有着强大的C#4.0作为后台支撑。和以往的所有版本相比,C#4.0的动态性大大增强——dynamic就是一个非常明显的例子:
(一)dynamic初探:
以前因为某些特殊原因,需要动态的调用外部类(假设这个类是实现了某个带有参数的接口函数的),通常我们只能用反射了。示例代码如下:
Assembly asm = Assembly.LoadFile(“xxxxx”)
asm.CreateInstance("MyAssembly.ClassName").GetType ().InvokeMember("Say", BindingFlags.InvokeMethod, null, asm.CreateInstance("MyAssembly.ClassName "), new string[] { "aaa" });
这里顺便简略说一下反射流程:首先通过绝对路径加载某个NET的dll文件,然后创建该assembly中某个class的instance(该 class必须有无参构造函数),获取其类型之后动态调用其函数Say,“BindingFlags.InvokeMethod”表明是一个普通类方 法,“null”的地方是传递一个参数名的,和指明最后的string[]中的一串values内容一一匹配的……可见使用反射调用函数是很痛苦的一件事 情。
现在呢?您根本不需要那么麻烦了!因为C#的dynamic会为您做好一切的,下面就是见证奇迹的时刻——
Assembly asm = Assembly.LoadFile("xxxxx");
dynamic dfun = asm.CreateInstance("MyAssembly.ClassName");
dfun.Say("Hello!");
注意到咖啡色的代码了么——什么?dynamic竟然可以智能感知出动态加载的那个类的方法Say?其实不然:当你按下这个点的时候,IDE是没有智能感 知的,但是如果你知道这个类是有这个方法(因为接口给了其一个契约,必须实现接口中的方法;而接口的方法是公开的),你就可以完全不理会智能感知,照样 写,照样编译通过运行。神奇吧!
看到这里,你就不会认为dynamic和var是“差不多”的概念了(var无非是根据赋值的类型编译器自己判断;且var不能作为函数返回值类型,但是dynamic可以)。
或许有人会疑问:dynamic可以完全替代类似像简单工厂、抽象工厂一类的东西了咯?我的理解是——不对!从上面的定义中可以得知:dynamic必须首先获取对象实例,然后动态反射是它做的事情;如果完全取代反射,实例也获取不到,如何反射呢?真是“巧妇难为无米之炊”啊!
说道dynamic可以作为返回值,下面给出一个例子:
class DynamicClass
{
public int Num1 { get; set; }
public int Num2 { get; set; }
public DynamicClass(int n1, int n2)
{
Num1 = n1;
Num2 = n2;
}
public dynamic DynamicAction
{ get; set; }
}
主函数注意咖啡色部分:
static void Main(string[] args)
{
DynamicClass t = new DynamicClass(1, 2);
t.DynamicAction = new Func<int, int, double>((x, y) => x + y);
Console.WriteLine(t.DynamicAction.Invoke(t.Num1,t.Num2));
}
道理很简单:因为dynamic类型可以赋值任何东西(包括匿名委托),所以我创建了一个匿名委托给它。然后调用计算结果(匿名委托的调用使用Invoke,可以省略)。
但是……dynamic不仅仅可以动态反射类方法和属性,还可以“空中楼阁”般动态地去创建一个类方法和属性,并且赋值,相信吗?这是第二话。
(二)神奇的ExpandoObject类和自定义动态类扩展:
dynamic在第一话中已经展示它动态根据赋值类型直接自动完成反射的强大功能。现在又是一个新奇迹的诞生——
static void Main(string[] args)
{
dynamic d = new ExpandoObject();
d.Name = "ServiceBoy";
d.Action = Func<string>(()=>d.Name;);
Console.WriteLine(d.Action());
}
初看这个代码只是简单的读写Name属性,毫无稀奇可言。但是你注意哦——你到MSDN——或者你索性new ExpandoObject().Name 试试看,有Name和Action这个属性吗?——没有啊,真的没有!嘿,奇了怪了,既然没有,为什么你可以凭空“捏造出一个属性”,而且可以给属性赋 值,并且读取属性内容呢?
俗话说的好——天下没有白给的食——微软这个类意在向我们揭露一个惊天的大秘密,那就是你可以自定义dynamic类,让这个类跟随你的要求动态的改变自 己(比如增加一个新属性等)。我们可以参照MSDN,给出一个自定义的ExpandoObject:
public class SimpleDynamic : DynamicObject
{
Dictionary<string, object> Properties = new Dictionary<string, object>();
Dictionary<string, object[]> Methods = new Dictionary<string, object[]>();
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
if (!Methods.Keys.Contains(binder.Name)) { Methods.Add(binder.Name, null); } if (args != null) { Methods[binder.Name] = args; } StringBuilder sbu = new StringBuilder(); foreach (var item in args) { sbu.Append(item); } result = sbu.ToString(); return true; } public override bool TrySetMember(SetMemberBinder binder, object value) { if (!Properties.Keys.Contains(binder.Name)) { Properties.Add(binder.Name, value.ToString()); } return true; } public override bool TryGetMember(GetMemberBinder binder, out object result) { return Properties.TryGetValue(binder.Name, out result); } } 首先说明这个例子的作用:随意增加不重复的属性并赋值(取值),并且让你随意创建或者调用(带参或无参)函数进行输入(输出)。 分析一下这个类的主要特点:
一般地,任何一个类——如果需要动态为自身添加属性、方法等的,就必须实现IDynamicObjectProvidor接口或者是 DynamicObject虚类(之所以用虚类的原因是“各取所需”的缘故,DynamicObject类都通过虚方法virtual去“实现”了接口中 所有的方法,只要继承了这个类,读者可以根据需要“任意”动态覆盖你要的方法)。这里介绍三个最常见的方法: 如果需要支持动态创建写属性,必须覆盖TrySetMember,其方法介绍如下:
参数名称 作用说明 binder:SetMemberBinder类型 用于获取动态创建赋值属性的时候“属性名”等一些常见信息(如示例中Name获取动态赋值的那个属性)。 value:object类型 用于获取设置动态属性的那个值。 如果需要支持动态创建读属性,必须覆盖TryGetMember,其参数作用和TrySetMember大致相当,只是反作用(用于获取 某个已有属性的内容,并且反向传递给object作为输出结果,注意TryGetMember的value是一个out类型)。同时,这个函数多出一个 result类型,用于返回已有属性的存储的值(NULL抛出异常,被认为是错误的)。
如果需要动态调用函数并输出结果,必须覆盖TryInvokeMember方法,此函数比较复杂:
参数名称 作用说明 binder:InvokeMemberBinder类型 用于获取动态创建函数时候一些与函数相关的属性:
(比如Name是函数名,其中还有一个CallInfo内嵌属性,您还可以获得ArgumentNames(C#4.0中最新的可选参数的名称,通过其ArgumentNameCount获取可选参数名的总个数))。 Args:object[]类型 获取动态给函数赋的值。 result:object类型 返回动态函数执行的结果,Null表示异常。 根据以上表格,对照不难读懂我的示例代码——现在假设你是这样调用的:
1)
dynamic d = new SimpleDynamic();
d.Name = “Serviceboy”;
Console.WriteLine(d.Name);
首先创建了一个d的动态类型,然后当赋值给Name的时候,因为Name是属性,所以触发了“TrySetMember”函数,该函数自动检查是否 已经存在这个属性名,如果不存在,则将其添加进入一个Dictionary中并将对应赋予的值传递进去保存起来。当使用输出的时候,同样 地,TryGetMember被触发,系统检测是否预先创建过这个值,如果没有,则抛出异常;存在的话,取出对应的存储value并返回给系统。
2)
dynamic d = new SimpleDynamic();
Console.WriteLine(d.Say(“Hello!”));
首先创建了一个d的动态类型,当动态创建一个方法的时候,系统检测是否包含这个方法名,不包含将添加这个方法名到Dictionary保存,接着检查参数 是否为空,不为空把参数赋值给那个函数名作为Key的Dictionary中保存,最后使用StringBuilder串起来赋值给result作为输 出。
下面给出一个比较复杂的例子——自定义的XML创建器(仿Jeffery Zhao):
public class XmlCreator : DynamicObject
{
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
//如果除了第一个节点是字符串,后面都是XElement对象,表明此节点是父节点
if (args[1] is XElement)
{
XElement root = new XElement(args[0].ToString());
//把子节点添加到父节点
for (int i = 1; i < args.Length; ++i)
{
root.Add(args[i]);
}
result = root;
}
//否则是子节点
else
{
//拷贝所有属性到数组:
string[] attributes = new string[binder.CallInfo.ArgumentNames.Count];
for (int i = 0; i < binder.CallInfo.ArgumentNames.Count; i++)
{
attributes[i] = binder.CallInfo.ArgumentNames[i];
}
//拷贝所有属性值到数组:
string[] values = new string[args.Length - 1];
for (int i = 1; i < args.Length; ++i)
{
values[i - 1] = args[i].ToString();
}
XElement subelement = new XElement(args[0].ToString());
//属性名称获取
for (int i = 0; i < attributes.Length; ++i)
{
subelement.SetAttributeValue(attributes[i], values[i]);
}
result = subelement;
}
return result != null;
}
}
该函数功能是:输出任意同时带有属性的节点,同时可以嵌套——比如:
dynamic xmlCreator = new XmlCreator();
XElement ele = xmlCreator.CreateElement(“Books”,
xmlCreator(“Book”,name:C#,price:100.50) ); 大家可以自己想一想是怎么一个原理哦。
Tags: C#
javaeclipse中的重构梦晴[2011年11月15日]
作者: 日期:2012-05-10
java eclipse中的重构
eclipse重构详解 博客分类: eclipse java
eclipse refactoring 重构是对软件内部结构的一种调整,目的是在不改变软件行为的前提下,提高其可理解性,降低其修改成本。开发人员可以使用一系列重构准则,在不改变软件行为的前提下,调整软件的结构。
有很多种原因,开发人员应该重构代码,例如之前的开发人员代码写得很烂、自己以前设计时有缺陷、需求变更需要添加一些新的功能或修改原有功能等等。Martin Fowler在其著名的<<Refactoring—Improving the Design of Existing Code>>一书中谈到了为何重构的几点原因:
1. 重构可以改进软件设计
如果不进行重构,程序的设计会变得越来越糟糕。通常程序员只为短期的目的,或者在没有完全理解整体设计的时候,就开始修改代码,这样程序将会逐渐失去自己的结构,程序员也愈来愈难通过阅读源码理解原本设计,相信对此每一个开发人员都深有体会。
代码结构的流失是累积性的,愈难看出代码所代表的意思,就越难保护其中的设计,于是设计也将变得越来越糟糕,经常性重构可以帮助维持设计该有的形态。
2. 重构使软件更易被理解
很多开发人员认为代码只要能够运行起来就可以了,笔者刚开始做开发的时候也是这么认为的,也写过很多的垃圾代码,也因此吃了不少苦头。
也许有些人可能会认为自己可能不久就会离开所在的职位,不必在意代码的质量,但作为一个开发人员来说,写出漂亮的代码是最基本的素质。
在软件的不断修改过程中,代码的可读性越来越差也是会慢慢累积的,但这不要紧,只要记得持续重构,就能使自己的代码更容易被理解。
3. 重构可以协助找到Bugs
对代码的理解,可以更容易找到bug,在重构的同时,也能够更好的理解代码及其行为,从而通过重构能够帮助开发人员写出更强壮的代码。
4. 重构可以提高编程的速度
良好的设计是快速软件开发的根本,如果没有良好的设计,也许开始的一段时间开发人员的进展迅速,但是恶劣的设计很快就会使开发速度慢下来。也许把时间花在调试上的时间会越来越多,修改的时间会越来越长,而且这会是一个恶性的循环。
良好的设计是维持软件开发速度的根本,重构可以帮助开发人员更快速地开发软件,因为它能够阻止系统的设计变质,能够提高代码的可读性。
使用Eclipse进行代码重构
重构是软件开发过程中保证代码质量非常重要的手段,而手动进行重构代码的话,很容易引入一些低级错误(例如,单词拼写错误),从而导致浪费大量不必要的时间。Eclipse为重构提供了很强大的支持,很大程度上用户不必为重构的笔误而再烦恼。
在Eclipse中,可以使用JDT提供的重构功能对Java项目、类和其成员进行重构,所有这些被重构的部分都可以看成一个JDT能识别的Java元素。要执行重构,首先必须选择相应重构的Java元素,一些重构是适合任何Java元素的,而一部分重构只适合特定的Java元素,几乎所有的重构都能够在重构对话框中看到预览的效果。
要使用Eclipse的重构功能,可以先选择相应的Java元素(Java工程中的资源,包括工程、文件、方法、变量等),通过右键菜单选择Refactor菜单下的重构功能,如图1所示。
图1 选择重构菜单
在Eclipse中,可以简单的把重构分为结构性重构、类级别重构和类内部重构,每种类型的重构又分别包含了一些具体的实现,接下来将分别介绍Eclipse如何对Java元素进行重构。
提示:在JDT可识别的范围内,可以认为工程中资源都是Java元素,包括Java文件名、类、方法、变量等。
结构性重构
结构性重构涉及到JAVA元素的物理结构的改变,包括“Rename”、“Move”、“Change Method Signature”、“Convert Anonymous Class to Nested”和“Move Member Type to New File”,下面将一一介绍这些重构在Eclipse中的实现。
1. Rename
Rename重构的功能就是重命名Java元素。虽然可以通过手动修改文件的文件名或其它Java元素的名称,但这种方式不会更新与此Java元素相关联的引用,用户必须手动查找和此Java元素相关的位置,然后进行手动修改。通过手动修改名称的方式,造成笔误的可能性会太太增加。通过Eclipse提供的Rename的功能,Eclipse会自动完成更新相关引用的操作。
当Java元素的命名不清晰或功能发生改变的时,为了保持代码的可读性,可以通过Eclipse的重构功能重命名Java元素。选择相应的Java元素,选择右键Refactor菜单下的Rename菜单,可以对当前选择的元素进行重命名,在弹出的重命名对话框中修改相应的元素名称即可,例如修改一个包的重命名,如图2所示。
图2 Rename对话框
要修改包名的同时,可以选择是否更新引用和更新子目录,甚至是非Java文件也可以选择性的更新。选择Preview按钮可以预览重命名重构后的效果,如图3所示。
图3 预览重命名包名
可以查看预览的内容是否一致,确认是否要进行重命名的重构。可以进行重命名的Java元素有Java项目、Java文件、包、方法和属性字段等。
提示:非Java项目和Java文件等也可以通过重构菜单的Rename进行重命名。
2. Move
Move的重构和Rename的重构类似,它可以把一个Java元素从一个地方移动到另一个地方,Move的重构主要用来移动一个类到不同的包下。首先选中一个Java文件,选择Refactor菜单下的Move菜单项,弹出Move的重构对话框,如图4所示。
图4 Move对话框
可以选择是否更新引用,设定移动文件重构的一些参数。
提示:也可以通过拖动的方式把一个文件从一个包移动到另一个包,实现移动文件的重构。
3. Change Method Signature
“Change Method Signature”重构的功能是改变方法的定义,例如改变方法的参数名称、类型和个数、返回值的类型,方法的可见性以及方法的名称等。
要改变方法的定义,可以先选择方法,通过右键菜单选择Refactor菜单的“Change Method Signature”子菜单项,弹出“Change Method Signature”对话框,如图5所示。
图5 “Change Method Signature”对话框
可以通过“Change Method Signature”对话框改变方法的参数名称、类型和个数、返回值的类型,方法的可见性以及方法名称等。
4. Convert Anonymous Class to Nested
“Convert Anonymous Class to Nested”重构的功能是把匿名类改成内部类,这样同一个类的其它部分也可以共享此类了。
例如有例程1所示的类。
例程1 KeyListenerExample.java
public class KeyListenerExample { Display display; Shell shell; KeyListenerExample() { display = new Display(); shell = new Shell(display); shell.setSize(250, 200); shell.setText("A KeyListener Example"); Text text = new Text(shell, SWT.BORDER); text.setBounds(50, 50, 100, 20); text.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { System.out.println("key Pressed -" + e.character); } public void keyReleased(KeyEvent e) { System.out.println("key Released -" + e.character); } }); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } public static void main(String[] args) { new KeyListenerExample(); } }
在KeyListenerExample类有一个匿名类,实现了KeyListener接口,可以把这个匿名类改成内部类,首先选择匿名类,右键选择Refactor的“Convert Anonymous Class to Nested”菜单,输入内部类的名称,如图6所示。
图6 “Convert Anonymous Class to Nested”对话框
重构后的结果是Eclipse为此创建了一个内部类,名称为TestKeyListener,重构后的代码如例程2所示。
例程2 重构后的KeyListenerExample.java
public class KeyListenerExample { private final class TestKeyListener implements KeyListener { public void keyPressed(KeyEvent e) { System.out.println("key Pressed -" + e.character); } public void keyReleased(KeyEvent e) { System.out.println("key Released -" + e.character); } } Display display; Shell shell; KeyListenerExample() { display = new Display(); shell = new Shell(display); shell.setSize(250, 200); shell.setText("A KeyListener Example"); Text text = new Text(shell, SWT.BORDER); text.setBounds(50, 50, 100, 20); text.addKeyListener(new TestKeyListener()); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } public static void main(String[] args) { new KeyListenerExample(); } }
也可以通过“Convert Anonymous Class to Nested”对话框定义新生成的内部类的可访问性。
5. Move Member Type to Top Level
通过“Move Member Type to Top Level”的重构方式,可以把内部类改成非内部类,并且重新创建一个新的文件,这样其它的类就可以共享此类。
例程2创建了一个内部类TestKeyListener,现在可以通过“Move Member Type to Top Level”重构的方式,把TestKeyListener放入一个单独的类中。首先选择TestKeyListener类,从右键菜单Refactor中选择“Move Member Type to Top Level”,打开“Move Member Type to Top Level”对话框,如图7所示。
图7 “Move Member Type to Top Level”对话框
通过上面“Move Member Type to Top Level”重构,可以把内部类改成非内部类。
提示:有些时候,重构并不是一步完成的,可以一步一步重构,例如,首先把匿名类改成内部类,再接着把内部类改成非内部类。
类级别重构
类级别重构有如下一些:
1. Push Down
“Push Down”重构功能是把父类的方法和属性移动到所有的子类中,父类的方法可以选择性的保留抽象方法。首先选择父类,右键选择Refactor菜单的“Push Down”菜单项,可以通过“Push Down”对话框选择重构,如图8所示。
图8 “Push Down”对话框
“Push Down”重构在重新设计类的时候是非常有用的,它可以比较有较的改善类的继承关系,清楚定义类的行为。
2. Pull Up
“Pull Up”重构和“Push Down”重构正好相反,它的作用是把方法和属性移动到其父类中去。选择需要重构的子类,从右键菜单选择Refactor菜单的“Pull up”菜单项,通过“Pull Up”对话框进行重构,如图9所示。
图9 “Pull Up”对话框
提示:“Pull Up”重构和“Push Down”重构后可能会出错,在使用此重构的同时,应该先弄清楚某些方法中是否有引用到其它方法或属性。
3. Extract Interface
“Extract Interface”重构能够从一个已存在的类中提取接口,它可以从某个类中选择方法,把这些方法提取到一个单独的接口中。选择提取接口的类,右键选择Refactor菜单的“Extract Interface”菜单项,打开“Extract Interface”对话框,如图10所示。
图10 “Extract Interface”对话框
单元OK按钮,将会提取TestInterface的接口,提取接口后,当前选择的类将会实现此接口。
提示:只有公用方法才可以被提取为接口的方法。
4. Generalize Declared Type
“Generalize Declared Type”重构能够改变变量、参数、属性以及函数的返回值的类型,可以把这些类型改成其父类的类型。在Refactor菜单中选择“Generalize Declared Type”,如图11所示。
图11 “Generalize Declared Type”对话框
单击OK按钮,能够把声明的类型改成当对话框中选择的类型。
5. User Supertype Where Possible
“User Supertype Where Possible”重构能够用某一个类的父类的类型替换当前类的类型,选择需要被替换引用的类。在Refactor菜单中选择“User Supertype Where Possible”打开“User Supertype Where Possible”对话框,如图12所示。
图12 “User Supertype Where Possible”对话框
“Generalize Declared Type”重构和“User Supertype Where Possible”重构在面向接口编程方面是很有用的,可以把引用的对象尽可能用接口进行实现。
提示:“User Supertype Where Possible”重构将替换其它类中的引用,要想看到重构的效果,应该找到其它类引用的位置,此操作不会修改当前文件。
类内部重构
类内部重构有如下一些:
1. Inline
“Inline”重构能用函数的内容替换掉函数的引用。首先选择函数的引用,在Refactor菜单中选择“Inline”打开“Inline”对话框,如图13所示。
图13 “Inline”对话框
单击确定按钮,Eclipse将会用方法实现的部分替换引用的部分,即当前不采用方法调用的方式进行操作。也可以选择“All invocations”和“Delete method declaration”,Eclipse会替换掉所有引用方法的位置,并且删除方法。
提示:Inline会用方法的实现部分替换所有调用方法的地方。
2. Extract Method
“Extract Method”重构和“Inline”重构相反,它能够从冗长的方法中提取小的方法,把大的方法分解成多个小方法来实现,通过此重构能够使代码看上去更简单漂亮,也很大程度上提高代码的复用性。可以选择要提取方法的代码,在Refactor菜单中选择“Extract Method”打开“Extract Method”对话框,如图14所示。
图14 “Extract Method”对话框
“Extract Method”重构是非常好的重构方式,能够把大的方法体重构成多个方法的实现,使代码更清楚易懂。
提示:“Extract Method”重构和“Inline”重构是对应的,有些时候为了组织一些不合的函数,可以先通过“Inline”的方式生成一个大的函数,再通过“Extract Method”来重构大的函数,使代码更趋于合理。
3. Extract Local Variable
在开发过程中,使用变量代替表达式是非常好的,这样能使代码更容易被理解。Eclipse中可以通过“Extract Local Variable”重构实现提取局部的表达式。首先选择表达式,在Refactor菜单中选择“Extract Local Variable”打开“Extract Local Variable”对话框,如图15所示。
图15 “Extract Local Variable”对话框
4. Extract Constant
“Extract Constant”重构和“Extract Local Variable”重构类似,它可以把表达式定义为常量,另外“Extract Constant”重构能够设定常量的可见性。选择表达式,在Refactor菜单中选择“Extract Constant”打开“Extract Constant”对话框,如图16所示。
图16 “Extract Constant”对话框
5. Introduce Parameter
“Introduce Parameter”重构可以通过函数中的表达式、变量或引用为函数添加新的参数,还能够自动更新引用此函数的其它位置的默认参数。要想进行“Introduce Parameter”重构,可以选择表达式、变量或引用。在Refactor菜单中选择“Introduce Parameter”打开“Introduce Parameter”对话框,如图17所示。
图17 “Introduce Parameter”对话框
6. Introduce Factory
“Introduce Factory”重构能够为类创建工厂方法。首先选择需要创建工厂方法的类的构造函数,在Refactor菜单中选择“Introduce Factory”打开“Introduce Factory”对话框,如图18所示。
图18 “Introduce Factory”对话框
在“Introduce Factory”对话框中,可以输入工厂方法的名字,以及工厂类,Eclipse将会自动根据构造函数创建工厂方法。
提示:工厂类应该已经存在,通常可以在一个工厂类中为多个关联的类创建工厂方法,所以在使用“Introduce Factory”重构前,应该先创建好工厂类。
7. Convert Local Variable to Field
“Convert Local Variable to Field”重构能够把局部的变量转换成类中的全局变量。首先选择要转换的局部变量,在Refactor菜单中选择“Convert Local Variable to Field”打开“Convert Local Variable to Field”对话框,如图19所示。
图19 “Convert Local Variable to Field”对话框
在“Convert Local Variable to Field”对话框中,还能够修改变量的名称以及变量的可见性。
8. Encapsulate Field
“Encapsulate Field”重构能够包装属性的可访问性,以及生成访问的方法。首先选择要包装的属性,在Refactor菜单中选择“Encapsulate Field”打开“Encapsulate Field”对话框,如图20所示。
图20 “Encapsulate Field”对话框
通常通过“Encapsulate Field”可以生成get和set方法。在“Encapsulate Field”对话框中可以输入属性的访问方法的名称,以及方法生成的位置和方法的可见性。
提示:通过右键菜单的Source菜单也能生成相应的get和set方法。
Undo and Redo
Eclipse的自动重构功能能够很好的支持各种程序元素的重命名,并自动更新相关的引用。Eclipse能够支持方法、字段在类之间移动,并自动更新引用,较好地支持内联字段、函数的更新替换,较好地支持抽取方法、变量等程序元素。
重构的过程是一个不断尝试和探索的过程。Eclipse的重构支持撤销和重做,并且能够预览重构结果,这些是很实用的功能。要想执行撤消和重做(Undo and Redo)的功能,可以直接按快捷键Ctrl+Z以及Ctrl+Y,也可以选择Edit菜单的Undo和Redo操作。
提示:虽然Eclipse对重构提供了很强大的支持,但是重构后代码的测试是必不可少的,而且不能指望Eclipse能够解决所有重构的问题,有些时候手动重构还是必须的。自动重构的理念应该是“工具辅助下的重构工作”,但开发人员仍然承担很大一部分重构工作。
posted on 2011-11-15 10:30 梦晴 阅读(9) 评论(0) 编辑 收藏
eclipse重构详解 博客分类: eclipse java
eclipse refactoring 重构是对软件内部结构的一种调整,目的是在不改变软件行为的前提下,提高其可理解性,降低其修改成本。开发人员可以使用一系列重构准则,在不改变软件行为的前提下,调整软件的结构。
有很多种原因,开发人员应该重构代码,例如之前的开发人员代码写得很烂、自己以前设计时有缺陷、需求变更需要添加一些新的功能或修改原有功能等等。Martin Fowler在其著名的<<Refactoring—Improving the Design of Existing Code>>一书中谈到了为何重构的几点原因:
1. 重构可以改进软件设计
如果不进行重构,程序的设计会变得越来越糟糕。通常程序员只为短期的目的,或者在没有完全理解整体设计的时候,就开始修改代码,这样程序将会逐渐失去自己的结构,程序员也愈来愈难通过阅读源码理解原本设计,相信对此每一个开发人员都深有体会。
代码结构的流失是累积性的,愈难看出代码所代表的意思,就越难保护其中的设计,于是设计也将变得越来越糟糕,经常性重构可以帮助维持设计该有的形态。
2. 重构使软件更易被理解
很多开发人员认为代码只要能够运行起来就可以了,笔者刚开始做开发的时候也是这么认为的,也写过很多的垃圾代码,也因此吃了不少苦头。
也许有些人可能会认为自己可能不久就会离开所在的职位,不必在意代码的质量,但作为一个开发人员来说,写出漂亮的代码是最基本的素质。
在软件的不断修改过程中,代码的可读性越来越差也是会慢慢累积的,但这不要紧,只要记得持续重构,就能使自己的代码更容易被理解。
3. 重构可以协助找到Bugs
对代码的理解,可以更容易找到bug,在重构的同时,也能够更好的理解代码及其行为,从而通过重构能够帮助开发人员写出更强壮的代码。
4. 重构可以提高编程的速度
良好的设计是快速软件开发的根本,如果没有良好的设计,也许开始的一段时间开发人员的进展迅速,但是恶劣的设计很快就会使开发速度慢下来。也许把时间花在调试上的时间会越来越多,修改的时间会越来越长,而且这会是一个恶性的循环。
良好的设计是维持软件开发速度的根本,重构可以帮助开发人员更快速地开发软件,因为它能够阻止系统的设计变质,能够提高代码的可读性。
使用Eclipse进行代码重构
重构是软件开发过程中保证代码质量非常重要的手段,而手动进行重构代码的话,很容易引入一些低级错误(例如,单词拼写错误),从而导致浪费大量不必要的时间。Eclipse为重构提供了很强大的支持,很大程度上用户不必为重构的笔误而再烦恼。
在Eclipse中,可以使用JDT提供的重构功能对Java项目、类和其成员进行重构,所有这些被重构的部分都可以看成一个JDT能识别的Java元素。要执行重构,首先必须选择相应重构的Java元素,一些重构是适合任何Java元素的,而一部分重构只适合特定的Java元素,几乎所有的重构都能够在重构对话框中看到预览的效果。
要使用Eclipse的重构功能,可以先选择相应的Java元素(Java工程中的资源,包括工程、文件、方法、变量等),通过右键菜单选择Refactor菜单下的重构功能,如图1所示。
图1 选择重构菜单
在Eclipse中,可以简单的把重构分为结构性重构、类级别重构和类内部重构,每种类型的重构又分别包含了一些具体的实现,接下来将分别介绍Eclipse如何对Java元素进行重构。
提示:在JDT可识别的范围内,可以认为工程中资源都是Java元素,包括Java文件名、类、方法、变量等。
结构性重构
结构性重构涉及到JAVA元素的物理结构的改变,包括“Rename”、“Move”、“Change Method Signature”、“Convert Anonymous Class to Nested”和“Move Member Type to New File”,下面将一一介绍这些重构在Eclipse中的实现。
1. Rename
Rename重构的功能就是重命名Java元素。虽然可以通过手动修改文件的文件名或其它Java元素的名称,但这种方式不会更新与此Java元素相关联的引用,用户必须手动查找和此Java元素相关的位置,然后进行手动修改。通过手动修改名称的方式,造成笔误的可能性会太太增加。通过Eclipse提供的Rename的功能,Eclipse会自动完成更新相关引用的操作。
当Java元素的命名不清晰或功能发生改变的时,为了保持代码的可读性,可以通过Eclipse的重构功能重命名Java元素。选择相应的Java元素,选择右键Refactor菜单下的Rename菜单,可以对当前选择的元素进行重命名,在弹出的重命名对话框中修改相应的元素名称即可,例如修改一个包的重命名,如图2所示。
图2 Rename对话框
要修改包名的同时,可以选择是否更新引用和更新子目录,甚至是非Java文件也可以选择性的更新。选择Preview按钮可以预览重命名重构后的效果,如图3所示。
图3 预览重命名包名
可以查看预览的内容是否一致,确认是否要进行重命名的重构。可以进行重命名的Java元素有Java项目、Java文件、包、方法和属性字段等。
提示:非Java项目和Java文件等也可以通过重构菜单的Rename进行重命名。
2. Move
Move的重构和Rename的重构类似,它可以把一个Java元素从一个地方移动到另一个地方,Move的重构主要用来移动一个类到不同的包下。首先选中一个Java文件,选择Refactor菜单下的Move菜单项,弹出Move的重构对话框,如图4所示。
图4 Move对话框
可以选择是否更新引用,设定移动文件重构的一些参数。
提示:也可以通过拖动的方式把一个文件从一个包移动到另一个包,实现移动文件的重构。
3. Change Method Signature
“Change Method Signature”重构的功能是改变方法的定义,例如改变方法的参数名称、类型和个数、返回值的类型,方法的可见性以及方法的名称等。
要改变方法的定义,可以先选择方法,通过右键菜单选择Refactor菜单的“Change Method Signature”子菜单项,弹出“Change Method Signature”对话框,如图5所示。
图5 “Change Method Signature”对话框
可以通过“Change Method Signature”对话框改变方法的参数名称、类型和个数、返回值的类型,方法的可见性以及方法名称等。
4. Convert Anonymous Class to Nested
“Convert Anonymous Class to Nested”重构的功能是把匿名类改成内部类,这样同一个类的其它部分也可以共享此类了。
例如有例程1所示的类。
例程1 KeyListenerExample.java
public class KeyListenerExample { Display display; Shell shell; KeyListenerExample() { display = new Display(); shell = new Shell(display); shell.setSize(250, 200); shell.setText("A KeyListener Example"); Text text = new Text(shell, SWT.BORDER); text.setBounds(50, 50, 100, 20); text.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { System.out.println("key Pressed -" + e.character); } public void keyReleased(KeyEvent e) { System.out.println("key Released -" + e.character); } }); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } public static void main(String[] args) { new KeyListenerExample(); } }
在KeyListenerExample类有一个匿名类,实现了KeyListener接口,可以把这个匿名类改成内部类,首先选择匿名类,右键选择Refactor的“Convert Anonymous Class to Nested”菜单,输入内部类的名称,如图6所示。
图6 “Convert Anonymous Class to Nested”对话框
重构后的结果是Eclipse为此创建了一个内部类,名称为TestKeyListener,重构后的代码如例程2所示。
例程2 重构后的KeyListenerExample.java
public class KeyListenerExample { private final class TestKeyListener implements KeyListener { public void keyPressed(KeyEvent e) { System.out.println("key Pressed -" + e.character); } public void keyReleased(KeyEvent e) { System.out.println("key Released -" + e.character); } } Display display; Shell shell; KeyListenerExample() { display = new Display(); shell = new Shell(display); shell.setSize(250, 200); shell.setText("A KeyListener Example"); Text text = new Text(shell, SWT.BORDER); text.setBounds(50, 50, 100, 20); text.addKeyListener(new TestKeyListener()); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } public static void main(String[] args) { new KeyListenerExample(); } }
也可以通过“Convert Anonymous Class to Nested”对话框定义新生成的内部类的可访问性。
5. Move Member Type to Top Level
通过“Move Member Type to Top Level”的重构方式,可以把内部类改成非内部类,并且重新创建一个新的文件,这样其它的类就可以共享此类。
例程2创建了一个内部类TestKeyListener,现在可以通过“Move Member Type to Top Level”重构的方式,把TestKeyListener放入一个单独的类中。首先选择TestKeyListener类,从右键菜单Refactor中选择“Move Member Type to Top Level”,打开“Move Member Type to Top Level”对话框,如图7所示。
图7 “Move Member Type to Top Level”对话框
通过上面“Move Member Type to Top Level”重构,可以把内部类改成非内部类。
提示:有些时候,重构并不是一步完成的,可以一步一步重构,例如,首先把匿名类改成内部类,再接着把内部类改成非内部类。
类级别重构
类级别重构有如下一些:
1. Push Down
“Push Down”重构功能是把父类的方法和属性移动到所有的子类中,父类的方法可以选择性的保留抽象方法。首先选择父类,右键选择Refactor菜单的“Push Down”菜单项,可以通过“Push Down”对话框选择重构,如图8所示。
图8 “Push Down”对话框
“Push Down”重构在重新设计类的时候是非常有用的,它可以比较有较的改善类的继承关系,清楚定义类的行为。
2. Pull Up
“Pull Up”重构和“Push Down”重构正好相反,它的作用是把方法和属性移动到其父类中去。选择需要重构的子类,从右键菜单选择Refactor菜单的“Pull up”菜单项,通过“Pull Up”对话框进行重构,如图9所示。
图9 “Pull Up”对话框
提示:“Pull Up”重构和“Push Down”重构后可能会出错,在使用此重构的同时,应该先弄清楚某些方法中是否有引用到其它方法或属性。
3. Extract Interface
“Extract Interface”重构能够从一个已存在的类中提取接口,它可以从某个类中选择方法,把这些方法提取到一个单独的接口中。选择提取接口的类,右键选择Refactor菜单的“Extract Interface”菜单项,打开“Extract Interface”对话框,如图10所示。
图10 “Extract Interface”对话框
单元OK按钮,将会提取TestInterface的接口,提取接口后,当前选择的类将会实现此接口。
提示:只有公用方法才可以被提取为接口的方法。
4. Generalize Declared Type
“Generalize Declared Type”重构能够改变变量、参数、属性以及函数的返回值的类型,可以把这些类型改成其父类的类型。在Refactor菜单中选择“Generalize Declared Type”,如图11所示。
图11 “Generalize Declared Type”对话框
单击OK按钮,能够把声明的类型改成当对话框中选择的类型。
5. User Supertype Where Possible
“User Supertype Where Possible”重构能够用某一个类的父类的类型替换当前类的类型,选择需要被替换引用的类。在Refactor菜单中选择“User Supertype Where Possible”打开“User Supertype Where Possible”对话框,如图12所示。
图12 “User Supertype Where Possible”对话框
“Generalize Declared Type”重构和“User Supertype Where Possible”重构在面向接口编程方面是很有用的,可以把引用的对象尽可能用接口进行实现。
提示:“User Supertype Where Possible”重构将替换其它类中的引用,要想看到重构的效果,应该找到其它类引用的位置,此操作不会修改当前文件。
类内部重构
类内部重构有如下一些:
1. Inline
“Inline”重构能用函数的内容替换掉函数的引用。首先选择函数的引用,在Refactor菜单中选择“Inline”打开“Inline”对话框,如图13所示。
图13 “Inline”对话框
单击确定按钮,Eclipse将会用方法实现的部分替换引用的部分,即当前不采用方法调用的方式进行操作。也可以选择“All invocations”和“Delete method declaration”,Eclipse会替换掉所有引用方法的位置,并且删除方法。
提示:Inline会用方法的实现部分替换所有调用方法的地方。
2. Extract Method
“Extract Method”重构和“Inline”重构相反,它能够从冗长的方法中提取小的方法,把大的方法分解成多个小方法来实现,通过此重构能够使代码看上去更简单漂亮,也很大程度上提高代码的复用性。可以选择要提取方法的代码,在Refactor菜单中选择“Extract Method”打开“Extract Method”对话框,如图14所示。
图14 “Extract Method”对话框
“Extract Method”重构是非常好的重构方式,能够把大的方法体重构成多个方法的实现,使代码更清楚易懂。
提示:“Extract Method”重构和“Inline”重构是对应的,有些时候为了组织一些不合的函数,可以先通过“Inline”的方式生成一个大的函数,再通过“Extract Method”来重构大的函数,使代码更趋于合理。
3. Extract Local Variable
在开发过程中,使用变量代替表达式是非常好的,这样能使代码更容易被理解。Eclipse中可以通过“Extract Local Variable”重构实现提取局部的表达式。首先选择表达式,在Refactor菜单中选择“Extract Local Variable”打开“Extract Local Variable”对话框,如图15所示。
图15 “Extract Local Variable”对话框
4. Extract Constant
“Extract Constant”重构和“Extract Local Variable”重构类似,它可以把表达式定义为常量,另外“Extract Constant”重构能够设定常量的可见性。选择表达式,在Refactor菜单中选择“Extract Constant”打开“Extract Constant”对话框,如图16所示。
图16 “Extract Constant”对话框
5. Introduce Parameter
“Introduce Parameter”重构可以通过函数中的表达式、变量或引用为函数添加新的参数,还能够自动更新引用此函数的其它位置的默认参数。要想进行“Introduce Parameter”重构,可以选择表达式、变量或引用。在Refactor菜单中选择“Introduce Parameter”打开“Introduce Parameter”对话框,如图17所示。
图17 “Introduce Parameter”对话框
6. Introduce Factory
“Introduce Factory”重构能够为类创建工厂方法。首先选择需要创建工厂方法的类的构造函数,在Refactor菜单中选择“Introduce Factory”打开“Introduce Factory”对话框,如图18所示。
图18 “Introduce Factory”对话框
在“Introduce Factory”对话框中,可以输入工厂方法的名字,以及工厂类,Eclipse将会自动根据构造函数创建工厂方法。
提示:工厂类应该已经存在,通常可以在一个工厂类中为多个关联的类创建工厂方法,所以在使用“Introduce Factory”重构前,应该先创建好工厂类。
7. Convert Local Variable to Field
“Convert Local Variable to Field”重构能够把局部的变量转换成类中的全局变量。首先选择要转换的局部变量,在Refactor菜单中选择“Convert Local Variable to Field”打开“Convert Local Variable to Field”对话框,如图19所示。
图19 “Convert Local Variable to Field”对话框
在“Convert Local Variable to Field”对话框中,还能够修改变量的名称以及变量的可见性。
8. Encapsulate Field
“Encapsulate Field”重构能够包装属性的可访问性,以及生成访问的方法。首先选择要包装的属性,在Refactor菜单中选择“Encapsulate Field”打开“Encapsulate Field”对话框,如图20所示。
图20 “Encapsulate Field”对话框
通常通过“Encapsulate Field”可以生成get和set方法。在“Encapsulate Field”对话框中可以输入属性的访问方法的名称,以及方法生成的位置和方法的可见性。
提示:通过右键菜单的Source菜单也能生成相应的get和set方法。
Undo and Redo
Eclipse的自动重构功能能够很好的支持各种程序元素的重命名,并自动更新相关的引用。Eclipse能够支持方法、字段在类之间移动,并自动更新引用,较好地支持内联字段、函数的更新替换,较好地支持抽取方法、变量等程序元素。
重构的过程是一个不断尝试和探索的过程。Eclipse的重构支持撤销和重做,并且能够预览重构结果,这些是很实用的功能。要想执行撤消和重做(Undo and Redo)的功能,可以直接按快捷键Ctrl+Z以及Ctrl+Y,也可以选择Edit菜单的Undo和Redo操作。
提示:虽然Eclipse对重构提供了很强大的支持,但是重构后代码的测试是必不可少的,而且不能指望Eclipse能够解决所有重构的问题,有些时候手动重构还是必须的。自动重构的理念应该是“工具辅助下的重构工作”,但开发人员仍然承担很大一部分重构工作。
posted on 2011-11-15 10:30 梦晴 阅读(9) 评论(0) 编辑 收藏
Tags: java
C#对文件的操作[2011年02月15日]
作者: 日期:2012-05-10
判断文件夹是否为空
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(@"d:\a");
if (di.GetFiles().Length + di.GetDirectories().Length == 0)
{
//目录为空
}
StreamWriter sw = File.AppendText(Server.MapPath(".")+"\\myText.txt");
sw.WriteLine("追逐理想");
sw.WriteLine("kzlll");
sw.WriteLine(".NET笔记");
sw.Flush();
sw.Close();
C#拷贝文件
string orignFile,NewFile;
OrignFile = Server.MapPath(".")+"\\myText.txt";
NewFile = Server.MapPath(".")+"\\myTextCopy.txt";
File.Copy(OrignFile,NewFile,true);
C#删除文件
string delFile = Server.MapPath(".")+"\\myTextCopy.txt";
File.Delete(delFile);
C#移动文件
string orignFile,NewFile;
OrignFile = Server.MapPath(".")+"\\myText.txt";
NewFile = Server.MapPath(".")+"\\myTextCopy.txt";
File.Move(OrignFile,NewFile);
C#创建目录
// 创建目录c:\sixAge
DirectoryInfo d=Directory.CreateDirectory("c:\\sixAge");
// d1指向c:\sixAge\sixAge1
DirectoryInfo d1=d.CreateSubdirectory("sixAge1");
// d2指向c:\sixAge\sixAge1\sixAge1_1
DirectoryInfo d2=d1.CreateSubdirectory("sixAge1_1");
// 将当前目录设为c:\sixAge
Directory.SetCurrentDirectory("c:\\sixAge");
// 创建目录c:\sixAge\sixAge2
Directory.CreateDirectory("sixAge2");
// 创建目录c:\sixAge\sixAge2\sixAge2_1
Directory.CreateDirectory("sixAge2\\sixAge2_1");
递归删除文件夹及文件
<%@ Page Language=C#%>
<%@ Import namespace="System.IO"%>
<Script runat=server>
public void DeleteFolder(string dir)
{
if (Directory.Exists(dir)) //如果存在这个文件夹删除之
{
foreach(string d in Directory.GetFileSystemEntries(dir))
{
if(File.Exists(d))
File.Delete(d); //直接删除其中的文件
else
DeleteFolder(d); //递归删除子文件夹
}
Directory.Delete(dir); //删除已空文件夹
Response.Write(dir+" 文件夹删除成功");
}
else
Response.Write(dir+" 该文件夹不存在"); //如果文件夹不存在则提示
}
protected void Page_Load (Object sender ,EventArgs e)
{
string Dir="D:\\gbook\\11";
DeleteFolder(Dir); //调用函数删除文件夹
}
// ================================================== ====
// 实现一个静态方法将指定文件夹下面的所有内容copy到目标文件夹下面
// 如果目标文件夹为只读属性就会报错。
// April 18April2005 In STU
// ================================================== ====
public static void CopyDir(string srcPath,string aimPath)
{
try
{
// 检查目标目录是否以目录分割字符结束如果不是则添加之
if(aimPath[aimPath.Length-1] != Path.DirectorySeparatorChar)
aimPath += Path.DirectorySeparatorChar;
// 判断目标目录是否存在如果不存在则新建之
if(!Directory.Exists(aimPath)) Directory.CreateDirectory(aimPath);
// 得到源目录的文件列表,该里面是包含文件以及目录路径的一个数组
// 如果你指向copy目标文件下面的文件而不包含目录请使用下面的方法
// string[] fileList = Directory.GetFiles(srcPath);
string[] fileList = Directory.GetFileSystemEntries(srcPath);
// 遍历所有的文件和目录
foreach(string file in fileList)
{
// 先当作目录处理如果存在这个目录就递归Copy该目录下面的文件
if(Directory.Exists(file))
CopyDir(file,aimPath+Path.GetFileName(file));
// 否则直接Copy文件
else
File.Copy(file,aimPath+Path.GetFileName(file),true );
}
}
catch (Exception e)
{
MessageBox.Show (e.ToString());
}
}
// ================================================== ====
// 实现一个静态方法将指定文件夹下面的所有内容Detele
// 测试的时候要小心操作,删除之后无法恢复。
// April 18April2005 In STU
// ================================================== ====
public static void DeleteDir(string aimPath)
{
try
{
// 检查目标目录是否以目录分割字符结束如果不是则添加之
if(aimPath[aimPath.Length-1] != Path.DirectorySeparatorChar)
aimPath += Path.DirectorySeparatorChar;
// 得到源目录的文件列表,该里面是包含文件以及目录路径的一个数组
// 如果你指向Delete目标文件下面的文件而不包含目录请使用下面的方法
// string[] fileList = Directory.GetFiles(aimPath);
string[] fileList = Directory.GetFileSystemEntries(aimPath);
// 遍历所有的文件和目录
foreach(string file in fileList)
{
// 先当作目录处理如果存在这个目录就递归Delete该目录下面的文件
if(Directory.Exists(file))
{
DeleteDir(aimPath+Path.GetFileName(file));
}
// 否则直接Delete文件
else
{
File.Delete (aimPath+Path.GetFileName(file));
}
}
//删除文件夹
System.IO .Directory .Delete (aimPath,true);
}
catch (Exception e)
{
MessageBox.Show (e.ToString());
}
}
需要引用命名空间:
using System.IO;
/**//// <summary>
/// 拷贝文件夹(包括子文件夹)到指定文件夹下,源文件夹和目标文件夹均需绝对路径. 格式: CopyFolder(源文件夹,目标文件夹);
/// </summary>
/// <param name="strFromPath"></param>
/// <param name="strToPath"></param>
//------------------------------------------------ --
//作者:明天去要饭 QQ:305725744
//------------------------------------------------ ---
public static void CopyFolder(string strFromPath,string strToPath)
{
//如果源文件夹不存在,则创建
if (!Directory.Exists(strFromPath))
{
Directory.CreateDirectory(strFromPath);
}
//取得要拷贝的文件夹名
string strFolderName = strFromPath.Substring(strFromPath.LastIndexOf("\\" ) + 1,strFromPath.Length - strFromPath.LastIndexOf("\\") - 1);
//如果目标文件夹中没有源文件夹则在目标文件夹中创建源文件夹
if (!Directory.Exists(strToPath + "\\" + strFolderName))
{
Directory.CreateDirectory(strToPath + "\\" + strFolderName);
}
//创建数组保存源文件夹下的文件名
string[] strFiles = Directory.GetFiles(strFromPath);
//循环拷贝文件
for(int i = 0;i < strFiles.Length;i++)
{
//取得拷贝的文件名,只取文件名,地址截掉。
string strFileName = strFiles[i].Substring(strFiles[i].LastIndexOf("\\" ) + 1,strFiles[i].Length - strFiles[i].LastIndexOf("\\") - 1);
//开始拷贝文件,true表示覆盖同名文件
File.Copy(strFiles[i],strToPath + "\\" + strFolderName + "\\" + strFileName,true);
}
//创建DirectoryInfo实例
DirectoryInfo dirInfo = new DirectoryInfo(strFromPath);
//取得源文件夹下的所有子文件夹名称
DirectoryInfo[] ZiPath = dirInfo.GetDirectories();
for (int j = 0;j < ZiPath.Length;j++)
{
//获取所有子文件夹名
string strZiPath = strFromPath + "\\" + ZiPath[j].ToString();
//把得到的子文件夹当成新的源文件夹,从头开始新一轮的拷贝
CopyFolder(strZiPath,strToPath + "\\" + strFolderName);
}
}
一.读取文本文件
1/**//// <summary>
2/// 读取文本文件
3/// </summary>
4private void ReadFromTxtFile()
5{
6 if(filePath.PostedFile.FileName != "")
7 {
8 txtFilePath =filePath.PostedFile.FileName;
9 fileExtName = txtFilePath.Substring(txtFilePath.LastIndexOf(".") +1,3);
10
11 if(fileExtName !="txt" && fileExtName != "TXT")
12 {
13 Response.Write("请选择文本文件");
14 }
15 else
16 {
17 StreamReader fileStream = new StreamReader(txtFilePath,Encoding.Default);
18 txtContent.Text = fileStream.ReadToEnd();
19 fileStream.Close();
20 }
21 }
22 }
二.获取文件列表
1/**//// <summary>
2/// 获取文件列表
3/// </summary>
4private void GetFileList()
5{
6 string strCurDir,FileName,FileExt;
7
8 /**////文件大小
9 long FileSize;
10
11 /**////最后修改时间;
12 DateTime FileModify;
13
14 /**////初始化
15 if(!IsPostBack)
16 {
17 /**////初始化时,默认为当前页面所在的目录
18 strCurDir = Server.MapPath(".");
19 lblCurDir.Text = strCurDir;
20 txtCurDir.Text = strCurDir;
21 }
22 else
23 {
24 strCurDir = txtCurDir.Text;
25 txtCurDir.Text = strCurDir;
26 lblCurDir.Text = strCurDir;
27 }
28 FileInfo fi;
29 DirectoryInfo dir;
30 TableCell td;
31 TableRow tr;
32 tr = new TableRow();
33
34 /**////动态添加单元格内容
35 td = new TableCell();
36 td.Controls.Add(new LiteralControl("文件名"));
37 tr.Cells.Add(td);
38 td = new TableCell();
39 td.Controls.Add(new LiteralControl("文件类型"));
40 tr.Cells.Add(td);
41 td = new TableCell();
42 td.Controls.Add(new LiteralControl("文件大小"));
43 tr.Cells.Add(td);
44 td = new TableCell();
45 td.Controls.Add(new LiteralControl("最后修改时间"));
46 tr.Cells.Add(td);
47
48 tableDirInfo.Rows.Add(tr);
49
50 /**////针对当前目录建立目录引用对象
51 DirectoryInfo dirInfo = new DirectoryInfo(txtCurDir.Text);
52
53 /**////循环判断当前目录下的文件和目录
54 foreach(FileSystemInfo fsi in dirInfo.GetFileSystemInfos())
55 {
56 FileName = "";
57 FileExt = "";
58 FileSize = 0;
59
60 /**////如果是文件
61 if(fsi is FileInfo)
62 {
63 fi = (FileInfo)fsi;
64
65 /**////取得文件名
66 FileName = fi.Name;
67
68 /**////取得文件的扩展名
69 FileExt = fi.Extension;
70
71 /**////取得文件的大小
72 FileSize = fi.Length;
73
74 /**////取得文件的最后修改时间
75 FileModify = fi.LastWriteTime;
76 }
77
78 /**////否则是目录
79 else
80 {
81 dir = (DirectoryInfo)fsi;
82
83 /**////取得目录名
84 FileName = dir.Name;
85
86 /**////取得目录的最后修改时间
87 FileModify = dir.LastWriteTime;
88
89 /**////设置文件的扩展名为"文件夹"
90 FileExt = "文件夹";
91 }
92
93 /**////动态添加表格内容
94 tr = new TableRow();
95 td = new TableCell();
96 td.Controls.Add(new LiteralControl(FileName));
97 tr.Cells.Add(td);
98 td = new TableCell();
99 td.Controls.Add(new LiteralControl(FileExt));
100 tr.Cells.Add(td);
101 td = new TableCell();
102 td.Controls.Add(new LiteralControl(FileSize.ToString()+"字节"));
103 tr.Cells.Add(td);
104 td = new TableCell();
105 td.Controls.Add(new LiteralControl(FileModify.ToString("yyyy-mm-dd hh:mm:ss")));
106 tr.Cells.Add(td);
107 tableDirInfo.Rows.Add(tr);
108 }
109}
三.读取日志文件
1/**//// <summary>
2/// 读取日志文件
3/// </summary>
4private void ReadLogFile()
5{
6 /**////从指定的目录以打开或者创建的形式读取日志文件
7 FileStream fs = new FileStream(Server.MapPath("upedFile")+"\\logfile.txt", FileMode.OpenOrCreate, FileAccess.Read);
8
9 /**////定义输出字符串
10 StringBuilder output = new StringBuilder();
11
12 /**////初始化该字符串的长度为0
13 output.Length = 0;
14
15 /**////为上面创建的文件流创建读取数据流
16 StreamReader read = new StreamReader(fs);
17
18 /**////设置当前流的起始位置为文件流的起始点
19 read.BaseStream.Seek(0, SeekOrigin.Begin);
20
21 /**////读取文件
22 while (read.Peek() > -1)
23 {
24 /**////取文件的一行内容并换行
25 output.Append(read.ReadLine() + "\n");
26 }
27
28 /**////关闭释放读数据流
29 read.Close();
30
31 /**////返回读到的日志文件内容
32 return output.ToString();
33}
四.写入日志文件
1/**//// <summary>
2/// 写入日志文件
3/// </summary>
4/// <param name="input"></param>
5private void WriteLogFile(string input)
6{
7 /**////指定日志文件的目录
8 string fname = Server.MapPath("upedFile") + "\\logfile.txt";
9 /**////定义文件信息对象
10 FileInfo finfo = new FileInfo(fname);
11
12 /**////判断文件是否存在以及是否大于2K
13 if ( finfo.Exists && finfo.Length > 2048 )
14 {
15 /**////删除该文件
16 finfo.Delete();
17 }
18 /**////创建只写文件流
19 using(FileStream fs = finfo.OpenWrite())
20 {
21 /**////根据上面创建的文件流创建写数据流
22 StreamWriter w = new StreamWriter(fs);
23
24 /**////设置写数据流的起始位置为文件流的末尾
25 w.BaseStream.Seek(0, SeekOrigin.End);
26
27 /**////写入“Log Entry : ”
28 w.Write("\nLog Entry : ");
29
30 /**////写入当前系统时间并换行
31 w.Write("{0} {1} \r\n", DateTime.Now.ToLongTimeString(),
32 DateTime.Now.ToLongDateString());
33
34 /**////写入日志内容并换行
35 w.Write(input + "\n");
36
37 /**////写入------------------------------------“并换行
38 w.Write("------------------------------------\n");
39
40 /**////清空缓冲区内容,并把缓冲区内容写入基础流
41 w.Flush();
42
43 /**////关闭写数据流
44 w.Close();
45 }
46}
五.创建HTML文件
1/**//// <summary>
2/// 创建HTML文件
3/// </summary>
4private void CreateHtmlFile()
5{
6 /**////定义和html标记数目一致的数组
7 string[] newContent = new string[5];
8 StringBuilder strhtml = new StringBuilder();
9 try
10 {
11 /**////创建StreamReader对象
12 using (StreamReader sr = new StreamReader(Server.MapPath("createHTML") + "\\template.html"))
13 {
14 String oneline;
15
16 /**////读取指定的HTML文件模板
17 while ((oneline = sr.ReadLine()) != null)
18 {
19 strhtml.Append(oneline);
20 }
21 sr.Close();
22 }
23 }
24 catch(Exception err)
25 {
26 /**////输出异常信息
27 Response.Write(err.ToString());
28 }
29 /**////为标记数组赋值
30 newContent[0] = txtTitle.Text;//标题
31 newContent[1] = "BackColor='#cccfff'";//背景色
32 newContent[2] = "#ff0000";//字体颜色
33 newContent[3] = "100px";//字体大小
34 newContent[4] = txtContent.Text;//主要内容
35
36 /**////根据上面新的内容生成html文件
37 try
38 {
39 /**////指定要生成的HTML文件
40 string fname = Server.MapPath("createHTML") +"\\" + DateTime.Now.ToString("yyyymmddhhmmss") + ".html";
41
42 /**////替换html模版文件里的标记为新的内容
43 for(int i=0;i < 5;i++)
44 {
45 strhtml.Replace("$htmlkey["+i+"]",newContent[i]);
46 }
47 /**////创建文件信息对象
48 FileInfo finfo = new FileInfo(fname);
49
50 /**////以打开或者写入的形式创建文件流
51 using(FileStream fs = finfo.OpenWrite())
52 {
53 /**////根据上面创建的文件流创建写数据流
54 StreamWriter sw = new StreamWriter(fs,System.Text.Encoding.GetEncoding(" GB2312"));
55
56 /**////把新的内容写到创建的HTML页面中
57 sw.WriteLine(strhtml);
58 sw.Flush();
59 sw.Close();
60 }
61
62 /**////设置超级链接的属性
63 hyCreateFile.Text = DateTime.Now.ToString("yyyymmddhhmmss")+".html";
64 hyCreateFile.NavigateUrl = "createHTML/"+DateTime.Now.ToString("yyyymmddhhmms s")+".html";
65 }
66 catch(Exception err)
67 {
68 Response.Write (err.ToString());
69 }
70}
一、判断文件或文件夹是否存在
使用System.IO.File,
要检查一个文件是否存在非常简单: bool exist = System.IO.File.Exists(fileName);
如果需要判断目录(文件夹)是否存在,可以使用System.IO.Directory: bool exist = System.IO.Directory.Exists(folderName);
二、使用delegate类型设计自定义事件
在C#编程中,除了Method和Property,任何Class都可以有自己的事件(Event)。定义和使用自定义事件的步骤如下:
(1)在Class之外定义一个delegate类型,用于确定事件程序的接口
(2)在Class内部,声明一个public event变量,类型为上一步骤定义的delegate类型
(3)在某个Method或者Property内部某处,触发事件
(4)Client程序中使用+=操作符指定事件处理程序
例子:
// 定义Delegate类型,约束事件程序的参数
public delegate void MyEventHandler(object sender, long lineNumber) ;
public class DataImports {
// 定义新事件
NewLineRead public event MyEventHandler NewLineRead ;
public void ImportData() { long i = 0 ;
// 事件参数 while() { i++ ;
// 触发事件 if( NewLineRead != null ) NewLineRead(this, i); //... } //... } //... }
// 以下为Client代码 private void CallMethod()
{ // 声明Class变量,不需要WithEvents private DataImports _da = null;
// 指定事件处理程序 _da.NewLineRead += new MyEventHandler(this.DA_EnterNewLine) ;
// 调用Class方法,途中会触发事件 _da.ImportData(); } // 事件处理程序 private void DA_EnterNewLine(object sender, long lineNumber) { // ... }
三、IP与主机名解析
使用System.Net可以实现与Ping命令行类似的IP解析功能,例如将主机名解析为IP或者反过来:
private string GetHostNameByIP(string ipAddress)
{ IPHostEntry hostInfo = Dns.GetHostByAddress(ipAddress); return hostInfo.HostName; }
private string GetIPByHostName(string hostName)
{ System.Net.IPHostEntry hostInfo = Dns.GetHostByName(hostName); return hostInfo.AddressList[0].ToString(); }
一、最小化窗口
点击“X”或“Alt+F4”时,最小化窗口,
如: protected override void WndProc(ref Message m)
{ const int WM_SYSCOMMAND = 0x0112; const int SC_CLOSE = 0xF060; if (m.Msg == WM_SYSCOMMAND && (int) m.WParam == SC_CLOSE)
{ // User clicked close button this.WindowState = FormWindowState.Minimized; return; } base.WndProc(ref m); }
二、如何让Foreach 循环运行的更快 foreach是一个对集合中的元素进行简单的枚举及处理的现成语句,
用法如下例所示: using System; using System.Collections; namespace LoopTest { class Class1
{ static void Main(string[] args)
{ // create an ArrayList of strings ArrayList array = new ArrayList(); array.Add("Marty"); array.Add("Bill"); array.Add("George");
// print the value of every item foreach (string item in array) { Console.WriteLine(item); } } }
你可以将foreach语句用在每个实现了Ienumerable接口的集合里。如果想了解更多foreach的用法,你可以查看.NET Framework SDK文档中的C# Language Specification。 在编译的时候,C#编辑器会对每一个foreach 区域进行转换。IEnumerator enumerator = array.GetEnumerator();
try
{ string item; while (enumerator.MoveNext())
{ item = (string) enumerator.Current; Console.WriteLine(item); } } finally
{ IDisposable d = enumerator as IDisposable; if (d != null) d.Dispose(); }
这说明在后台,foreach的管理会给你的程序带来一些增加系统开销的额外代码。
三、将图片保存到一个XML文件 WinForm的资源文件中,
将PictureBox的Image属性等非文字内容都转变成文本保存,这是通过序列化(Serialization)实现的,
例子:// using System.Runtime.Serialization.Formatters.Soap; Stream stream = new FileStream("E:\\Image.xml",FileMode.Create,FileAcc ess.Write,FileShare.None); SoapFormatter f = new SoapFormatter(); Image img = Image.FromFile("E:\\Image.bmp");
f.Serialize(stream,img); stream.Close();
四、屏蔽CTRL-V 在WinForm中的TextBox控件没有办法屏蔽CTRL-V的剪贴板粘贴动作,如果需要一个输入框,但是不希望用户粘贴剪贴板的内容,可以改用RichTextBox控件,并且在KeyDown中屏蔽掉CTRL-V键,
例子:
private void richTextBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{ if(e.Control && e.KeyCode==Keys.V) e.Handled = true; }
asp.net的文件操作
文件操作常有以下几种方法:
appenttext
copy
move
delete
exists
open
openread
openwrite
create
createtext
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(@"d:\a");
if (di.GetFiles().Length + di.GetDirectories().Length == 0)
{
//目录为空
}
StreamWriter sw = File.AppendText(Server.MapPath(".")+"\\myText.txt");
sw.WriteLine("追逐理想");
sw.WriteLine("kzlll");
sw.WriteLine(".NET笔记");
sw.Flush();
sw.Close();
C#拷贝文件
string orignFile,NewFile;
OrignFile = Server.MapPath(".")+"\\myText.txt";
NewFile = Server.MapPath(".")+"\\myTextCopy.txt";
File.Copy(OrignFile,NewFile,true);
C#删除文件
string delFile = Server.MapPath(".")+"\\myTextCopy.txt";
File.Delete(delFile);
C#移动文件
string orignFile,NewFile;
OrignFile = Server.MapPath(".")+"\\myText.txt";
NewFile = Server.MapPath(".")+"\\myTextCopy.txt";
File.Move(OrignFile,NewFile);
C#创建目录
// 创建目录c:\sixAge
DirectoryInfo d=Directory.CreateDirectory("c:\\sixAge");
// d1指向c:\sixAge\sixAge1
DirectoryInfo d1=d.CreateSubdirectory("sixAge1");
// d2指向c:\sixAge\sixAge1\sixAge1_1
DirectoryInfo d2=d1.CreateSubdirectory("sixAge1_1");
// 将当前目录设为c:\sixAge
Directory.SetCurrentDirectory("c:\\sixAge");
// 创建目录c:\sixAge\sixAge2
Directory.CreateDirectory("sixAge2");
// 创建目录c:\sixAge\sixAge2\sixAge2_1
Directory.CreateDirectory("sixAge2\\sixAge2_1");
递归删除文件夹及文件
<%@ Page Language=C#%>
<%@ Import namespace="System.IO"%>
<Script runat=server>
public void DeleteFolder(string dir)
{
if (Directory.Exists(dir)) //如果存在这个文件夹删除之
{
foreach(string d in Directory.GetFileSystemEntries(dir))
{
if(File.Exists(d))
File.Delete(d); //直接删除其中的文件
else
DeleteFolder(d); //递归删除子文件夹
}
Directory.Delete(dir); //删除已空文件夹
Response.Write(dir+" 文件夹删除成功");
}
else
Response.Write(dir+" 该文件夹不存在"); //如果文件夹不存在则提示
}
protected void Page_Load (Object sender ,EventArgs e)
{
string Dir="D:\\gbook\\11";
DeleteFolder(Dir); //调用函数删除文件夹
}
// ================================================== ====
// 实现一个静态方法将指定文件夹下面的所有内容copy到目标文件夹下面
// 如果目标文件夹为只读属性就会报错。
// April 18April2005 In STU
// ================================================== ====
public static void CopyDir(string srcPath,string aimPath)
{
try
{
// 检查目标目录是否以目录分割字符结束如果不是则添加之
if(aimPath[aimPath.Length-1] != Path.DirectorySeparatorChar)
aimPath += Path.DirectorySeparatorChar;
// 判断目标目录是否存在如果不存在则新建之
if(!Directory.Exists(aimPath)) Directory.CreateDirectory(aimPath);
// 得到源目录的文件列表,该里面是包含文件以及目录路径的一个数组
// 如果你指向copy目标文件下面的文件而不包含目录请使用下面的方法
// string[] fileList = Directory.GetFiles(srcPath);
string[] fileList = Directory.GetFileSystemEntries(srcPath);
// 遍历所有的文件和目录
foreach(string file in fileList)
{
// 先当作目录处理如果存在这个目录就递归Copy该目录下面的文件
if(Directory.Exists(file))
CopyDir(file,aimPath+Path.GetFileName(file));
// 否则直接Copy文件
else
File.Copy(file,aimPath+Path.GetFileName(file),true );
}
}
catch (Exception e)
{
MessageBox.Show (e.ToString());
}
}
// ================================================== ====
// 实现一个静态方法将指定文件夹下面的所有内容Detele
// 测试的时候要小心操作,删除之后无法恢复。
// April 18April2005 In STU
// ================================================== ====
public static void DeleteDir(string aimPath)
{
try
{
// 检查目标目录是否以目录分割字符结束如果不是则添加之
if(aimPath[aimPath.Length-1] != Path.DirectorySeparatorChar)
aimPath += Path.DirectorySeparatorChar;
// 得到源目录的文件列表,该里面是包含文件以及目录路径的一个数组
// 如果你指向Delete目标文件下面的文件而不包含目录请使用下面的方法
// string[] fileList = Directory.GetFiles(aimPath);
string[] fileList = Directory.GetFileSystemEntries(aimPath);
// 遍历所有的文件和目录
foreach(string file in fileList)
{
// 先当作目录处理如果存在这个目录就递归Delete该目录下面的文件
if(Directory.Exists(file))
{
DeleteDir(aimPath+Path.GetFileName(file));
}
// 否则直接Delete文件
else
{
File.Delete (aimPath+Path.GetFileName(file));
}
}
//删除文件夹
System.IO .Directory .Delete (aimPath,true);
}
catch (Exception e)
{
MessageBox.Show (e.ToString());
}
}
需要引用命名空间:
using System.IO;
/**//// <summary>
/// 拷贝文件夹(包括子文件夹)到指定文件夹下,源文件夹和目标文件夹均需绝对路径. 格式: CopyFolder(源文件夹,目标文件夹);
/// </summary>
/// <param name="strFromPath"></param>
/// <param name="strToPath"></param>
//------------------------------------------------ --
//作者:明天去要饭 QQ:305725744
//------------------------------------------------ ---
public static void CopyFolder(string strFromPath,string strToPath)
{
//如果源文件夹不存在,则创建
if (!Directory.Exists(strFromPath))
{
Directory.CreateDirectory(strFromPath);
}
//取得要拷贝的文件夹名
string strFolderName = strFromPath.Substring(strFromPath.LastIndexOf("\\" ) + 1,strFromPath.Length - strFromPath.LastIndexOf("\\") - 1);
//如果目标文件夹中没有源文件夹则在目标文件夹中创建源文件夹
if (!Directory.Exists(strToPath + "\\" + strFolderName))
{
Directory.CreateDirectory(strToPath + "\\" + strFolderName);
}
//创建数组保存源文件夹下的文件名
string[] strFiles = Directory.GetFiles(strFromPath);
//循环拷贝文件
for(int i = 0;i < strFiles.Length;i++)
{
//取得拷贝的文件名,只取文件名,地址截掉。
string strFileName = strFiles[i].Substring(strFiles[i].LastIndexOf("\\" ) + 1,strFiles[i].Length - strFiles[i].LastIndexOf("\\") - 1);
//开始拷贝文件,true表示覆盖同名文件
File.Copy(strFiles[i],strToPath + "\\" + strFolderName + "\\" + strFileName,true);
}
//创建DirectoryInfo实例
DirectoryInfo dirInfo = new DirectoryInfo(strFromPath);
//取得源文件夹下的所有子文件夹名称
DirectoryInfo[] ZiPath = dirInfo.GetDirectories();
for (int j = 0;j < ZiPath.Length;j++)
{
//获取所有子文件夹名
string strZiPath = strFromPath + "\\" + ZiPath[j].ToString();
//把得到的子文件夹当成新的源文件夹,从头开始新一轮的拷贝
CopyFolder(strZiPath,strToPath + "\\" + strFolderName);
}
}
一.读取文本文件
1/**//// <summary>
2/// 读取文本文件
3/// </summary>
4private void ReadFromTxtFile()
5{
6 if(filePath.PostedFile.FileName != "")
7 {
8 txtFilePath =filePath.PostedFile.FileName;
9 fileExtName = txtFilePath.Substring(txtFilePath.LastIndexOf(".") +1,3);
10
11 if(fileExtName !="txt" && fileExtName != "TXT")
12 {
13 Response.Write("请选择文本文件");
14 }
15 else
16 {
17 StreamReader fileStream = new StreamReader(txtFilePath,Encoding.Default);
18 txtContent.Text = fileStream.ReadToEnd();
19 fileStream.Close();
20 }
21 }
22 }
二.获取文件列表
1/**//// <summary>
2/// 获取文件列表
3/// </summary>
4private void GetFileList()
5{
6 string strCurDir,FileName,FileExt;
7
8 /**////文件大小
9 long FileSize;
10
11 /**////最后修改时间;
12 DateTime FileModify;
13
14 /**////初始化
15 if(!IsPostBack)
16 {
17 /**////初始化时,默认为当前页面所在的目录
18 strCurDir = Server.MapPath(".");
19 lblCurDir.Text = strCurDir;
20 txtCurDir.Text = strCurDir;
21 }
22 else
23 {
24 strCurDir = txtCurDir.Text;
25 txtCurDir.Text = strCurDir;
26 lblCurDir.Text = strCurDir;
27 }
28 FileInfo fi;
29 DirectoryInfo dir;
30 TableCell td;
31 TableRow tr;
32 tr = new TableRow();
33
34 /**////动态添加单元格内容
35 td = new TableCell();
36 td.Controls.Add(new LiteralControl("文件名"));
37 tr.Cells.Add(td);
38 td = new TableCell();
39 td.Controls.Add(new LiteralControl("文件类型"));
40 tr.Cells.Add(td);
41 td = new TableCell();
42 td.Controls.Add(new LiteralControl("文件大小"));
43 tr.Cells.Add(td);
44 td = new TableCell();
45 td.Controls.Add(new LiteralControl("最后修改时间"));
46 tr.Cells.Add(td);
47
48 tableDirInfo.Rows.Add(tr);
49
50 /**////针对当前目录建立目录引用对象
51 DirectoryInfo dirInfo = new DirectoryInfo(txtCurDir.Text);
52
53 /**////循环判断当前目录下的文件和目录
54 foreach(FileSystemInfo fsi in dirInfo.GetFileSystemInfos())
55 {
56 FileName = "";
57 FileExt = "";
58 FileSize = 0;
59
60 /**////如果是文件
61 if(fsi is FileInfo)
62 {
63 fi = (FileInfo)fsi;
64
65 /**////取得文件名
66 FileName = fi.Name;
67
68 /**////取得文件的扩展名
69 FileExt = fi.Extension;
70
71 /**////取得文件的大小
72 FileSize = fi.Length;
73
74 /**////取得文件的最后修改时间
75 FileModify = fi.LastWriteTime;
76 }
77
78 /**////否则是目录
79 else
80 {
81 dir = (DirectoryInfo)fsi;
82
83 /**////取得目录名
84 FileName = dir.Name;
85
86 /**////取得目录的最后修改时间
87 FileModify = dir.LastWriteTime;
88
89 /**////设置文件的扩展名为"文件夹"
90 FileExt = "文件夹";
91 }
92
93 /**////动态添加表格内容
94 tr = new TableRow();
95 td = new TableCell();
96 td.Controls.Add(new LiteralControl(FileName));
97 tr.Cells.Add(td);
98 td = new TableCell();
99 td.Controls.Add(new LiteralControl(FileExt));
100 tr.Cells.Add(td);
101 td = new TableCell();
102 td.Controls.Add(new LiteralControl(FileSize.ToString()+"字节"));
103 tr.Cells.Add(td);
104 td = new TableCell();
105 td.Controls.Add(new LiteralControl(FileModify.ToString("yyyy-mm-dd hh:mm:ss")));
106 tr.Cells.Add(td);
107 tableDirInfo.Rows.Add(tr);
108 }
109}
三.读取日志文件
1/**//// <summary>
2/// 读取日志文件
3/// </summary>
4private void ReadLogFile()
5{
6 /**////从指定的目录以打开或者创建的形式读取日志文件
7 FileStream fs = new FileStream(Server.MapPath("upedFile")+"\\logfile.txt", FileMode.OpenOrCreate, FileAccess.Read);
8
9 /**////定义输出字符串
10 StringBuilder output = new StringBuilder();
11
12 /**////初始化该字符串的长度为0
13 output.Length = 0;
14
15 /**////为上面创建的文件流创建读取数据流
16 StreamReader read = new StreamReader(fs);
17
18 /**////设置当前流的起始位置为文件流的起始点
19 read.BaseStream.Seek(0, SeekOrigin.Begin);
20
21 /**////读取文件
22 while (read.Peek() > -1)
23 {
24 /**////取文件的一行内容并换行
25 output.Append(read.ReadLine() + "\n");
26 }
27
28 /**////关闭释放读数据流
29 read.Close();
30
31 /**////返回读到的日志文件内容
32 return output.ToString();
33}
四.写入日志文件
1/**//// <summary>
2/// 写入日志文件
3/// </summary>
4/// <param name="input"></param>
5private void WriteLogFile(string input)
6{
7 /**////指定日志文件的目录
8 string fname = Server.MapPath("upedFile") + "\\logfile.txt";
9 /**////定义文件信息对象
10 FileInfo finfo = new FileInfo(fname);
11
12 /**////判断文件是否存在以及是否大于2K
13 if ( finfo.Exists && finfo.Length > 2048 )
14 {
15 /**////删除该文件
16 finfo.Delete();
17 }
18 /**////创建只写文件流
19 using(FileStream fs = finfo.OpenWrite())
20 {
21 /**////根据上面创建的文件流创建写数据流
22 StreamWriter w = new StreamWriter(fs);
23
24 /**////设置写数据流的起始位置为文件流的末尾
25 w.BaseStream.Seek(0, SeekOrigin.End);
26
27 /**////写入“Log Entry : ”
28 w.Write("\nLog Entry : ");
29
30 /**////写入当前系统时间并换行
31 w.Write("{0} {1} \r\n", DateTime.Now.ToLongTimeString(),
32 DateTime.Now.ToLongDateString());
33
34 /**////写入日志内容并换行
35 w.Write(input + "\n");
36
37 /**////写入------------------------------------“并换行
38 w.Write("------------------------------------\n");
39
40 /**////清空缓冲区内容,并把缓冲区内容写入基础流
41 w.Flush();
42
43 /**////关闭写数据流
44 w.Close();
45 }
46}
五.创建HTML文件
1/**//// <summary>
2/// 创建HTML文件
3/// </summary>
4private void CreateHtmlFile()
5{
6 /**////定义和html标记数目一致的数组
7 string[] newContent = new string[5];
8 StringBuilder strhtml = new StringBuilder();
9 try
10 {
11 /**////创建StreamReader对象
12 using (StreamReader sr = new StreamReader(Server.MapPath("createHTML") + "\\template.html"))
13 {
14 String oneline;
15
16 /**////读取指定的HTML文件模板
17 while ((oneline = sr.ReadLine()) != null)
18 {
19 strhtml.Append(oneline);
20 }
21 sr.Close();
22 }
23 }
24 catch(Exception err)
25 {
26 /**////输出异常信息
27 Response.Write(err.ToString());
28 }
29 /**////为标记数组赋值
30 newContent[0] = txtTitle.Text;//标题
31 newContent[1] = "BackColor='#cccfff'";//背景色
32 newContent[2] = "#ff0000";//字体颜色
33 newContent[3] = "100px";//字体大小
34 newContent[4] = txtContent.Text;//主要内容
35
36 /**////根据上面新的内容生成html文件
37 try
38 {
39 /**////指定要生成的HTML文件
40 string fname = Server.MapPath("createHTML") +"\\" + DateTime.Now.ToString("yyyymmddhhmmss") + ".html";
41
42 /**////替换html模版文件里的标记为新的内容
43 for(int i=0;i < 5;i++)
44 {
45 strhtml.Replace("$htmlkey["+i+"]",newContent[i]);
46 }
47 /**////创建文件信息对象
48 FileInfo finfo = new FileInfo(fname);
49
50 /**////以打开或者写入的形式创建文件流
51 using(FileStream fs = finfo.OpenWrite())
52 {
53 /**////根据上面创建的文件流创建写数据流
54 StreamWriter sw = new StreamWriter(fs,System.Text.Encoding.GetEncoding(" GB2312"));
55
56 /**////把新的内容写到创建的HTML页面中
57 sw.WriteLine(strhtml);
58 sw.Flush();
59 sw.Close();
60 }
61
62 /**////设置超级链接的属性
63 hyCreateFile.Text = DateTime.Now.ToString("yyyymmddhhmmss")+".html";
64 hyCreateFile.NavigateUrl = "createHTML/"+DateTime.Now.ToString("yyyymmddhhmms s")+".html";
65 }
66 catch(Exception err)
67 {
68 Response.Write (err.ToString());
69 }
70}
一、判断文件或文件夹是否存在
使用System.IO.File,
要检查一个文件是否存在非常简单: bool exist = System.IO.File.Exists(fileName);
如果需要判断目录(文件夹)是否存在,可以使用System.IO.Directory: bool exist = System.IO.Directory.Exists(folderName);
二、使用delegate类型设计自定义事件
在C#编程中,除了Method和Property,任何Class都可以有自己的事件(Event)。定义和使用自定义事件的步骤如下:
(1)在Class之外定义一个delegate类型,用于确定事件程序的接口
(2)在Class内部,声明一个public event变量,类型为上一步骤定义的delegate类型
(3)在某个Method或者Property内部某处,触发事件
(4)Client程序中使用+=操作符指定事件处理程序
例子:
// 定义Delegate类型,约束事件程序的参数
public delegate void MyEventHandler(object sender, long lineNumber) ;
public class DataImports {
// 定义新事件
NewLineRead public event MyEventHandler NewLineRead ;
public void ImportData() { long i = 0 ;
// 事件参数 while() { i++ ;
// 触发事件 if( NewLineRead != null ) NewLineRead(this, i); //... } //... } //... }
// 以下为Client代码 private void CallMethod()
{ // 声明Class变量,不需要WithEvents private DataImports _da = null;
// 指定事件处理程序 _da.NewLineRead += new MyEventHandler(this.DA_EnterNewLine) ;
// 调用Class方法,途中会触发事件 _da.ImportData(); } // 事件处理程序 private void DA_EnterNewLine(object sender, long lineNumber) { // ... }
三、IP与主机名解析
使用System.Net可以实现与Ping命令行类似的IP解析功能,例如将主机名解析为IP或者反过来:
private string GetHostNameByIP(string ipAddress)
{ IPHostEntry hostInfo = Dns.GetHostByAddress(ipAddress); return hostInfo.HostName; }
private string GetIPByHostName(string hostName)
{ System.Net.IPHostEntry hostInfo = Dns.GetHostByName(hostName); return hostInfo.AddressList[0].ToString(); }
一、最小化窗口
点击“X”或“Alt+F4”时,最小化窗口,
如: protected override void WndProc(ref Message m)
{ const int WM_SYSCOMMAND = 0x0112; const int SC_CLOSE = 0xF060; if (m.Msg == WM_SYSCOMMAND && (int) m.WParam == SC_CLOSE)
{ // User clicked close button this.WindowState = FormWindowState.Minimized; return; } base.WndProc(ref m); }
二、如何让Foreach 循环运行的更快 foreach是一个对集合中的元素进行简单的枚举及处理的现成语句,
用法如下例所示: using System; using System.Collections; namespace LoopTest { class Class1
{ static void Main(string[] args)
{ // create an ArrayList of strings ArrayList array = new ArrayList(); array.Add("Marty"); array.Add("Bill"); array.Add("George");
// print the value of every item foreach (string item in array) { Console.WriteLine(item); } } }
你可以将foreach语句用在每个实现了Ienumerable接口的集合里。如果想了解更多foreach的用法,你可以查看.NET Framework SDK文档中的C# Language Specification。 在编译的时候,C#编辑器会对每一个foreach 区域进行转换。IEnumerator enumerator = array.GetEnumerator();
try
{ string item; while (enumerator.MoveNext())
{ item = (string) enumerator.Current; Console.WriteLine(item); } } finally
{ IDisposable d = enumerator as IDisposable; if (d != null) d.Dispose(); }
这说明在后台,foreach的管理会给你的程序带来一些增加系统开销的额外代码。
三、将图片保存到一个XML文件 WinForm的资源文件中,
将PictureBox的Image属性等非文字内容都转变成文本保存,这是通过序列化(Serialization)实现的,
例子:// using System.Runtime.Serialization.Formatters.Soap; Stream stream = new FileStream("E:\\Image.xml",FileMode.Create,FileAcc ess.Write,FileShare.None); SoapFormatter f = new SoapFormatter(); Image img = Image.FromFile("E:\\Image.bmp");
f.Serialize(stream,img); stream.Close();
四、屏蔽CTRL-V 在WinForm中的TextBox控件没有办法屏蔽CTRL-V的剪贴板粘贴动作,如果需要一个输入框,但是不希望用户粘贴剪贴板的内容,可以改用RichTextBox控件,并且在KeyDown中屏蔽掉CTRL-V键,
例子:
private void richTextBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{ if(e.Control && e.KeyCode==Keys.V) e.Handled = true; }
asp.net的文件操作
文件操作常有以下几种方法:
appenttext
copy
move
delete
exists
open
openread
openwrite
create
createtext
Tags: C#
Java中字符串处理[2011年10月14日]
作者: 日期:2012-05-10
在Java开发过程中经常会遇到字符处理的问题,比如接收到xml格式的字符串,需要把它转换成对象。对象需要转化成xml字符串发送出去。
这里收藏两篇文章供日后参考。
1.利用jdom把xml字符串转化为对象
JAVA解析XML格式字符串
http://zhenhu5131420.blog.163.com/blog/static/4391 43252009102351346784/
http://blog.sina.com.cn/s/blog_700dec940100spox.ht ml
package com.freesky.xml;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.xml.sax.InputSource;
public class DuXMLDoc {
public List xmlElements(String xmlDoc) {
//创建一个新的字符串
StringReader read = new StringReader(xmlDoc);
//创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
InputSource source = new InputSource(read);
//创建一个新的SAXBuilder
SAXBuilder sb = new SAXBuilder();
try {
//通过输入源构造一个Document
Document doc = sb.build(source);
//取的根元素
Element root = doc.getRootElement();
System.out.println(root.getName());//输出根元素的名称(测试)
//得到根元素所有子元素的集合
List jiedian = root.getChildren();
//获得XML中的命名空间(XML中未定义可不写)
Namespace ns = root.getNamespace();
Element et = null;
for(int i=0;i<jiedian.size();i++){
et = (Element) jiedian.get(i);//循环依次得到子元素
System.out.println(et.getChild("users_id",ns).getT ext());
System.out.println(et.getChild("users_address",ns) .getText());
}
et = (Element) jiedian.get(0);
List zjiedian = et.getChildren();
for(int j=0;j<zjiedian.size();j++){
Element xet = (Element) zjiedian.get(j);
System.out.println(xet.getName());
}
} catch (JDOMException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
}
return null;
}
public static void main(String[] args){
DuXMLDoc doc = new DuXMLDoc();
// String xml = "<?xml version=\"1.0\" encoding=\"gb2312\"?>"+
// "<Result xmlns=\"http://www.fiorano.com/fesb/activity/DBQueryOnInpu t2/Out\">"+
// "<row resultcount=\"1\">"+
// "<users_id>1001 </users_id>"+
// "<users_name>wangwei </users_name>"+
// "<users_group>80 </users_group>"+
// "<users_address>1001号 </users_address>"+
// "</row>"+
// "<row resultcount=\"1\">"+
// "<users_id>1002 </users_id>"+
// "<users_name>wangwei </users_name>"+
// "<users_group>80 </users_group>"+
// "<users_address>1002号 </users_address>"+
// "</row>"+
// "</Result>";
// doc.xmlElements(xml);
File file = new File("./xml/outparam.xml");
try {
FileReader fr = new FileReader(file);
char[] values = new char[800];
int length = fr.read(values);
String s = new String(values, 0 , length);
System.out.println(s);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.自定义格式,把对象转换成xml字符串。
Java生成XML格式的字符串
http://padden.blog.51cto.com/2514314/578685
package com.freesky.xml;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 根据该对象可以构造Xml字符串
*
*
*/
public class XmlObject {
private static String HEAD = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
private String name;
private Object value;
private List<Attribute> attributes;
private List<XmlObject> subXmlObjects;
public static void main(String[] args) {
XmlObject xmlObject = new XmlObject("deviceparams");
xmlObject.setAttribute("type", "input");
xmlObject.setAttribute("id", "123123123");
XmlObject paramObject = new XmlObject("param");
paramObject.setAttribute("name", "techtype");
paramObject.setAttribute("value", "HW123");
xmlObject.addSubXmlObject(paramObject);
XmlObject inputObject = new XmlObject("inputparams");
inputObject.addSubXmlObject(paramObject);
xmlObject.addSubXmlObject(inputObject);
System.out.println(xmlObject.toFormatXml());
}
/**
* 根据name构造XmlObject
*
* @param name
* 生成xml时标签名,如name="html",则生成xml为<html/>
*/
public XmlObject(String name) {
this.name = name;
}
/**
* 获得当前对象的名称
*
* @return 返回当前对象的名称
*/
public final String getName() {
return name;
}
/**
* 设置当前对象的名称
*
* @param name
* 给定名称
*/
public final void setName(String name) {
this.name = name;
}
/**
* 获得当前对象的值
*
* @return 返回当前对象的值
*/
public final Object getValue() {
return value;
}
/**
* 设置当前对象的值
*
* @param value
* 给定值
*/
public final void setValue(Object value) {
this.value = value;
}
/**
* 为当前XmlObject添加属性
*
* @param name
* 属性名
* @param value
* 属性值
*/
public final void setAttribute(String name, Object value) {
if (attributes == null) {
attributes = new ArrayList<Attribute>();
}
Attribute attribute = null;
for (Attribute att : attributes) {
if (name.equalsIgnoreCase(att.getName())) {
attribute = att;
break;
}
}
if (attribute == null) {
attribute = new Attribute(name, value);
attributes.add(attribute);
} else {
attribute.setValue(value);
}
}
/**
* 根据属性名称获得当前XmlObject对象的属性值
*
* @param name
* 属性名称
* @return 属性值
*/
public final Object getAttributeValue(String name) {
return getAttributeValue(name, null);
}
/**
* 根据属性名称获得当前XmlObject对象的属性值
*
* @param name
* 属性名称
* @param defaultValue
* 默认值
* @return 若属性存在,则返回属性值,否则返回默认值
*/
public final Object getAttributeValue(String name, Object defaultValue) {
Attribute attribute = null;
for (Attribute att : attributes) {
if (name.equalsIgnoreCase(att.getName())) {
attribute = att;
break;
}
}
if (attribute == null) {
return defaultValue;
} else {
return attribute.getValue();
}
}
/**
* 为当前XmlObject对象添加子XmlObject对象
*
* @param xmlObject
* 给定XmlObject对象
*/
public final void addSubXmlObject(XmlObject xmlObject) {
if (subXmlObjects == null) {
subXmlObjects = new ArrayList<XmlObject>();
}
subXmlObjects.add(xmlObject);
}
/**
* 构造当前对象的压缩XML字符串
*
* @return XML字符串
*/
public final String toCompactXml() {
return HEAD + getNoHeadXml("", "");
}
/**
* 根据格式化留白("\t")和默认的行分隔符("\t")构造当前对象的XML字符串
*
* @return XML字符串
*/
public String toFormatXml() {
return toFormatXml("\t");
}
/**
* 根据格式化留白和默认的行分隔符("\n")构造当前对象的XML字符串
*
* @param blank
* 格式化留白
* @return XML字符串
*/
protected final String toFormatXml(String blank) {
return HEAD + "\n" + getNoHeadXml(blank, "\n");
}
/**
* 根据格式化留白和行分隔符构造当前对象的无头的XML字符串
*
* @param blank
* 格式化留白
* @param split
* 行分隔符
* @return 无头的XML字符串
*/
protected final String getNoHeadXml(String blank, String split) {
return getNoHeadXml(blank, split, 0);
}
private final String getNoHeadXml(String blank, String split, int count) {
String blanks = "";
for (int i = 0; i < count; i++) {
blanks += blank;
}
StringBuffer sb = new StringBuffer();
sb.append(blanks + "<" + name);
if (attributes != null) {
Iterator<Attribute> iterator = attributes.iterator();
while (iterator.hasNext()) {
Attribute attribute = iterator.next();
sb.append(" " + attribute.getName() + "=\""
+ attribute.getValue() + "\"");
}
}
if (subXmlObjects == null) {
if (value == null) {
sb.append("/>" + split);
} else {
sb.append(">");
sb.append(value);
sb.append("</" + name + ">" + split);
}
} else {
sb.append(">" + split);
Iterator<XmlObject> iterator = subXmlObjects.iterator();
count += 1;
while (iterator.hasNext()) {
XmlObject xmlObject = iterator.next();
sb.append(xmlObject.getNoHeadXml(blank, split, count));
}
sb.append(blanks + "</" + name + ">" + split);
}
return sb.toString();
}
class Attribute {
private String name;
private Object value;
public Attribute(String name, Object value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
}
3. 利用JDK1.5中javax.xml包对字符串进行处理,其中提供了DOM解析和SAX解析。下面这个例子演示了如何解析xml文件,如何构造xml字符串,以及如何解析xml字符串 。
package com.freesky.xml;
/** 要读的xml文件
<?xml version="1.0" encoding="GB2312"?>
<学生花名册>
<学生 性别 = "男">
<姓名>李华</姓名>
<年龄>14</年龄>
</学生>
<学生 性别 = "男">
<姓名>张三</姓名>
<年龄>16</年龄>
</学生>
</学生花名册>
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationExcept ion;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
public class DomTest {
Vector<Student> students_Vector;
/**
* 功能:读取XML文件中的信息
*/
public Vector<Student> readXMLFile(String file) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(file); // 获取到xml文件
// 下面开始读取
loadDocument(doc);
return students_Vector;
}
/**
* 功能:直接提取XML格式字符串中的信息
*/
public Vector<Student> readXMLString(String xmlStr) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
InputStream inputStream = new ByteArrayInputStream(xmlStr.getBytes());
Document doc = builder.parse(inputStream); //
// 下面开始读取
loadDocument(doc);
return students_Vector;
}
private void loadDocument(Document doc) {
// 下面开始读取
Element root = doc.getDocumentElement(); // 获取根元素
NodeList students = root.getElementsByTagName("学生");
students_Vector = new Vector<Student>();
for (int i = 0; i < students.getLength(); i++) {
// 一次取得每一个学生元素
Element ss = (Element) students.item(i);
// 创建一个学生的实例
Student stu = new Student();
stu.setSex(ss.getAttribute("性别"));
NodeList names = ss.getElementsByTagName("姓名");
Element e = (Element) names.item(0);
Node t = e.getFirstChild();
stu.setName(t.getNodeValue());
NodeList ages = ss.getElementsByTagName("年龄");
e = (Element) ages.item(0);
t = e.getFirstChild();
stu.setAge(Integer.parseInt(t.getNodeValue()));
students_Vector.add(stu);
}
}
// 将Document内容 写入XML字符串并返回
private String callWriteXmlString(Document doc, String encoding) {
try {
Source source = new DOMSource(doc);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
OutputStreamWriter write = new OutputStreamWriter(outStream);
Result result = new StreamResult(write);
Transformer xformer = TransformerFactory.newInstance()
.newTransformer();
xformer.setOutputProperty(OutputKeys.ENCODING, encoding);
xformer.transform(source, result);
return outStream.toString();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
return null;
} catch (TransformerException e) {
e.printStackTrace();
return null;
}
}
/**
* 功能:生成XML格式的字符串
*/
public String writeXMLString() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
try {
builder = dbf.newDocumentBuilder();
} catch (Exception e) {
}
Document doc = builder.newDocument();
Element root = doc.createElement("学生花名册");
doc.appendChild(root); // 将根元素添加到文档上 // 获取学生信息
for (Student s : students_Vector) {
// 创建一个学生
Element stu = doc.createElement("学生");
stu.setAttribute("性别", s.getSex());
root.appendChild(stu);// 添加属性
// 创建文本姓名节点
Element name = doc.createElement("姓名");
stu.appendChild(name);
Text tname = doc.createTextNode(s.getName());
name.appendChild(tname);
// 创建文本年龄节点
Element age = doc.createElement("年龄");
stu.appendChild(age); // 将age添加到学生节点上
Text tage = doc.createTextNode(String.valueOf(s.getAge()));
age.appendChild(tage); // 将文本节点放在age节点上
}
try {
String result = callWriteXmlString(doc, "gb2312");
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 主函数
*/
public static void main(String args[]) {
String str = "xml\\Student.xml";
DomTest t = new DomTest();
System.out.println("解析原始XML文件:");
try {
Vector<Student> v = t.readXMLFile(str);
Iterator<Student> it = v.iterator();
while (it.hasNext()) {
Student s = (Student) it.next();
System.out.println(s.getName() + "\t" + s.getAge() + "\t"
+ s.getSex());
}
} catch (Exception e) {
e.printStackTrace();
}
String xmlStr = t.writeXMLString();
System.out.println("\n生成的XML字符串:\n" + xmlStr);
try {
Vector<Student> v = t.readXMLString(xmlStr);
Iterator<Student> it = v.iterator();
System.out.println("\n解析生成的XML字符串:");
while (it.hasNext()) {
Student s = (Student) it.next();
System.out.println(s.getName() + "\t" + s.getAge() + "\t"
+ s.getSex());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Student {
private String sex;
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(String s) {
sex = s;
}
public String getSex() {
return sex;
}
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
}
这里收藏两篇文章供日后参考。
1.利用jdom把xml字符串转化为对象
JAVA解析XML格式字符串
http://zhenhu5131420.blog.163.com/blog/static/4391 43252009102351346784/
http://blog.sina.com.cn/s/blog_700dec940100spox.ht ml
package com.freesky.xml;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.xml.sax.InputSource;
public class DuXMLDoc {
public List xmlElements(String xmlDoc) {
//创建一个新的字符串
StringReader read = new StringReader(xmlDoc);
//创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
InputSource source = new InputSource(read);
//创建一个新的SAXBuilder
SAXBuilder sb = new SAXBuilder();
try {
//通过输入源构造一个Document
Document doc = sb.build(source);
//取的根元素
Element root = doc.getRootElement();
System.out.println(root.getName());//输出根元素的名称(测试)
//得到根元素所有子元素的集合
List jiedian = root.getChildren();
//获得XML中的命名空间(XML中未定义可不写)
Namespace ns = root.getNamespace();
Element et = null;
for(int i=0;i<jiedian.size();i++){
et = (Element) jiedian.get(i);//循环依次得到子元素
System.out.println(et.getChild("users_id",ns).getT ext());
System.out.println(et.getChild("users_address",ns) .getText());
}
et = (Element) jiedian.get(0);
List zjiedian = et.getChildren();
for(int j=0;j<zjiedian.size();j++){
Element xet = (Element) zjiedian.get(j);
System.out.println(xet.getName());
}
} catch (JDOMException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
}
return null;
}
public static void main(String[] args){
DuXMLDoc doc = new DuXMLDoc();
// String xml = "<?xml version=\"1.0\" encoding=\"gb2312\"?>"+
// "<Result xmlns=\"http://www.fiorano.com/fesb/activity/DBQueryOnInpu t2/Out\">"+
// "<row resultcount=\"1\">"+
// "<users_id>1001 </users_id>"+
// "<users_name>wangwei </users_name>"+
// "<users_group>80 </users_group>"+
// "<users_address>1001号 </users_address>"+
// "</row>"+
// "<row resultcount=\"1\">"+
// "<users_id>1002 </users_id>"+
// "<users_name>wangwei </users_name>"+
// "<users_group>80 </users_group>"+
// "<users_address>1002号 </users_address>"+
// "</row>"+
// "</Result>";
// doc.xmlElements(xml);
File file = new File("./xml/outparam.xml");
try {
FileReader fr = new FileReader(file);
char[] values = new char[800];
int length = fr.read(values);
String s = new String(values, 0 , length);
System.out.println(s);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.自定义格式,把对象转换成xml字符串。
Java生成XML格式的字符串
http://padden.blog.51cto.com/2514314/578685
package com.freesky.xml;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 根据该对象可以构造Xml字符串
*
*
*/
public class XmlObject {
private static String HEAD = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
private String name;
private Object value;
private List<Attribute> attributes;
private List<XmlObject> subXmlObjects;
public static void main(String[] args) {
XmlObject xmlObject = new XmlObject("deviceparams");
xmlObject.setAttribute("type", "input");
xmlObject.setAttribute("id", "123123123");
XmlObject paramObject = new XmlObject("param");
paramObject.setAttribute("name", "techtype");
paramObject.setAttribute("value", "HW123");
xmlObject.addSubXmlObject(paramObject);
XmlObject inputObject = new XmlObject("inputparams");
inputObject.addSubXmlObject(paramObject);
xmlObject.addSubXmlObject(inputObject);
System.out.println(xmlObject.toFormatXml());
}
/**
* 根据name构造XmlObject
*
* @param name
* 生成xml时标签名,如name="html",则生成xml为<html/>
*/
public XmlObject(String name) {
this.name = name;
}
/**
* 获得当前对象的名称
*
* @return 返回当前对象的名称
*/
public final String getName() {
return name;
}
/**
* 设置当前对象的名称
*
* @param name
* 给定名称
*/
public final void setName(String name) {
this.name = name;
}
/**
* 获得当前对象的值
*
* @return 返回当前对象的值
*/
public final Object getValue() {
return value;
}
/**
* 设置当前对象的值
*
* @param value
* 给定值
*/
public final void setValue(Object value) {
this.value = value;
}
/**
* 为当前XmlObject添加属性
*
* @param name
* 属性名
* @param value
* 属性值
*/
public final void setAttribute(String name, Object value) {
if (attributes == null) {
attributes = new ArrayList<Attribute>();
}
Attribute attribute = null;
for (Attribute att : attributes) {
if (name.equalsIgnoreCase(att.getName())) {
attribute = att;
break;
}
}
if (attribute == null) {
attribute = new Attribute(name, value);
attributes.add(attribute);
} else {
attribute.setValue(value);
}
}
/**
* 根据属性名称获得当前XmlObject对象的属性值
*
* @param name
* 属性名称
* @return 属性值
*/
public final Object getAttributeValue(String name) {
return getAttributeValue(name, null);
}
/**
* 根据属性名称获得当前XmlObject对象的属性值
*
* @param name
* 属性名称
* @param defaultValue
* 默认值
* @return 若属性存在,则返回属性值,否则返回默认值
*/
public final Object getAttributeValue(String name, Object defaultValue) {
Attribute attribute = null;
for (Attribute att : attributes) {
if (name.equalsIgnoreCase(att.getName())) {
attribute = att;
break;
}
}
if (attribute == null) {
return defaultValue;
} else {
return attribute.getValue();
}
}
/**
* 为当前XmlObject对象添加子XmlObject对象
*
* @param xmlObject
* 给定XmlObject对象
*/
public final void addSubXmlObject(XmlObject xmlObject) {
if (subXmlObjects == null) {
subXmlObjects = new ArrayList<XmlObject>();
}
subXmlObjects.add(xmlObject);
}
/**
* 构造当前对象的压缩XML字符串
*
* @return XML字符串
*/
public final String toCompactXml() {
return HEAD + getNoHeadXml("", "");
}
/**
* 根据格式化留白("\t")和默认的行分隔符("\t")构造当前对象的XML字符串
*
* @return XML字符串
*/
public String toFormatXml() {
return toFormatXml("\t");
}
/**
* 根据格式化留白和默认的行分隔符("\n")构造当前对象的XML字符串
*
* @param blank
* 格式化留白
* @return XML字符串
*/
protected final String toFormatXml(String blank) {
return HEAD + "\n" + getNoHeadXml(blank, "\n");
}
/**
* 根据格式化留白和行分隔符构造当前对象的无头的XML字符串
*
* @param blank
* 格式化留白
* @param split
* 行分隔符
* @return 无头的XML字符串
*/
protected final String getNoHeadXml(String blank, String split) {
return getNoHeadXml(blank, split, 0);
}
private final String getNoHeadXml(String blank, String split, int count) {
String blanks = "";
for (int i = 0; i < count; i++) {
blanks += blank;
}
StringBuffer sb = new StringBuffer();
sb.append(blanks + "<" + name);
if (attributes != null) {
Iterator<Attribute> iterator = attributes.iterator();
while (iterator.hasNext()) {
Attribute attribute = iterator.next();
sb.append(" " + attribute.getName() + "=\""
+ attribute.getValue() + "\"");
}
}
if (subXmlObjects == null) {
if (value == null) {
sb.append("/>" + split);
} else {
sb.append(">");
sb.append(value);
sb.append("</" + name + ">" + split);
}
} else {
sb.append(">" + split);
Iterator<XmlObject> iterator = subXmlObjects.iterator();
count += 1;
while (iterator.hasNext()) {
XmlObject xmlObject = iterator.next();
sb.append(xmlObject.getNoHeadXml(blank, split, count));
}
sb.append(blanks + "</" + name + ">" + split);
}
return sb.toString();
}
class Attribute {
private String name;
private Object value;
public Attribute(String name, Object value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
}
3. 利用JDK1.5中javax.xml包对字符串进行处理,其中提供了DOM解析和SAX解析。下面这个例子演示了如何解析xml文件,如何构造xml字符串,以及如何解析xml字符串 。
package com.freesky.xml;
/** 要读的xml文件
<?xml version="1.0" encoding="GB2312"?>
<学生花名册>
<学生 性别 = "男">
<姓名>李华</姓名>
<年龄>14</年龄>
</学生>
<学生 性别 = "男">
<姓名>张三</姓名>
<年龄>16</年龄>
</学生>
</学生花名册>
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationExcept ion;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
public class DomTest {
Vector<Student> students_Vector;
/**
* 功能:读取XML文件中的信息
*/
public Vector<Student> readXMLFile(String file) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(file); // 获取到xml文件
// 下面开始读取
loadDocument(doc);
return students_Vector;
}
/**
* 功能:直接提取XML格式字符串中的信息
*/
public Vector<Student> readXMLString(String xmlStr) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
InputStream inputStream = new ByteArrayInputStream(xmlStr.getBytes());
Document doc = builder.parse(inputStream); //
// 下面开始读取
loadDocument(doc);
return students_Vector;
}
private void loadDocument(Document doc) {
// 下面开始读取
Element root = doc.getDocumentElement(); // 获取根元素
NodeList students = root.getElementsByTagName("学生");
students_Vector = new Vector<Student>();
for (int i = 0; i < students.getLength(); i++) {
// 一次取得每一个学生元素
Element ss = (Element) students.item(i);
// 创建一个学生的实例
Student stu = new Student();
stu.setSex(ss.getAttribute("性别"));
NodeList names = ss.getElementsByTagName("姓名");
Element e = (Element) names.item(0);
Node t = e.getFirstChild();
stu.setName(t.getNodeValue());
NodeList ages = ss.getElementsByTagName("年龄");
e = (Element) ages.item(0);
t = e.getFirstChild();
stu.setAge(Integer.parseInt(t.getNodeValue()));
students_Vector.add(stu);
}
}
// 将Document内容 写入XML字符串并返回
private String callWriteXmlString(Document doc, String encoding) {
try {
Source source = new DOMSource(doc);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
OutputStreamWriter write = new OutputStreamWriter(outStream);
Result result = new StreamResult(write);
Transformer xformer = TransformerFactory.newInstance()
.newTransformer();
xformer.setOutputProperty(OutputKeys.ENCODING, encoding);
xformer.transform(source, result);
return outStream.toString();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
return null;
} catch (TransformerException e) {
e.printStackTrace();
return null;
}
}
/**
* 功能:生成XML格式的字符串
*/
public String writeXMLString() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
try {
builder = dbf.newDocumentBuilder();
} catch (Exception e) {
}
Document doc = builder.newDocument();
Element root = doc.createElement("学生花名册");
doc.appendChild(root); // 将根元素添加到文档上 // 获取学生信息
for (Student s : students_Vector) {
// 创建一个学生
Element stu = doc.createElement("学生");
stu.setAttribute("性别", s.getSex());
root.appendChild(stu);// 添加属性
// 创建文本姓名节点
Element name = doc.createElement("姓名");
stu.appendChild(name);
Text tname = doc.createTextNode(s.getName());
name.appendChild(tname);
// 创建文本年龄节点
Element age = doc.createElement("年龄");
stu.appendChild(age); // 将age添加到学生节点上
Text tage = doc.createTextNode(String.valueOf(s.getAge()));
age.appendChild(tage); // 将文本节点放在age节点上
}
try {
String result = callWriteXmlString(doc, "gb2312");
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 主函数
*/
public static void main(String args[]) {
String str = "xml\\Student.xml";
DomTest t = new DomTest();
System.out.println("解析原始XML文件:");
try {
Vector<Student> v = t.readXMLFile(str);
Iterator<Student> it = v.iterator();
while (it.hasNext()) {
Student s = (Student) it.next();
System.out.println(s.getName() + "\t" + s.getAge() + "\t"
+ s.getSex());
}
} catch (Exception e) {
e.printStackTrace();
}
String xmlStr = t.writeXMLString();
System.out.println("\n生成的XML字符串:\n" + xmlStr);
try {
Vector<Student> v = t.readXMLString(xmlStr);
Iterator<Student> it = v.iterator();
System.out.println("\n解析生成的XML字符串:");
while (it.hasNext()) {
Student s = (Student) it.next();
System.out.println(s.getName() + "\t" + s.getAge() + "\t"
+ s.getSex());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Student {
private String sex;
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(String s) {
sex = s;
}
public String getSex() {
return sex;
}
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
}
Tags: java
SqlServer只读(转[2011年08月04日]
作者: 日期:2012-05-10
症状:
在sqlserver2005中附加数据库时,附加的数据库会变成只读的,只能进行查询操作。
原因:
出现此问题的原因一般是由于数据库的运行账户在操作系统中的权限不够造成的,sqlserver的默认启动账户是“网络服务(Network Service)”,而该账户的权限是有限的
解决方法:
1 打开SqlServer Configuration Manager 开始-》Microsoft Sqlserver 2005-》配置工具-》SqlServer Configuration Manager
2 在SqlServer Configuration Manager 窗口左边选中SQLServer 2005 服务,在窗口右边会出现一些列表项,选中Sqlserver(MSSqlserver)或SqlServer(SqlExpress)点击右键选择属性。
3 打开属性窗口,会发现内置帐户下面的下拉框选中的网络服务,将其改为本地服务。
4 打开SqlServer 2005 ,在只读的数据库上右击选择属性,选中属性窗口左边选择页下面的选项,在窗口右边将“数据库为只读”改为false ,点击确定即可。
在sqlserver2005中附加数据库时,附加的数据库会变成只读的,只能进行查询操作。
原因:
出现此问题的原因一般是由于数据库的运行账户在操作系统中的权限不够造成的,sqlserver的默认启动账户是“网络服务(Network Service)”,而该账户的权限是有限的
解决方法:
1 打开SqlServer Configuration Manager 开始-》Microsoft Sqlserver 2005-》配置工具-》SqlServer Configuration Manager
2 在SqlServer Configuration Manager 窗口左边选中SQLServer 2005 服务,在窗口右边会出现一些列表项,选中Sqlserver(MSSqlserver)或SqlServer(SqlExpress)点击右键选择属性。
3 打开属性窗口,会发现内置帐户下面的下拉框选中的网络服务,将其改为本地服务。
4 打开SqlServer 2005 ,在只读的数据库上右击选择属性,选中属性窗口左边选择页下面的选项,在窗口右边将“数据库为只读”改为false ,点击确定即可。
Tags: SQLServer
JAVA代码规范[2011年11月16日]
作者: 日期:2012-05-10
重要提醒:系统检测到您的帐号可能存在被盗风险,请尽快查看风险提示,并立即修改密码。 | 关闭
网易博客安全提醒:系统检测到您当前密码的安全性较低,为了您的账号安全,建议您适时修改密码 立即修改 | 关闭
定义这个规范的目的是让项目中所有的文档都看起来像一个人写的,增加可读性,减少项目组中因为换人而带来的损失。(这些规范并不是一定要绝对遵守,但是一定要让程序有良好的可读性)
主要分四个部分,1、命名规范,对于临时变量,可以采用短变量名提高效率,对于成员变量,尽量从变量名上体现其含义。2、文件样式规范,主要关注正确的注释填写。3、代码编写格式,这个可以通过一些工具来实现。比如jalopy。4。程序编写规范, 出于减容以及提速的考虑,具体编程中的一些小技巧和注意点,需要注意的是,往往忠孝不能两全,规则本身会有冲突的地方,熊掌和鱼,根据系统需求决定取舍。
命名规范 一般不要用package,浪费。
Class 的命名
Class 的名字必须由大写字母开头而其它字母都小写的单词组成
Class 变量的命名
变量的名字必须用一个小写字母开头。后面的单词用大写字母开头。
Static Final 变量的命名
Static Final 变量的名字应该都大写,并且指出完整含义。
参数的命名
参数的名字必须和变量的命名规范一致。
数组的命名
数组应该总是用下面的方式来命名:
byte[] buffer;
而不是:
byte buffer[];
方法的参数
使用有意义的参数命名,如果可能的话,使用和要赋值的字段一样的名字:
1
2
3
4
SetCounter(int size)
{
this.size = size;
}
多层for循环循环变量的命名
最好采用能够体现其层数的循环变量名
如:
for(i=0;i<10;i++)
for(ii=0;ii<10;ii++)
for(iii=0;iii<10;iii++);
Java 文件样式 所有的 Java(*.java) 文件都必须遵守如下的样式规则
版权信息
版权信息必须在 java 文件的开头,比如:
/** * Copyright ? 2000 XXX Co. Ltd. * All right reserved. */
其它不需要出现在 javadoc 的信息也可以包含在这?。
Package/Imports
package 行要在 import 行之前,import 中标准的包名要在本地的包名之前,而且按照字母顺序排列。如果 import 行中包含了同一个包中的不同子目录,则应该用 * 来处理。 1
2
3
package hotlava.net.stats;
import java.io.*;
import java.util.Observable;import hotlava.util.Application;
这儿java.io.* 使用来代替InputStream and OutputStream 的。
Class
接下来的是类的注释,一般是用来解释类的。 1
/** * A class representing a set of packet and byte counters * It is observable to allow it to be watched, but only * reports changes when the current set is complete */
接下来是类定义,包含了在不同的行的 extends 和 implements 1
publicclass CounterSet extends Observable implements Cloneable
Class Fields
接下来是类的成员变量: 1
2
3
/** * Packet counters */
protectedint[] packets;
public 的成员变量必须生成文文件(JavaDoc)。proceted、private和 package 定义的成员变量如果名字含义明确的话,可以没有注释。
存取方法
接下来是类变量的存取的方法。它只是简单的用来将类的变量赋值获取值的话,可以简单的写在一行上。 1
2
3
4
5
6
7
8
9
10
11
12
13
/** * Get the counters * @return an array containing the statistical data. This array has been * freshly allocated and can be modified by the caller. */publicint[] getPackets() {
return copyArray(packets, offset);
}
publicint[] getBytes() {
return copyArray(bytes, offset);
}
publicint[] getPackets() {
return packets;
}
publicvoid setPackets(int[] packets) {
this.packets = packets;
}
其它的方法不要写在一行上
构造函数
接下来是构造函数,它应该用递增的方式写(比如:参数多的写在后面)。
访问类型 ("public", "private" 等.) 和任何 "static", "final" 或 "synchronized" 应该在一行中,并且方法和参数另写一行,这样可以使方法和参数更易读。 1
2
3
4
public CounterSet
(int size){
this.size = size;
}
类方法
下面开始写类的方法: 1
2
3
4
5
6
7
8
/** * Set the packet counters * (such as when restoring from a database) */
protected finalvoid setArray(int[] r1, int[] r2, int[] r3, int[] r4) throws IllegalArgumentException
{
// // Ensure the arrays are of equal size //
if (r1.length != r2.length || r1.length != r3.length || r1.length != r4.length)
thrownew IllegalArgumentException("Arrays must be of the same size"); System.arraycopy(r1, 0, r3, 0, r1.length);
System.arraycopy(r2, 0, r4, 0, r1.length);
}
代码编写格式 下列规范可以省去,可以用eclipse插件来达到目的。
代码样式
代码应该用 unix 的格式,而不是 windows 的(比如:回车变成回车+换行)
文档化
必须用 javadoc 来为类生成文档。不仅因为它是标准,这也是被各种 java 编译器都认可的方法。
缩进
缩进应该是每行4个空格. 不要在源文件中保存Tab字符. 在使用不同的源代码管理工具时Tab字符将因为用户设置的不同而扩展为不同的宽度.
如果你使用 UltrEdit 作为你的 Java 源代码编辑器的话,你可以通过如下操作来禁止保存Tab字符, 方法是通过 UltrEdit中先设定 Tab 使用的长度室4个空格,然后用 Format|Tabs to Spaces 菜单将 Tab 转换为空格。
页宽
页宽应该设置为80字符. 源代码一般不会超过这个宽度, 并导致无法完整显示, 但这一设置也可以灵活调整. 在任何情况下, 超长的语句应该在一个逗号或者一个操作符后折行. 一条语句折行后, 应该比原来的语句再缩进2个字符.
{} 对
{} 中的语句应该单独作为一行. 例如, 下面的第1行是错误的, 第2行是正确的: 1
2
if (i>0) { i ++ };
// 错误, { 和 } 在同一行
1
2
3
4
if (i>0) {
i ++
};
// 正确, { 单独作为一行 } 语句永远单独作为一行.
如果 } 语句应该缩进到与其相对应的 { 那一行相对齐的位置。
括号
左括号和后一个字符之间不应该出现空格, 同样, 右括号和前一个字符之间也不应该出现空格. 下面的例子说明括号和空格的错误及正确使用: 1
2
CallProc( AParameter ); // 错误
CallProc(AParameter); // 正确
不要在语句中使用无意义的括号. 括号只应该为达到某种目的而出现在源代码中。下面的例子说明错误和正确的用法: 1
2
if ((I) = 42) {// 错误 - 括号毫无意义
if (I == 42) or (J == 42) then // 正确 - 的确需要括号
程序编写规范 1. new 关键字,如果要用,尽量在loading阶段,此时系统内存较多空闲的时候。不要用new string(“aaaa”);
2. 对流对象的使用,一定要保证在任何情况下都能关闭,如,在finally{}中关闭。
3. 调用getClass()方法时,尽可能利用现存的static的对象实例,而不是新建一个对象实例再调用。
4. 用System.arraycopy ()拷贝数组,而不是用for循环拷贝。
5. 对于非常量的字符串,用stringbuffer而不是string
6. 对常量字符串,用string而不是stringbuffer。
7. 对于简单if else的优化,如:
if( a==b)
{
return true;
}
else
{
return false;
}
直接 return (a==b);
8. 尽量不要在for循环()内调用方法。同样,将vector.size()变为 array.length,同样的处理方法。
EXAMPLE package examples.rules.opt;
import java.util.Vector;
public class CEL {
void method (Vector vector) {
for (int i = 0; i < vector.size (); i++) // VIOLATION
{
System.out.println(i);
}
}
}
REPAIR
If the method returns a value that will not change during the loop, store
its value in a temporary variable before the loop.
package examples.rules.opt;
import java.util.Vector;
public class CELFixed {
void method (Vector vector) {
int size = vector.size ();
for (int i = 0; i < size; i++) // FIXED
{
System.out.println(i);
}
}
}
9. 要在循环{}内声明新的变量名:
EXAMPLE package examples.rules.opt;
public class LOOP {
public void method(int max) {
for (int i = 0; i < max; i++) {
StringBuffer sb = new StringBuffer(); // VIOLATION
sb.append("loop: ");
sb.append(i);
System.out.println(sb.toString());
}
}
}
REPAIR
Declare the variable outside of the loop and reuse it.
package examples.rules.opt;
public class LOOPFixed {
public void method(int max) {
StringBuffer sb = new StringBuffer(); // FIXED
for (int i = 0; i < max; i++) {
sb.append("loop: ");
sb.append(i);
System.out.println(sb.toString());
sb.setLength(0); // FIXED
}
}
}
10. Final的成员变量最好加上static。
package examples.rules.opt;
public class NSF {
final int magicNumber= 0; //VIOLATION
}
REPAIR
package examples.rule.opt;
public class NSFFixed {
static final in magicNumber= 0; //FIXED
}
11.对于字符串中单个字符的比较,用 'charAt()' 而不是 'startsWith()'
package examples.rules.opt;
public class PCTS {
private void method(String s) {
if (s.startsWith("a")) // VIOLATION
System.out.println("starts with a.");
}
}
REPAIR
Replace 'startsWith()' with a test for non-zero length and 'charAt(0)'.
package examples.rules.opt;
public class PCTSFixed {
private void method(String s) {
if (s.length () > 0 && s.charAt(0) == 'a') // FIXED
System.out.println("starts with a.");
}
}
12. stringbuffer最好指定初始容量。
package examples.rules.opt;
public class SB {
void method () {
StringBuffer buffer = new StringBuffer(); // VIOLATION
buffer.append ("hello");
}
}
REPAIR
Provide an initial capacity for the 'StringBuffer'.
package examples.rules.opt;
public class SBFixed {
void method () {
StringBuffer buffer = new StringBuffer(SBSIZE); // FIXED
buffer.append ("hello");
}
private static final int SBSIZE = 100;
}
13. 对于字符串相加,其中的单个字符用’’而不用””
EXAMPLE package examples.rules.opt;
public class STR {
public String method(String s) {
String string = null;
string = s + "d"; // VIOLATION
return string;
}
}
REPAIR
Instead of concatenating one-character String constants, replace them with
character constants.
package examples.rules.opt;
public class STRFixed {
public String method(String s) {
String string = null;
string = s + 'd'; // FIXED
return string;
}
}
14. "try/catch/finally" 块要放在循环体外面
15.尽量用栈变量而不是实例变量和类变量,可以提高速度。
EXAMPLE package examples.rules.opt;
public class USV {
void addSum (int[] values) {
for (int i=0; i < values.length; i++) // VIOLATION
_sum += values;
}
private int _sum;
}
REPAIR
If possible, use local variables when accessing variables frequently.
For example you could use a local temporary variable as in:
package examples.rules.opt;
public class USVFixed {
void addSum (int[] values) {
int sum = _sum; // FIXED: temporary local variable.
for (int i=0; i < values.length; i++)
sum += values;
_sum = sum;
}
private int _sum;
}
16.用移位代替2的幂次方的乘除
17.不要用引入匿名类代替接口的实现
package examples.rules.j2me;
public class ACII extends Frame {
private TextField text;
public ACII() {
add(text = new TextField(), "Center");
pack();
setVisible(true);
text.addActionListener(new ActionListener() { // VIOLATION
public void actionPerformed(ActionEvent event) {
text.setText("");
}
});
}
}
REPAIR
The example code can be rewritten without the inner class as:
package examples.rules.j2me;
public class ACIIFixed extends Frame
implements ActionListener { // FIXED
private TextField text;
/**
* Creates a new example object.
*/
public ACIIFixed() {
add(text = new TextField(), "Center");
pack();
setVisible(true);
text.addActionListener(this); // FIXED
}
public void actionPerformed(ActionEvent event) {
text.setText("");
}
}
18.不要直接声明并初始化大的数组。
EXAMPLE For example, the expression int[] prime = {2, 3, 5, 7, 11}; produces
the following bytecode sequence:
0: iconst_5
1: newarray int
3: dup
4: iconst_0
5: iconst_2
6: iastore
7: dup
8: iconst_1
9: iconst_3
10: iastore
11: dup
12: iconst_2
13: iconst_5
14: iastore
15: dup
16: iconst_3
17: bipush 7
19: iastore
20: dup
21: iconst_4
22: bipush 11
24: iastore
25: astore_1
The initialization uses 4 instructions per value, but, depending on
the particular values, usually more than 4 bytes per value.
19.不要返回对象实例,而要返回一个索引。
EXAMPLE package examples.rules.j2me;
import java.awt.Dimension;
public class EURP {
public static Dimension getSizeOfRectangle(int x1, int y1, int x2, int y2) {
return new Dimension(Math.abs(x1-x2), Math.abs(y1-y2)); // VIOLATION
}
}
REPAIR
With a re-usable return object, the above code can be rewritten as follows:
package examples.rules.j2me;
import java.awt.Dimension;
public class EURPFixed {
public static Dimension getSizeOfRectangle(int x1, int y1, int x2, int y2, Dimension returnValue) {
returnValue.width = Math.abs(x1-x2);
returnValue.height = Math.abs(y1-y2);
return returnValue; // FIXED
}
}
20.对大数组的初始化,最好检查OutOfMemoryError的异常。
网易博客安全提醒:系统检测到您当前密码的安全性较低,为了您的账号安全,建议您适时修改密码 立即修改 | 关闭
定义这个规范的目的是让项目中所有的文档都看起来像一个人写的,增加可读性,减少项目组中因为换人而带来的损失。(这些规范并不是一定要绝对遵守,但是一定要让程序有良好的可读性)
主要分四个部分,1、命名规范,对于临时变量,可以采用短变量名提高效率,对于成员变量,尽量从变量名上体现其含义。2、文件样式规范,主要关注正确的注释填写。3、代码编写格式,这个可以通过一些工具来实现。比如jalopy。4。程序编写规范, 出于减容以及提速的考虑,具体编程中的一些小技巧和注意点,需要注意的是,往往忠孝不能两全,规则本身会有冲突的地方,熊掌和鱼,根据系统需求决定取舍。
命名规范 一般不要用package,浪费。
Class 的命名
Class 的名字必须由大写字母开头而其它字母都小写的单词组成
Class 变量的命名
变量的名字必须用一个小写字母开头。后面的单词用大写字母开头。
Static Final 变量的命名
Static Final 变量的名字应该都大写,并且指出完整含义。
参数的命名
参数的名字必须和变量的命名规范一致。
数组的命名
数组应该总是用下面的方式来命名:
byte[] buffer;
而不是:
byte buffer[];
方法的参数
使用有意义的参数命名,如果可能的话,使用和要赋值的字段一样的名字:
1
2
3
4
SetCounter(int size)
{
this.size = size;
}
多层for循环循环变量的命名
最好采用能够体现其层数的循环变量名
如:
for(i=0;i<10;i++)
for(ii=0;ii<10;ii++)
for(iii=0;iii<10;iii++);
Java 文件样式 所有的 Java(*.java) 文件都必须遵守如下的样式规则
版权信息
版权信息必须在 java 文件的开头,比如:
/** * Copyright ? 2000 XXX Co. Ltd. * All right reserved. */
其它不需要出现在 javadoc 的信息也可以包含在这?。
Package/Imports
package 行要在 import 行之前,import 中标准的包名要在本地的包名之前,而且按照字母顺序排列。如果 import 行中包含了同一个包中的不同子目录,则应该用 * 来处理。 1
2
3
package hotlava.net.stats;
import java.io.*;
import java.util.Observable;import hotlava.util.Application;
这儿java.io.* 使用来代替InputStream and OutputStream 的。
Class
接下来的是类的注释,一般是用来解释类的。 1
/** * A class representing a set of packet and byte counters * It is observable to allow it to be watched, but only * reports changes when the current set is complete */
接下来是类定义,包含了在不同的行的 extends 和 implements 1
publicclass CounterSet extends Observable implements Cloneable
Class Fields
接下来是类的成员变量: 1
2
3
/** * Packet counters */
protectedint[] packets;
public 的成员变量必须生成文文件(JavaDoc)。proceted、private和 package 定义的成员变量如果名字含义明确的话,可以没有注释。
存取方法
接下来是类变量的存取的方法。它只是简单的用来将类的变量赋值获取值的话,可以简单的写在一行上。 1
2
3
4
5
6
7
8
9
10
11
12
13
/** * Get the counters * @return an array containing the statistical data. This array has been * freshly allocated and can be modified by the caller. */publicint[] getPackets() {
return copyArray(packets, offset);
}
publicint[] getBytes() {
return copyArray(bytes, offset);
}
publicint[] getPackets() {
return packets;
}
publicvoid setPackets(int[] packets) {
this.packets = packets;
}
其它的方法不要写在一行上
构造函数
接下来是构造函数,它应该用递增的方式写(比如:参数多的写在后面)。
访问类型 ("public", "private" 等.) 和任何 "static", "final" 或 "synchronized" 应该在一行中,并且方法和参数另写一行,这样可以使方法和参数更易读。 1
2
3
4
public CounterSet
(int size){
this.size = size;
}
类方法
下面开始写类的方法: 1
2
3
4
5
6
7
8
/** * Set the packet counters * (such as when restoring from a database) */
protected finalvoid setArray(int[] r1, int[] r2, int[] r3, int[] r4) throws IllegalArgumentException
{
// // Ensure the arrays are of equal size //
if (r1.length != r2.length || r1.length != r3.length || r1.length != r4.length)
thrownew IllegalArgumentException("Arrays must be of the same size"); System.arraycopy(r1, 0, r3, 0, r1.length);
System.arraycopy(r2, 0, r4, 0, r1.length);
}
代码编写格式 下列规范可以省去,可以用eclipse插件来达到目的。
代码样式
代码应该用 unix 的格式,而不是 windows 的(比如:回车变成回车+换行)
文档化
必须用 javadoc 来为类生成文档。不仅因为它是标准,这也是被各种 java 编译器都认可的方法。
缩进
缩进应该是每行4个空格. 不要在源文件中保存Tab字符. 在使用不同的源代码管理工具时Tab字符将因为用户设置的不同而扩展为不同的宽度.
如果你使用 UltrEdit 作为你的 Java 源代码编辑器的话,你可以通过如下操作来禁止保存Tab字符, 方法是通过 UltrEdit中先设定 Tab 使用的长度室4个空格,然后用 Format|Tabs to Spaces 菜单将 Tab 转换为空格。
页宽
页宽应该设置为80字符. 源代码一般不会超过这个宽度, 并导致无法完整显示, 但这一设置也可以灵活调整. 在任何情况下, 超长的语句应该在一个逗号或者一个操作符后折行. 一条语句折行后, 应该比原来的语句再缩进2个字符.
{} 对
{} 中的语句应该单独作为一行. 例如, 下面的第1行是错误的, 第2行是正确的: 1
2
if (i>0) { i ++ };
// 错误, { 和 } 在同一行
1
2
3
4
if (i>0) {
i ++
};
// 正确, { 单独作为一行 } 语句永远单独作为一行.
如果 } 语句应该缩进到与其相对应的 { 那一行相对齐的位置。
括号
左括号和后一个字符之间不应该出现空格, 同样, 右括号和前一个字符之间也不应该出现空格. 下面的例子说明括号和空格的错误及正确使用: 1
2
CallProc( AParameter ); // 错误
CallProc(AParameter); // 正确
不要在语句中使用无意义的括号. 括号只应该为达到某种目的而出现在源代码中。下面的例子说明错误和正确的用法: 1
2
if ((I) = 42) {// 错误 - 括号毫无意义
if (I == 42) or (J == 42) then // 正确 - 的确需要括号
程序编写规范 1. new 关键字,如果要用,尽量在loading阶段,此时系统内存较多空闲的时候。不要用new string(“aaaa”);
2. 对流对象的使用,一定要保证在任何情况下都能关闭,如,在finally{}中关闭。
3. 调用getClass()方法时,尽可能利用现存的static的对象实例,而不是新建一个对象实例再调用。
4. 用System.arraycopy ()拷贝数组,而不是用for循环拷贝。
5. 对于非常量的字符串,用stringbuffer而不是string
6. 对常量字符串,用string而不是stringbuffer。
7. 对于简单if else的优化,如:
if( a==b)
{
return true;
}
else
{
return false;
}
直接 return (a==b);
8. 尽量不要在for循环()内调用方法。同样,将vector.size()变为 array.length,同样的处理方法。
EXAMPLE package examples.rules.opt;
import java.util.Vector;
public class CEL {
void method (Vector vector) {
for (int i = 0; i < vector.size (); i++) // VIOLATION
{
System.out.println(i);
}
}
}
REPAIR
If the method returns a value that will not change during the loop, store
its value in a temporary variable before the loop.
package examples.rules.opt;
import java.util.Vector;
public class CELFixed {
void method (Vector vector) {
int size = vector.size ();
for (int i = 0; i < size; i++) // FIXED
{
System.out.println(i);
}
}
}
9. 要在循环{}内声明新的变量名:
EXAMPLE package examples.rules.opt;
public class LOOP {
public void method(int max) {
for (int i = 0; i < max; i++) {
StringBuffer sb = new StringBuffer(); // VIOLATION
sb.append("loop: ");
sb.append(i);
System.out.println(sb.toString());
}
}
}
REPAIR
Declare the variable outside of the loop and reuse it.
package examples.rules.opt;
public class LOOPFixed {
public void method(int max) {
StringBuffer sb = new StringBuffer(); // FIXED
for (int i = 0; i < max; i++) {
sb.append("loop: ");
sb.append(i);
System.out.println(sb.toString());
sb.setLength(0); // FIXED
}
}
}
10. Final的成员变量最好加上static。
package examples.rules.opt;
public class NSF {
final int magicNumber= 0; //VIOLATION
}
REPAIR
package examples.rule.opt;
public class NSFFixed {
static final in magicNumber= 0; //FIXED
}
11.对于字符串中单个字符的比较,用 'charAt()' 而不是 'startsWith()'
package examples.rules.opt;
public class PCTS {
private void method(String s) {
if (s.startsWith("a")) // VIOLATION
System.out.println("starts with a.");
}
}
REPAIR
Replace 'startsWith()' with a test for non-zero length and 'charAt(0)'.
package examples.rules.opt;
public class PCTSFixed {
private void method(String s) {
if (s.length () > 0 && s.charAt(0) == 'a') // FIXED
System.out.println("starts with a.");
}
}
12. stringbuffer最好指定初始容量。
package examples.rules.opt;
public class SB {
void method () {
StringBuffer buffer = new StringBuffer(); // VIOLATION
buffer.append ("hello");
}
}
REPAIR
Provide an initial capacity for the 'StringBuffer'.
package examples.rules.opt;
public class SBFixed {
void method () {
StringBuffer buffer = new StringBuffer(SBSIZE); // FIXED
buffer.append ("hello");
}
private static final int SBSIZE = 100;
}
13. 对于字符串相加,其中的单个字符用’’而不用””
EXAMPLE package examples.rules.opt;
public class STR {
public String method(String s) {
String string = null;
string = s + "d"; // VIOLATION
return string;
}
}
REPAIR
Instead of concatenating one-character String constants, replace them with
character constants.
package examples.rules.opt;
public class STRFixed {
public String method(String s) {
String string = null;
string = s + 'd'; // FIXED
return string;
}
}
14. "try/catch/finally" 块要放在循环体外面
15.尽量用栈变量而不是实例变量和类变量,可以提高速度。
EXAMPLE package examples.rules.opt;
public class USV {
void addSum (int[] values) {
for (int i=0; i < values.length; i++) // VIOLATION
_sum += values;
}
private int _sum;
}
REPAIR
If possible, use local variables when accessing variables frequently.
For example you could use a local temporary variable as in:
package examples.rules.opt;
public class USVFixed {
void addSum (int[] values) {
int sum = _sum; // FIXED: temporary local variable.
for (int i=0; i < values.length; i++)
sum += values;
_sum = sum;
}
private int _sum;
}
16.用移位代替2的幂次方的乘除
17.不要用引入匿名类代替接口的实现
package examples.rules.j2me;
public class ACII extends Frame {
private TextField text;
public ACII() {
add(text = new TextField(), "Center");
pack();
setVisible(true);
text.addActionListener(new ActionListener() { // VIOLATION
public void actionPerformed(ActionEvent event) {
text.setText("");
}
});
}
}
REPAIR
The example code can be rewritten without the inner class as:
package examples.rules.j2me;
public class ACIIFixed extends Frame
implements ActionListener { // FIXED
private TextField text;
/**
* Creates a new example object.
*/
public ACIIFixed() {
add(text = new TextField(), "Center");
pack();
setVisible(true);
text.addActionListener(this); // FIXED
}
public void actionPerformed(ActionEvent event) {
text.setText("");
}
}
18.不要直接声明并初始化大的数组。
EXAMPLE For example, the expression int[] prime = {2, 3, 5, 7, 11}; produces
the following bytecode sequence:
0: iconst_5
1: newarray int
3: dup
4: iconst_0
5: iconst_2
6: iastore
7: dup
8: iconst_1
9: iconst_3
10: iastore
11: dup
12: iconst_2
13: iconst_5
14: iastore
15: dup
16: iconst_3
17: bipush 7
19: iastore
20: dup
21: iconst_4
22: bipush 11
24: iastore
25: astore_1
The initialization uses 4 instructions per value, but, depending on
the particular values, usually more than 4 bytes per value.
19.不要返回对象实例,而要返回一个索引。
EXAMPLE package examples.rules.j2me;
import java.awt.Dimension;
public class EURP {
public static Dimension getSizeOfRectangle(int x1, int y1, int x2, int y2) {
return new Dimension(Math.abs(x1-x2), Math.abs(y1-y2)); // VIOLATION
}
}
REPAIR
With a re-usable return object, the above code can be rewritten as follows:
package examples.rules.j2me;
import java.awt.Dimension;
public class EURPFixed {
public static Dimension getSizeOfRectangle(int x1, int y1, int x2, int y2, Dimension returnValue) {
returnValue.width = Math.abs(x1-x2);
returnValue.height = Math.abs(y1-y2);
return returnValue; // FIXED
}
}
20.对大数组的初始化,最好检查OutOfMemoryError的异常。
Tags: java
ASP.NETAjax客户组件[2011年02月25日]
作者: 日期:2012-05-10
沉沙 的博客
80后90初的我们已经渐渐的老去,是该为我们的青春留下点什么的时候了 Microsoft Ajax Library提供了一个客户组件模型。 服务器组件派生自:System.ComponentModel.Component 客户组件派生自:Sys.Component. 在MicrosoftAjax.js文件中,Sys.Component类的注册如下: Sys.Component.reisterClass(‘Sys.Component’,null,Sys.IDisposable,Sys.INotifyPropertyChange,Sy s.INotifyDisposing); Sys.Component类实现的接口如下:
(1) IDisposable:定义了一个dispose方法,作用是释放组件所使用的资源。
(2)INotifyPropertyChange:通过属性公布的值发生改变时,允许组件产生一个事件。可以利用绑定等特性实现相同或不同组件中的两个属性值的同步。
(3)INotifyDisposing:允许组件产生一个dispose事件来通知外部对象:它正在释放资源。客户组件可以产生事件。 客户组件分为非可视化和可视化两类。非可视化不提供UI。在Web页面中UI采用HTML代码定义。
可视化称为控件。在客户端可以创建一个控件或一个行为。
非可视化组件由基类:Sys.Component创建。
可视化组件由基类:Sys.UI.Behavior(行为)和Sys.UI.Control(控件)类创建。 控件与行为都与页面中的DOM元素关联,并提供了类似的特性。
行为:能改进DOM元素,但不能改变DOM元素提供的基本功能。
控件:创建元素包装器。
服务器控件与客户控件的区别:服务器控件包装的是服务器端DOM元素,而不是客户端元素。
行为与控件之间的区别:一个DOM元素可以有多个行为,不过只能与一个且仅一个控件关联。行为最适合采用一种递增方式为DOM元素增加客户功能。控件则用于为关联元素提供全部客户功能。 组件是能够封装其他对象和子组件的复杂对象。
组件的生命周期包括两个阶段:初始化和撤销。初始化:从创建组件时开始;撤销:从内存删除一个组件实例时。初始化方法:Initialize();撤销方法:Dispose(): 容器:包括一组子组件并为这些组件提供服务的对象。容器包括增加、删除和访问子组件的方法。容器实现接口:Sys.IContainer接口。
Sys._Application类就是一个容器,它只有一个实例:Application对象。
Application对象的目标之一是维护和跟踪页面中实例化的客户组件。
将组件包括在容器中的优点:可以通过容器获得客户组件的引用;页面被浏览器卸载时这些组件会由容器自动撤销。
客户组件在创建时会自动成为Application对象的子组件。
在处理组件之前,需要理解组件生命周期与客户页面周期之间存在的关系:
8.2处理客户组件
例1-1编写客户端脚本如下: <script type ="text/javascript"> Type.registerNamespace ('Samples'); Samples.TrivialComponent=function (){ Samples.TrivialComponent.initializeBase(this); } Samples.TrivialComponent.prototype={ //要介入组件的生命周期,需要覆写构造函数原型对象中的initialize、dispose
initialize:function ()
{Samples.TrivialComponent.callBaseMethod(this ,'initialize');
alert ('正在初始化(initialize)');
},
dispose:function (){
alert ('正在处理(dispose)') Samples.TrivialComponent.callBaseMethod(this,'disp ose');
},
//定义greet方法。用于显示信息。 //说明组件也可以声明方法。
greet:function (){
alert ("Hello,我是第一个组件");
}
}
Samples.TrivialComponent.registerClass('Samples.Tr ivialComponent',Sys .Component )
//组件派生自Sys .Component.说明是一个客户组件.
Sys .Application .add_init (pageInit);
function pageInit()
{
//实例化组件
//与JavaScript对象不同:不只用new操作符使用create方法;
//create方法:能完成与组件创建过程有关的所有任务。
//create方法:在客户页面生命周期的初始化阶段调用。
//使用create方法创建的组件将放在Application容器中。
//新创建的组件类型为:Samples.TrivialComponent
//id属性值为:trivialComponent
$create (Samples.TrivialComponent,{'id':'trivialComponent' });
}
function pageLoad(){
var trivialComponent=$find ('trivialComponent');
//find方法:用于访问Application对象的一个子组件。
trivialComponent.greet();
}
</script> Microsoft Ajax Library定义的一些别名 别名 完整名称 完成任务 $get Sys.UI.DemElement.getElementById 返回一个DOM元素的引用 $create Sys.Component.create 创建配置和初始化一个ASP.NET Ajax客户组件的实例 $find Sys.Application.findComponent 返回一个组件的引用 8.2.1创建组件
创建组件为什么用create而不只只用new?创建组件时只创建实例还不够,因为组件需要初始化,还要增加为Application对象的一个子组件。
创建一个新实例的代码: var trivialComponent=new Samples.TrivialComponent(); trivialComponent.set_id(‘trivialComponent’); trivialComponent.initialize(); Sys.Application.addComponent(trivialComponent); 由些可见:创建一个新实例需要以下几个过程:
(1)第一步:new创建Samples.TrivialComponent实例。
(2)调用initialize方法,使组件完成其内部初始化。
(3)调用Sys.Application的addComponent方法,将这个新实例增加为Application对象的一个子组件。
$create方法,它会自动完成整个过程。
$create方法原型:
$create(”类名”,{属性例表},{事件例表},{引用例表},$get(关联元素))
参数说明:
“类名”:需要实例化的组件的完全限定名称。这个组件必须派生自Sys.Component,否则出错。
“$get(关联元素)”:与之关联的DOM元素,可视化组件必须参数,而非可视化组件则不是必须的。
其它参数:在实例化之后且初始化前用于配置组件的对象,它们作为对象字面量传入。
{属性例表}:以名/值对方式为属性赋值。
{事件例表}: 将事件名称与处理程序以名/值对方式进行映射。
{引用例表}:将组件的属性名称映射到另一个组件的ID。
$create方法的特点:
(1)提供了一种简明的记法来完成与组件实例化、配置和初始化相关的全部工作。
(2)使用方法会增加一些开销。
(3)ASP.NET Ajax将客户组件与服务器控件关联时就使用了$create方法。 8.2.2访问组件
一旦客户组件得到正确的实例化,并增加到一个容器,就可以$find方法根据ID来访问这个组件。每个组件都暴露了ID属性。
var instance=$find(‘组件ID’,容器对象)
容器对象若省略:则为Sys.Application对象。容器对象为实现IContainer的实例。
只有当已经为组件指定了ID而且组件已经增加到一个容器时$find才能正常工作。 8.2.3事件和属性改变
回顾:在客户端中暴露事件所必须的三个步骤:
(1)创建一个方法为事件增加处理程序
(2)创建一个方法为事件删除处理程序
(3)创建一个方法负责产生事件。
这个过程同样适用于客户端组件暴露事件:唯一区别在于,不需要在构造函中保存Sys.EventHandlerList类的一个实例。
因为每个组件都由基类Sys.Component继承这个实例,此外,每个组件还继承了get_events方法来访问Sys.EventHandlersList实例。
客户组件有个propertyChanged事件,只要一个属性的值有改变就会产生这个事件。
例2:显示客户端组件事件与属性的处理 <script type ="text/javascript"> Type.registerNamespace ('Samples'); Samples.Customer=function () { Samples.Customer.initializeBase(this); this._fullName; } Samples.Customer.prototype={ get_fullName:function (){ return this._fullName; }, set_fullName:function (value){ if(value !=this._fullName) {this._fullName=value ;} this .raisePropertyChanged('fullName'); //产生事件PropertyChanged
} } Samples.Customer.registerClass('Samples.Customer', Sys.Component); function pageLoad(sender,e) { var customer=new Samples.Customer(); customer .add_propertyChanged(onPropertyChanged ); //为PropertyChanged事件添加处理程序。
customer.set_fullName("Lily");
}
function onPropertyChanged(sender ,e)
{ if(e.get_propertyName()=='fullName')
{ alert ("一个新值"+sender.get_fullName());}
}
</script> 8.3行为
行为:元素行为是一些封装组件,可以为Web页面增加有意思的新功能,同时改进内容、功能和样式的组件。可以使用行为来改进DOM元素的功能。 8.3.1Sys.UI.Behavior
行为:是一个派生自基类Sys.UI.Behavior的客户类。而Sys.UI.Behavior继承自Sys.Component。
行为是可视化组件,因为它们总是与一个DOM元素关联。创建行为的一个新实例时,要向构造函数传入这个元素。
行为的创建与非可视化组件基本上是一样的。 8.3.2创建行为
Type .registerNamespace ('Samples'); Samples.EmptyBehavior=function (element){ Samples.EmptyBehavior.initializeBase(this,[element ]); } Samples.EmptyBehavior.prototype={ initialize:function (){ Samples.EmptyBehavior.callBaseMethod(this,'initial ize'); }, dispose:function () { Samples.EmptyBehavior.callBaseMethod(this,'dispose ');} } Samples.EmptyBehavior.registerClass('Samples.Empty Behavior',Sys .UI.Behavior ); 理解语法,无需运行。 8.3.3访问行为
$create (Samples.EmptyBehavior,{'name':'myemptybehavior'}, {},{},$get('elementID')); 理解语法,无需运行。 var instance=$find ('someElement$myemptybehavior'); 为EmptyBehavior类的对象Id 为someElement的行为实例的name属性设置为myemptybehavior。 Var emptyBehaviorInstance= $get ('someElement').myEmptyBehavior; 如果设置了name属性,可以通过增加到关联元素的一个属性来访问行为。这个属性是基类在初始化行为的新实例时增加的,它与行为同名。 8.3.4改进文本框元素:
示例功能说明:输入域的样式类似于标签。用户将鼠标停在文本框上时,文本框的样式会改变,提示用户这是一个输入域。本示例创建一个客户端行为,对于一个DOM元素(TextBox)的事件,采用编程方式改变与这个DOM元素关联的CSS类。
例3-1:定义行为 <script type ="text/javascript"> Type.registerNamespace ('Samples'); Samples.FormattingBehavior=function (element) { Samples.FormattingBehavior.initializeBase(this,[el ement]); this._hoverCssClass=null ; this._focusCssClass=null ; this._currentCssClass=null ; this._mouseOver=null ; this._focus=null ; } Samples.FormattingBehavior.prototype={ initialize:function (){ Samples.FormattingBehavior.callBaseMethod(this,'in itialize'); $addHandlers (this .get_element(), {mouseout:this._onMouseout, mouseover:this._onMouseover, focus:this._onFocus, blur:this._onBlur}, this ); }, dispose:function (){ $clearHandlers (this.get_element()); Samples.FormattingBehavior.callBaseMethod(this,'di spose'); }, _onMouseover:function (){ this._mouseOver=true ; this._setCssClass(); }, _onMouseout:function (){ this._mouseOver=false; this._setCssClass(); }, _onFocus:function (){ this._focus=true ; this._setCssClass(); }, _onBlur:function (){ this._focus=false ; this._setCssClass(); }, _setCssClass:function (){ if(this._currentCssClass) { Sys.UI.DomElement .removeCssClass (this._element,this._currentCssClass); this._currentCssClass=null ; } if(this._error) { this._currentCssClass=this._errorCssClass;} else if(this._focus) {this._currentCssClass =this._focusCssClass;} else if(this._mouseOver) {this._currentCssClass =this._hoverCssClass;} if(this._currentCssClass) { Sys .UI .DomElement .addCssClass (this._element,this._currentCssClass); } }, get_hoverCssClass:function (){ return this._hoverCssClass; }, set_hoverCssClass:function (value){ this._hoverCssClass=value ; }, get_focusCssClass:function (){ return this._focusCssClass; }, set_focusCssClass:function (value){ this._focusCssClass=value ; } } Samples.FormattingBehavior.registerClass('Samples. FormattingBehavior',Sys .UI.Behavior ); </script> 例3-2创建CSS文件 .form div { margin-bottom:5px;} .field_hover {border : 1px;} .field_focus { border : 5px; } 例3-3 在程序中引用CSS <head runat="server"> <link href="Behavior.css" rel="stylesheet" type="text/css" /></head> 例3-4创建文本框,并为其添加行为 Sys .Application .add_init (pageInit); function pageInit(sender,e) { $create (Samples.FormattingBehavior, { 'hoverCssClass':'field_hover', 'focusCssClass':'field_focus' }, {},{},$get ('txtName')); $create (Samples.FormattingBehavior, {'hoverCssClass':'field_hover', 'focusCssClass':'field_focus' }, {},{},$get ('txtLastName')); } 例3-5 运行程序。理解行为。
8.4控件
控件也是与DOM元素关联的可视化组件。它不仅提示客户功能,还用来表示元素,提供额外的属性与方法,从而扩展用途。
8.4.1Sys.UI.Control
控件是派生自Sys.UI.Control的一个客户类。而Sys.UI.Control是Sys.Component类的一个子类。
控件有一个关联DOM元素, 这个关联DOM元素要在控件实例化期间传递给构造函数,并利用get_element方法返回。
定义控件语法:理解语法,不可运行。 Type .registerNamespace ('Samples'); Samples.EmptyControl=function (element){ Samples. EmptyControl.initializeBase(this ,[element]); } Samples. EmptyControl.prototype={ initialize:function (){ Samples. EmptyControl.callBaseMethod(this,'initialize'); }, dispose:function (){ Samples. EmptyControl.callBaseMethod(this ,'dispose'); } } Samples. EmptyControl.registerClass('Samples. EmptyControl,Sys .UI .Control); 8.4.2 创建控件
作为客户组件,控件也在客户端页面生命周期的初始化阶段用$create语句创建。控件必须与一个DOM元素关联。 $create (Samples.Button,{},{},{},$get (关联控件ID)); 8.4.3访问控件
一.可以通过$find方法访问。
二. 通过关联元素来访问。元素可有且仅有一个关联控件,初始化时会在DOM元素上创建一个名为control属性,其中存储控件的引用。
var controlInstance=someElement.control;
例4-1控件示例:实现在原按钮功能基础上,不回调。即取消原回调功能。
创建界面:注意:没有UpdatePanel
代码如下:运行程序,两个按钮都具有回调功能。以理解后续代码作用。 protected void Page_Load(object sender, EventArgs e) { Label1.Text = DateTime.Now.ToString();} protected void Button2_Click(object sender, EventArgs e) { Label1.Text = DateTime.Now.ToString();} protected void Button1_Click(object sender, EventArgs e) { Label1.Text = DateTime.Now.ToString();} 例4-2客户端代码如下: <script type ="text/javascript"> Type .registerNamespace ('Samples'); Samples.Button=function (element){ Samples.Button.initializeBase(this ,[element]); } Samples.Button.prototype={ initialize:function (){ Samples.Button.callBaseMethod(this,'initialize'); $addHandlers (this.get_element(),{click:this._onKeyPress},this) ; }, dispose:function (){ $clearHandlers (this .get_element()); Samples.Button.callBaseMethod(this ,'dispose'); }, _onKeyPress:function (evt){ evt.preventDefault(); //不执行事件的默认动作。
}
} Samples.Button.registerClass('Samples.Button',Sys .UI .Control);
Sys .Application .add_init(pageInit);
function pageInit()
{ $create (Samples.Button,{},{},{},$get ('Button1')); }
</script> 运行程序,第一个按钮不再进行回调,理解程序。
8.4.5创建一个PhotoGallery(图片库)
许多项目中需要将多个不同的元素组成。这就需要将客户控件与复杂标记相关联。解决办法:将各种复杂标记代码嵌在一个容器元素中(如div或span),再使用这个容器为客户控件的关联元素,然后再访问容器控件中的子元素。
例5-1设置界面如下:
例5-2属性设置如下: <div style="width: 375px" id="photoGallery"> <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager> <input id="gal_prevButton" type="button" value="上一个" />
<input id="gal_nextButton" type="button" value="下一个" /><br />
<img id="gal_image" alt="" style="width: 360px; height: 207px" />
</div> 在应用程序中创建文件夹:Images并放入相应图片。
例5-3客户端代码如下: <script type ="text/javascript"> Type.registerNamespace('Samples'); Samples.PhotoGallery=function (element){ Samples.PhotoGallery.initializeBase(this ,[element]); this._imageElement=null ; this._nextElement=null ; this._prevElement=null ; this._images=[]; this._index=-1; } Samples.PhotoGallery.prototype={ initialize:function (){ Samples.PhotoGallery.callBaseMethod(this ,'initialize'); $addHandlers (this._nextElement,{click:this.viewNext},this); $addHandlers(this._prevElement,{click:this.viewPre v},this); this._imgPreload=document.createElement('IMG'); $addHandlers(this._imgPreload,{load:this._onimageE lementLoaded},this ); if(this._index>=0){this._render();} }, dispose:function (){ $clearHandlers (this._prevElement); $clearHandlers (this._nextElement); $clearHandlers (this._imgPreload); Samples.PhotoGallery.callBaseMethod(this ,'dispose'); }, viewPrev:function (evt){ if(this._index>0){this._index--;this._render();} }, viewNext:function (evt){ if(this._index<this._images.length-1){ this._index++;this._render(); } }, _render:function (){ this._prevElement.disabled=(this._index==0); this._nextElement.disabled=(this._index==this._ima ges.length-1); this._imageElement.style.visibility='visible'; this._imageElement.src=this._images[this._index];/ /this._images[this._index]; }, _onimageElementLoaded:function (){ this._displayImage(); }, _displayImage:function (){ this._imageElement.style.visibility='hidden'; this._imageElement.scr=this._images[this._index]; }, get_images:function (){ return this._images; }, set_images:function (value){ this._images=value ; if(this._images .length>0){ this._index=0; if(this.get_isInitialized()) {this._render();} } }, get_prevElement:function (){ return this._prevElement; }, set_prevElement:function (value){ this._prevElement=value ; }, get_nextElement:function (){ return this._nextElement; }, set_nextElement:function (value){ this._nextElement=value ; }, get_imageElement:function (){ return this._imageElement; }, set_imageElement:function (value){ this._imageElement=value ; } } Samples.PhotoGallery.registerClass('Samples.PhotoG allery',Sys .UI .Control); Sys .Application .add_init (pageInit ); function pageInit(sender,e) { $create (Samples.PhotoGallery, {'imageElement':$get ('gal_image'), 'prevElement':$get ('gal_prevButton'), 'nextElement':$get ('gal_nextButton'), 'images':['Images/1.jpg','Images/2.jpg','Images/3. jpg','Images/4.jpg','Images/5.jpg'] }, {},{},$get ('photoGallery') ); } </script>
80后90初的我们已经渐渐的老去,是该为我们的青春留下点什么的时候了 Microsoft Ajax Library提供了一个客户组件模型。 服务器组件派生自:System.ComponentModel.Component 客户组件派生自:Sys.Component. 在MicrosoftAjax.js文件中,Sys.Component类的注册如下: Sys.Component.reisterClass(‘Sys.Component’,null,Sys.IDisposable,Sys.INotifyPropertyChange,Sy s.INotifyDisposing); Sys.Component类实现的接口如下:
(1) IDisposable:定义了一个dispose方法,作用是释放组件所使用的资源。
(2)INotifyPropertyChange:通过属性公布的值发生改变时,允许组件产生一个事件。可以利用绑定等特性实现相同或不同组件中的两个属性值的同步。
(3)INotifyDisposing:允许组件产生一个dispose事件来通知外部对象:它正在释放资源。客户组件可以产生事件。 客户组件分为非可视化和可视化两类。非可视化不提供UI。在Web页面中UI采用HTML代码定义。
可视化称为控件。在客户端可以创建一个控件或一个行为。
非可视化组件由基类:Sys.Component创建。
可视化组件由基类:Sys.UI.Behavior(行为)和Sys.UI.Control(控件)类创建。 控件与行为都与页面中的DOM元素关联,并提供了类似的特性。
行为:能改进DOM元素,但不能改变DOM元素提供的基本功能。
控件:创建元素包装器。
服务器控件与客户控件的区别:服务器控件包装的是服务器端DOM元素,而不是客户端元素。
行为与控件之间的区别:一个DOM元素可以有多个行为,不过只能与一个且仅一个控件关联。行为最适合采用一种递增方式为DOM元素增加客户功能。控件则用于为关联元素提供全部客户功能。 组件是能够封装其他对象和子组件的复杂对象。
组件的生命周期包括两个阶段:初始化和撤销。初始化:从创建组件时开始;撤销:从内存删除一个组件实例时。初始化方法:Initialize();撤销方法:Dispose(): 容器:包括一组子组件并为这些组件提供服务的对象。容器包括增加、删除和访问子组件的方法。容器实现接口:Sys.IContainer接口。
Sys._Application类就是一个容器,它只有一个实例:Application对象。
Application对象的目标之一是维护和跟踪页面中实例化的客户组件。
将组件包括在容器中的优点:可以通过容器获得客户组件的引用;页面被浏览器卸载时这些组件会由容器自动撤销。
客户组件在创建时会自动成为Application对象的子组件。
在处理组件之前,需要理解组件生命周期与客户页面周期之间存在的关系:
8.2处理客户组件
例1-1编写客户端脚本如下: <script type ="text/javascript"> Type.registerNamespace ('Samples'); Samples.TrivialComponent=function (){ Samples.TrivialComponent.initializeBase(this); } Samples.TrivialComponent.prototype={ //要介入组件的生命周期,需要覆写构造函数原型对象中的initialize、dispose
initialize:function ()
{Samples.TrivialComponent.callBaseMethod(this ,'initialize');
alert ('正在初始化(initialize)');
},
dispose:function (){
alert ('正在处理(dispose)') Samples.TrivialComponent.callBaseMethod(this,'disp ose');
},
//定义greet方法。用于显示信息。 //说明组件也可以声明方法。
greet:function (){
alert ("Hello,我是第一个组件");
}
}
Samples.TrivialComponent.registerClass('Samples.Tr ivialComponent',Sys .Component )
//组件派生自Sys .Component.说明是一个客户组件.
Sys .Application .add_init (pageInit);
function pageInit()
{
//实例化组件
//与JavaScript对象不同:不只用new操作符使用create方法;
//create方法:能完成与组件创建过程有关的所有任务。
//create方法:在客户页面生命周期的初始化阶段调用。
//使用create方法创建的组件将放在Application容器中。
//新创建的组件类型为:Samples.TrivialComponent
//id属性值为:trivialComponent
$create (Samples.TrivialComponent,{'id':'trivialComponent' });
}
function pageLoad(){
var trivialComponent=$find ('trivialComponent');
//find方法:用于访问Application对象的一个子组件。
trivialComponent.greet();
}
</script> Microsoft Ajax Library定义的一些别名 别名 完整名称 完成任务 $get Sys.UI.DemElement.getElementById 返回一个DOM元素的引用 $create Sys.Component.create 创建配置和初始化一个ASP.NET Ajax客户组件的实例 $find Sys.Application.findComponent 返回一个组件的引用 8.2.1创建组件
创建组件为什么用create而不只只用new?创建组件时只创建实例还不够,因为组件需要初始化,还要增加为Application对象的一个子组件。
创建一个新实例的代码: var trivialComponent=new Samples.TrivialComponent(); trivialComponent.set_id(‘trivialComponent’); trivialComponent.initialize(); Sys.Application.addComponent(trivialComponent); 由些可见:创建一个新实例需要以下几个过程:
(1)第一步:new创建Samples.TrivialComponent实例。
(2)调用initialize方法,使组件完成其内部初始化。
(3)调用Sys.Application的addComponent方法,将这个新实例增加为Application对象的一个子组件。
$create方法,它会自动完成整个过程。
$create方法原型:
$create(”类名”,{属性例表},{事件例表},{引用例表},$get(关联元素))
参数说明:
“类名”:需要实例化的组件的完全限定名称。这个组件必须派生自Sys.Component,否则出错。
“$get(关联元素)”:与之关联的DOM元素,可视化组件必须参数,而非可视化组件则不是必须的。
其它参数:在实例化之后且初始化前用于配置组件的对象,它们作为对象字面量传入。
{属性例表}:以名/值对方式为属性赋值。
{事件例表}: 将事件名称与处理程序以名/值对方式进行映射。
{引用例表}:将组件的属性名称映射到另一个组件的ID。
$create方法的特点:
(1)提供了一种简明的记法来完成与组件实例化、配置和初始化相关的全部工作。
(2)使用方法会增加一些开销。
(3)ASP.NET Ajax将客户组件与服务器控件关联时就使用了$create方法。 8.2.2访问组件
一旦客户组件得到正确的实例化,并增加到一个容器,就可以$find方法根据ID来访问这个组件。每个组件都暴露了ID属性。
var instance=$find(‘组件ID’,容器对象)
容器对象若省略:则为Sys.Application对象。容器对象为实现IContainer的实例。
只有当已经为组件指定了ID而且组件已经增加到一个容器时$find才能正常工作。 8.2.3事件和属性改变
回顾:在客户端中暴露事件所必须的三个步骤:
(1)创建一个方法为事件增加处理程序
(2)创建一个方法为事件删除处理程序
(3)创建一个方法负责产生事件。
这个过程同样适用于客户端组件暴露事件:唯一区别在于,不需要在构造函中保存Sys.EventHandlerList类的一个实例。
因为每个组件都由基类Sys.Component继承这个实例,此外,每个组件还继承了get_events方法来访问Sys.EventHandlersList实例。
客户组件有个propertyChanged事件,只要一个属性的值有改变就会产生这个事件。
例2:显示客户端组件事件与属性的处理 <script type ="text/javascript"> Type.registerNamespace ('Samples'); Samples.Customer=function () { Samples.Customer.initializeBase(this); this._fullName; } Samples.Customer.prototype={ get_fullName:function (){ return this._fullName; }, set_fullName:function (value){ if(value !=this._fullName) {this._fullName=value ;} this .raisePropertyChanged('fullName'); //产生事件PropertyChanged
} } Samples.Customer.registerClass('Samples.Customer', Sys.Component); function pageLoad(sender,e) { var customer=new Samples.Customer(); customer .add_propertyChanged(onPropertyChanged ); //为PropertyChanged事件添加处理程序。
customer.set_fullName("Lily");
}
function onPropertyChanged(sender ,e)
{ if(e.get_propertyName()=='fullName')
{ alert ("一个新值"+sender.get_fullName());}
}
</script> 8.3行为
行为:元素行为是一些封装组件,可以为Web页面增加有意思的新功能,同时改进内容、功能和样式的组件。可以使用行为来改进DOM元素的功能。 8.3.1Sys.UI.Behavior
行为:是一个派生自基类Sys.UI.Behavior的客户类。而Sys.UI.Behavior继承自Sys.Component。
行为是可视化组件,因为它们总是与一个DOM元素关联。创建行为的一个新实例时,要向构造函数传入这个元素。
行为的创建与非可视化组件基本上是一样的。 8.3.2创建行为
Type .registerNamespace ('Samples'); Samples.EmptyBehavior=function (element){ Samples.EmptyBehavior.initializeBase(this,[element ]); } Samples.EmptyBehavior.prototype={ initialize:function (){ Samples.EmptyBehavior.callBaseMethod(this,'initial ize'); }, dispose:function () { Samples.EmptyBehavior.callBaseMethod(this,'dispose ');} } Samples.EmptyBehavior.registerClass('Samples.Empty Behavior',Sys .UI.Behavior ); 理解语法,无需运行。 8.3.3访问行为
$create (Samples.EmptyBehavior,{'name':'myemptybehavior'}, {},{},$get('elementID')); 理解语法,无需运行。 var instance=$find ('someElement$myemptybehavior'); 为EmptyBehavior类的对象Id 为someElement的行为实例的name属性设置为myemptybehavior。 Var emptyBehaviorInstance= $get ('someElement').myEmptyBehavior; 如果设置了name属性,可以通过增加到关联元素的一个属性来访问行为。这个属性是基类在初始化行为的新实例时增加的,它与行为同名。 8.3.4改进文本框元素:
示例功能说明:输入域的样式类似于标签。用户将鼠标停在文本框上时,文本框的样式会改变,提示用户这是一个输入域。本示例创建一个客户端行为,对于一个DOM元素(TextBox)的事件,采用编程方式改变与这个DOM元素关联的CSS类。
例3-1:定义行为 <script type ="text/javascript"> Type.registerNamespace ('Samples'); Samples.FormattingBehavior=function (element) { Samples.FormattingBehavior.initializeBase(this,[el ement]); this._hoverCssClass=null ; this._focusCssClass=null ; this._currentCssClass=null ; this._mouseOver=null ; this._focus=null ; } Samples.FormattingBehavior.prototype={ initialize:function (){ Samples.FormattingBehavior.callBaseMethod(this,'in itialize'); $addHandlers (this .get_element(), {mouseout:this._onMouseout, mouseover:this._onMouseover, focus:this._onFocus, blur:this._onBlur}, this ); }, dispose:function (){ $clearHandlers (this.get_element()); Samples.FormattingBehavior.callBaseMethod(this,'di spose'); }, _onMouseover:function (){ this._mouseOver=true ; this._setCssClass(); }, _onMouseout:function (){ this._mouseOver=false; this._setCssClass(); }, _onFocus:function (){ this._focus=true ; this._setCssClass(); }, _onBlur:function (){ this._focus=false ; this._setCssClass(); }, _setCssClass:function (){ if(this._currentCssClass) { Sys.UI.DomElement .removeCssClass (this._element,this._currentCssClass); this._currentCssClass=null ; } if(this._error) { this._currentCssClass=this._errorCssClass;} else if(this._focus) {this._currentCssClass =this._focusCssClass;} else if(this._mouseOver) {this._currentCssClass =this._hoverCssClass;} if(this._currentCssClass) { Sys .UI .DomElement .addCssClass (this._element,this._currentCssClass); } }, get_hoverCssClass:function (){ return this._hoverCssClass; }, set_hoverCssClass:function (value){ this._hoverCssClass=value ; }, get_focusCssClass:function (){ return this._focusCssClass; }, set_focusCssClass:function (value){ this._focusCssClass=value ; } } Samples.FormattingBehavior.registerClass('Samples. FormattingBehavior',Sys .UI.Behavior ); </script> 例3-2创建CSS文件 .form div { margin-bottom:5px;} .field_hover {border : 1px;} .field_focus { border : 5px; } 例3-3 在程序中引用CSS <head runat="server"> <link href="Behavior.css" rel="stylesheet" type="text/css" /></head> 例3-4创建文本框,并为其添加行为 Sys .Application .add_init (pageInit); function pageInit(sender,e) { $create (Samples.FormattingBehavior, { 'hoverCssClass':'field_hover', 'focusCssClass':'field_focus' }, {},{},$get ('txtName')); $create (Samples.FormattingBehavior, {'hoverCssClass':'field_hover', 'focusCssClass':'field_focus' }, {},{},$get ('txtLastName')); } 例3-5 运行程序。理解行为。
8.4控件
控件也是与DOM元素关联的可视化组件。它不仅提示客户功能,还用来表示元素,提供额外的属性与方法,从而扩展用途。
8.4.1Sys.UI.Control
控件是派生自Sys.UI.Control的一个客户类。而Sys.UI.Control是Sys.Component类的一个子类。
控件有一个关联DOM元素, 这个关联DOM元素要在控件实例化期间传递给构造函数,并利用get_element方法返回。
定义控件语法:理解语法,不可运行。 Type .registerNamespace ('Samples'); Samples.EmptyControl=function (element){ Samples. EmptyControl.initializeBase(this ,[element]); } Samples. EmptyControl.prototype={ initialize:function (){ Samples. EmptyControl.callBaseMethod(this,'initialize'); }, dispose:function (){ Samples. EmptyControl.callBaseMethod(this ,'dispose'); } } Samples. EmptyControl.registerClass('Samples. EmptyControl,Sys .UI .Control); 8.4.2 创建控件
作为客户组件,控件也在客户端页面生命周期的初始化阶段用$create语句创建。控件必须与一个DOM元素关联。 $create (Samples.Button,{},{},{},$get (关联控件ID)); 8.4.3访问控件
一.可以通过$find方法访问。
二. 通过关联元素来访问。元素可有且仅有一个关联控件,初始化时会在DOM元素上创建一个名为control属性,其中存储控件的引用。
var controlInstance=someElement.control;
例4-1控件示例:实现在原按钮功能基础上,不回调。即取消原回调功能。
创建界面:注意:没有UpdatePanel
代码如下:运行程序,两个按钮都具有回调功能。以理解后续代码作用。 protected void Page_Load(object sender, EventArgs e) { Label1.Text = DateTime.Now.ToString();} protected void Button2_Click(object sender, EventArgs e) { Label1.Text = DateTime.Now.ToString();} protected void Button1_Click(object sender, EventArgs e) { Label1.Text = DateTime.Now.ToString();} 例4-2客户端代码如下: <script type ="text/javascript"> Type .registerNamespace ('Samples'); Samples.Button=function (element){ Samples.Button.initializeBase(this ,[element]); } Samples.Button.prototype={ initialize:function (){ Samples.Button.callBaseMethod(this,'initialize'); $addHandlers (this.get_element(),{click:this._onKeyPress},this) ; }, dispose:function (){ $clearHandlers (this .get_element()); Samples.Button.callBaseMethod(this ,'dispose'); }, _onKeyPress:function (evt){ evt.preventDefault(); //不执行事件的默认动作。
}
} Samples.Button.registerClass('Samples.Button',Sys .UI .Control);
Sys .Application .add_init(pageInit);
function pageInit()
{ $create (Samples.Button,{},{},{},$get ('Button1')); }
</script> 运行程序,第一个按钮不再进行回调,理解程序。
8.4.5创建一个PhotoGallery(图片库)
许多项目中需要将多个不同的元素组成。这就需要将客户控件与复杂标记相关联。解决办法:将各种复杂标记代码嵌在一个容器元素中(如div或span),再使用这个容器为客户控件的关联元素,然后再访问容器控件中的子元素。
例5-1设置界面如下:
例5-2属性设置如下: <div style="width: 375px" id="photoGallery"> <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager> <input id="gal_prevButton" type="button" value="上一个" />
<input id="gal_nextButton" type="button" value="下一个" /><br />
<img id="gal_image" alt="" style="width: 360px; height: 207px" />
</div> 在应用程序中创建文件夹:Images并放入相应图片。
例5-3客户端代码如下: <script type ="text/javascript"> Type.registerNamespace('Samples'); Samples.PhotoGallery=function (element){ Samples.PhotoGallery.initializeBase(this ,[element]); this._imageElement=null ; this._nextElement=null ; this._prevElement=null ; this._images=[]; this._index=-1; } Samples.PhotoGallery.prototype={ initialize:function (){ Samples.PhotoGallery.callBaseMethod(this ,'initialize'); $addHandlers (this._nextElement,{click:this.viewNext},this); $addHandlers(this._prevElement,{click:this.viewPre v},this); this._imgPreload=document.createElement('IMG'); $addHandlers(this._imgPreload,{load:this._onimageE lementLoaded},this ); if(this._index>=0){this._render();} }, dispose:function (){ $clearHandlers (this._prevElement); $clearHandlers (this._nextElement); $clearHandlers (this._imgPreload); Samples.PhotoGallery.callBaseMethod(this ,'dispose'); }, viewPrev:function (evt){ if(this._index>0){this._index--;this._render();} }, viewNext:function (evt){ if(this._index<this._images.length-1){ this._index++;this._render(); } }, _render:function (){ this._prevElement.disabled=(this._index==0); this._nextElement.disabled=(this._index==this._ima ges.length-1); this._imageElement.style.visibility='visible'; this._imageElement.src=this._images[this._index];/ /this._images[this._index]; }, _onimageElementLoaded:function (){ this._displayImage(); }, _displayImage:function (){ this._imageElement.style.visibility='hidden'; this._imageElement.scr=this._images[this._index]; }, get_images:function (){ return this._images; }, set_images:function (value){ this._images=value ; if(this._images .length>0){ this._index=0; if(this.get_isInitialized()) {this._render();} } }, get_prevElement:function (){ return this._prevElement; }, set_prevElement:function (value){ this._prevElement=value ; }, get_nextElement:function (){ return this._nextElement; }, set_nextElement:function (value){ this._nextElement=value ; }, get_imageElement:function (){ return this._imageElement; }, set_imageElement:function (value){ this._imageElement=value ; } } Samples.PhotoGallery.registerClass('Samples.PhotoG allery',Sys .UI .Control); Sys .Application .add_init (pageInit ); function pageInit(sender,e) { $create (Samples.PhotoGallery, {'imageElement':$get ('gal_image'), 'prevElement':$get ('gal_prevButton'), 'nextElement':$get ('gal_nextButton'), 'images':['Images/1.jpg','Images/2.jpg','Images/3. jpg','Images/4.jpg','Images/5.jpg'] }, {},{},$get ('photoGallery') ); } </script>
Tags: ajax






