[Flash]flash cs3生成网页的flash插入方法研究

mikel阅读(669)

先来看看flash自动生成的网页是如何插入flash文件的:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh_cn" lang="zh_cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>test</title>
<scrīpt language="javascrīpt">AC_FL_RunContent = 0;</scrīpt>
<scrīpt src="AC_RunActiveContent.js" language="javascrīpt"></scrīpt>

<style type="text/css">
<!–
body {
 background-color: #999900;
}
–>
</style></head>
<body>
<!–影片中使用的 URL–>
<!–影片中使用的文本–>
<!–
eee
–>
<!– saved from url=(0013)about:internet –>
<scrīpt language="javascrīpt">
 if (AC_FL_RunContent == 0) {
  alert("此页需要 AC_RunActiveContent.js");
 } else {
  AC_FL_RunContent(
   'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0',
   'width', '550',
   'height', '400',
   'src', 'test',
   'quality', 'high',
   'pluginspage', 'http://www.macromedia.com/go/getflashplayer',
   'align', 'middle',
   'play', 'true',
   'loop', 'true',
   'scale', 'showall',
   'wmode', 'transparent',
   'devicefont', 'false',
   'id', 'test',
   'bgcolor', '#666666',
   'name', 'test',
   'menu', 'true',
   'allowFullScreen', 'false',
   'allowscrīptAccess','sameDomain',
   'flashvars','txt=wwwww',
   'movie', 'test',
   'salign', ''
   ); //end AC code
 }
 function sendvar(){
  test.style.height=500;
  test.SetVariable("mv","kkkkkk")
  }
</scrīpt>
<noscrīpt>
 <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="550" height="400" id="test" align="middle">
 <param name="allowscrīptAccess" value="sameDomain" />
 <param name="allowFullScreen" value="false" />
 <param name="movie" value="test.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#666666" /
><embed src="test.swf" quality="high" bgcolor="#666666" width="550" height="400" name="test" align="middle" allowscrīptAccess="sameDomain" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
 </object>
</noscrīpt>
 <br>
 <label>xxx
 <input type="submit" name="Submit" value="提交" ōnClick="sendvar()">
 </label>

</body>
</html>
这个网页插入flash共使用了3种方式,应对各种情况,尽可能使swf文件在各种情况、各种浏览器中都能够正常显示运行。

先来看看第一种情况:
最开始使用javascrīpt插入swf文件,这种方式兼容性最好,可以同时兼容IE内核的浏览器及FireFox 浏览器,而且这种插入方式可以避免IE中控件激活框的出现,非常实用。这段自动生成的代码包含的内容很丰富,你可以在其中任意添加IE或者其他浏览器使用 的参数,例如:
'name', 'test',
'id', 'test',
这个是javascrīpt引用swf文件的变量名,使javascrit可以直接对该swf文件进行操作,其中IE只使用id变量就可以了,name变量是针对embed插入方式FireFox使用的。

虽然javascrīpt的插入方式优点多多,但是一旦用户禁用了javascrīpt,就不行了。下面说说第二种方式:
删除所有的javascrīpt代码(同时删除<noscrīpt>和</noscrīpt>)。
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0
" width="550" height="400" id="test" align="middle">
 <param name="allowscrīptAccess" value="sameDomain" />
 <param name="allowFullScreen" value="false" />
 <param name="movie" value="test.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#666666" />
这是IE使用的flash文件插入方式,如果只使用了这段代码,IE可以正常显示,但是FireFox就不能显示了。

第三种,embed插入方式
<embed src="test.swf" quality="high" bgcolor="#666666" width="550" height="400" name="test" align="middle" allowscrīptAccess="sameDomain" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer
" />
这种方式IE和FireFox都可以正常显示

第二种和第三种的参数解释可以参考下面的文章
http://space.flash8.net/space/?246908/action_viewspace_itemid_408019.html

就算不使用javascrīpt,后面两种flash插入方式也可以通过htm页面向flash传递变量:

1、object插入方式:
增加参数:<param name="flashvars" value="mv=hello!"> 

2、embed插入方式:
在后面加入: flashvars="mv=hello!"

通过以上两种方式,flash都可以收到一个变量名为“mv”的变量,内容为“hello!" 。

[Flex]使用Flash Builder 4 beta进行以数据为中心的开发

mikel阅读(791)

作者 Tim Buntel 译者 曹如进 

Adobe Flash Builder 4 beta为Flex开发者们,不管是新手还是老手,提供了更多的机会来创建以数据为中心的富互联网应用。这样一个由专业工具,一个开源框架以及无处不在的 客户端所组成的Flash平台,让你能够发布出令人瞠目的表现内容和应用程序。

尽管如此,大多数的应用还依赖于平台之外的服务。也许你的应用程序为企业数据库中存储的信息提供了报表以及数据可视化的功能,抑或你的富电子商务应 用程序需要与现有的订单管理系统,或者第三方的支付服务进行集成。那么你只需要连接到相应的服务器或者服务,这种应用特性就可以使得上述一切像发送电子邮 件般的简单,例如通过使用云托管服务和第三方的API查询数据库等等。

在以往的版本中,开发者必须学会各种技巧手工编写连接服务器和服务的代码。例如,需要知道连接一个SOAP服务使用的MXML标签不同于连接 ColdFusion组件或者PHP类。此外,你通常还得编写一些对于web开发者来说很少见,很困惑的代码,如事件监听和故障处理。

在Flash Builder 4 beta中,Adobe改变了这一切,它采用了一种全新的方式创建以数据为中心的应用程序。新环境下的Flex开发者可以快速的连接到数据和服务,并将它 们绑定到富UI控件上。这些创建面向数据的高级应用的新方法使得经验丰富的开发者们受益匪浅。

使用Flash Builder 4 beta进行以数据为中心的开发主要包括三个步骤:

  • 定义一个数据/服务模型
  • 将服务操作绑定到Flex组件上
  • 实现高级的数据处理,例如分页和数据管理

在这篇文章中,你将会经历创建一个简单数据管理应用的主要步骤。在这个场景中,你有一张Oracle数据库中的表,并想要创建一个Flex应用来允许用户查看它的数据以及新增,更新和删除记录。

要求

为了更好的理解这篇文章,你需要使用下面的软件和文件:

Flash Builder 4 beta

基础知识

之前有过Flex Builder的使用经验会很有帮助,但这不是必要的。你需要熟悉一种服务端技术例如ColdFusion,Java或者PHP。

第一步:创建一个服务

 由于在Adobe Flash Player中运行的应用程序不能直接与Oracle数据库交互,因此你需要利用一个服务来完成该任务:它可以接受来自Flex发来的请求并传递到数据库 中;还可以将数据库中的数据用一种可理解的格式发回给Flex。这样的远程服务有着相当多的实现方式,在Flash Builder 4 beta中已经内嵌支持了使用ColdFusion,PHP和Java创建服务,而其他类型的服务可以像SOAP web服务或者HTTP服务一样使用。使用ColdFusion是个理想的选择,因为它可以和任何后端数据库交互,且它语法的简单性使得你仅仅需要少量的 标签即可完成创建一个数据接入服务。加之ColdFusion支持一个高性能的名为AMF的协议与Flex应用程序进行数据交换。另外PHP和Java也 同样支持AMF,因此你大可以随心所欲的使用自己最熟悉和认为最高效的服务端技术。

使用ColdFusion,你需要为Flex应用程序执行的每一个数据操作创建一个ColdFusion组件(CFC)函数,如:获取一张表的 所有记录,向表中增加一条新的记录,以及删除一条记录等等。CFC中的函数可以返回弱类型和强类型的数据(例如,如果你正在采用一种更加面向对象的方式进 行开发的话,可以使用getAllRecords函数返回一个ColdFusion的查询对象或者一个对象数组);Flex两种类型的数据都能处理。最后一定要确保cffunction标签的access属性标记为remote后再测试组件。好了,到此为止你已经可以在Flex应用程序中使用这个服务了。

第二步:在Flash Builder中创建模型

在Flash Builder 4 beta中,新的数据/服务面板位于中心位置,主要是用来管理和交互你的应用程序中使用到的所有服务器和服务。它采用一个树状视图来表示所有服务中可用的 数据和操作。视图中呈现的数据和服务可以来源于不同的地方。例如,其中一个可能是ColdFusion组件或者PHP类,另外一个可能是云托管的第三方 RESTful服务。尽管如此,你不必担心它们在服务端如何实现,因为现在将结果绑定到UI组件、编写代码来调用操作都能统一到一个方法中。

为了让服务得以使用,Flash Builder 4 beta会自动检查内部服务并创建树状视图。在Flash Builder 4 beta中选择数据->连接到ColdFusion(或 者你的服务使用到的技术)。对ColdFusion而言,你只需要简单地提供一个想使用的服务名称(例如,EmployeeSvc),并将它定向到文件系 统中的CFC即可。这一步骤会依服务使用技术的不同而略有变化(例如,你也许会为web服务指定WSDL),但是结果一定是一样的:Flash Builder 4 beta通过在内部检查服务来发现返回的操作和数据类型,继而在数据/服务面板上创建服务的树状视图。

如果有必要的话,你还可以继续向服务树状视图中加入其它服务,或者也可以马上就在应用程序中使用已有的服务。如果服务是弱数据类型,那么需要一个额外的步骤。因为一个弱类型的服务仅返回数据,而没有关于数据所代表含义的信息。

比方说,你的CFC函数返回了一个ColdFusion查询对象,而Flash Builder 4 beta看到的只是一堆记录,它并不知道这些记录代表的是产品集合还是员工集合或是销售订单的集合;这仅仅是一堆数据而已。为了关联操作结果的数据类 型,Flash Builder 4 beta允许你手工配置操作返回的数据类型。当然,如果你在服务端使用强类型的数据类型,这步是可以略去的。

想要设置弱类型服务的返回类型,你可以右键点击数据/服务面板(例如,getAllItems操作),然后选择配置返回类型。向导会帮助你建立服务端弱类型数据与Flex应用程序中的强类型的映射关系。它通过给出一个真实的操作样例数据来让你决定选择什么样的类型。过程中你需要为操作返回的自定义类型指定一个名称,例如可以把返回的每一条记录称为Employee或者SalesOrder,还可以指定数据类型中的字段和格式——如将name的类型设置为string,员工的id设置为数字(见图1)。

图1. 配置操作返回类型

第三步:将服务连接到UI控件

既然你已经定义好了服务中所有的操作以及返回的数据类型,那么现在需要做的就是在应用程序中 的某个地方显示那些操作的结果。Flex框架中包含了大量的控件用以数据绑定,包括数据网格(data grids),列表控件(list boxes),表单域(form fields)等等。这些组件可以显示数据并允许用户与你的服务进行交互。

一开始就在设计视图中对UI进行布局,以及绑定操作到组件上会很简单。只要切换编辑器从源代码视图到设计视图,你就可以从组件面板中拖动组件到应用程序的画布(canvas)上并进行精确定位。

选择DataGrid组件(在组件面板里数据控件组的下面),将它拖放到页面中。你会发现它没有绑定到任何数据;如果运行程序,会发现它仅仅是 一个三列的空网格。为了能够让网格显示从你的服务操作中获取的数据,你只需要简单地将数据/服务面板中的操作拖拽到网格上即可。结束之后你会发现,网格将 会显示从操作返回的列。这时,保存项目,运行,就得到了一个正在使用你的ColdFusion服务填充网格的应用程序。这一切都无需编写任何代码,无需事 先任何事件监听器,无需知道服务端是ColdFusion还是Java或者SOAP。你还可以用很多其他方法来快速创建基于数据类型和服务的应用程序 UI。如可以从一个数据类型生成表单并且创建主从表,可以将一个服务拖拽到按钮组件中,然后每当用户点击这个按钮,就会触发操作的执行(例如,调用保存操 作),还可以将操作拖拽到图表控件上等等。

数据和服务特性并不是仅仅在设计视图中有用。通过使用服务模型生成的子类,你可以获得关于所有操作和数据类型,甚至值对象的自定义行为的代码提示。

高级数据特性

Flash Builder 4 beta以数据为中心的新特色功能,可以极大地提高你在创建以数据为中心的应用程序时的生产力。虽然在Flex Builder 3中也可以创建同样的应用,但是要花费更多的精力。这种新的高级数据特性,已经超越了生产力;它们能够让你实现在以前看来极度困难或是不可能的功能。比 如,客户端数据管理特性可以让你将客户端的常见数据服务操作(选择,创建,更新和删除记录)与服务端相应的数据操作进行映射。这将使得你能够批量处理操 作,而撤销功能可以使用户重做一些改变等等。另外一个强大的特性是支持自动分页。如果你要显示大量的记录,那么在应用程序一次性读取和加载它们的时候,会 有性能问题。而分页会自动地每次按需取出一小部分的记录;你需要做的只是提供一个能够接受某行开始以及所需读取的记录数为参数的服务,而Flash Builder 4 beta负责实现客户端的所有逻辑。

下一步怎么做

不管你是一名经验丰富的Flex开发者还是刚刚接触这个技术的新手,Flash Builder 4 beta都能够让你充分利用已有的服务端数据和服务逻辑知识,轻松的创建富应用开发体验。下载好软件后,今天就可以开始让你的用户看到数据新的呈现方式。 同时也别忘了看看Adobe实验室的视频和教程哦.

关于作者

Tim Buntel是Flash Builder(以前叫做Flex Builder)的高级产品经理。在2007年加入Flex小组之前,他曾担任多年的Adobe ColdFusion高级产品经理。

阅读原文Data-centric development with Flash Builder 4 beta

[Flex]第三方强力工具TurboDieselSportInjection和Reducer

mikel阅读(768)

在描述这2个强力工具之前,先说一下这两个应用背后的框架,apparat,是一个基于Java构造的开源框架,用于优化SWC和SWF,我们可以从GoogleCode中发现这个不起眼的开源项目(主要是GoogleCode中,项目太多了的缘故):
http://code.google.com/p/apparat/
而基于此框架诞生的2个强力应用工具,第一是TurboDieselSportInjection(名字太长了,连原作者也说他并不善于起名字,简称TDSI)。
它是从整个apparat框架中派生出来的一个针对性工具,允许你链接封装的__bytecode(供AVM解释的机器码)并使用全新内存API的工具。这个工具内置对机器码和Alchemy操作的支持!
第二个工具也很惊奇,叫做Reducer。 这个工具也是从apparat框架中派生出来的,用于优化SWF/SWC文件,主要是让文件尺寸变的更小,但是不影响到组件功能,作者描述到,如果你在 SWF或者SWC中使用PNG图形,实际上对于图形元素,SWF/SWC并不会进行压缩处理。Reducer这个工具就是为了安全的让开发者优化 [Embed]标签并且以后也能正常使用压缩后的元件,它将压缩有所无损的高质量图片以降低文件尺寸。

[C#]基于.net技术的 Rss 订阅开发

mikel阅读(618)

        RSS(Really Simple Syndication,真正简单的连锁)是一种 Web 内容连锁格式。RSS 成为通过 Web 连锁新闻内容的标准格式。刚好我现在vs的环境也是.net,因为在.NET3.5下,MS集成了RSS对象。这样一改变,就很大的方便了创建和读取 RSS了。
       首先搞了个Rss.aspx页面,在Page_Load方法里面显示让它以标准的xml格式输出
      Response.Cache.SetNoStore();
      Response.ContentType = "application/xml";
 
      然后根据需要订阅的页面传过来的参数进行一番判断。把所有符合条件的资源都放在DataTable里面。
 
     接着用MemoryStream对象对xml进行操作,就不多说了,看了代码就会明白,同时也给自己做个备忘。如下:
    

        MemoryStream ms = new MemoryStream();
        XmlTextWriter xmlTW = new XmlTextWriter(ms, Encoding.UTF8);
        xmlTW.Formatting = Formatting.Indented;
        xmlTW.WriteStartDocument();
        xmlTW.WriteStartElement("rss");
        xmlTW.WriteAttributeString("version", "2.0");
        xmlTW.WriteStartElement("channel");
        if (WebID == 0)
        {
        }
        else
        {
            xmlTW.WriteElementString("title", "欢迎订阅"+WebDs.Tables[0].Rows[0] ["Web_Name"].ToString()+">>"+ColumnDs.Tables[0].Rows[0]["ColumnName"].ToString());
            xmlTW.WriteElementString("link", ColumnDs.Tables[0].Rows[0]["CoulumnUrl"].ToString());
            xmlTW.WriteElementString("description", "");
         
        }
     
        DataTable dt = ds.Tables[0];

        foreach (DataRow dr in dt.Rows)
        {
            xmlTW.WriteStartElement("item");
          
            xmlTW.WriteElementString("title", dr["Article_Title"].ToString());
            xmlTW.WriteElementString("link", GetNewsLink(dr));
            xmlTW.WriteElementString("pubDate",string.Format("{0:R}",dr["CreateTime"]));
            xmlTW.WriteElementString("author", dr["UserLogin_FullName"].ToString());
            xmlTW.WriteElementString("description", Pub_Config.nohtml(Pub_Config.Substrin(dr["Article_Body"], 400)));
          
           
            xmlTW.WriteEndElement();

        }

        xmlTW.WriteEndElement();
        xmlTW.WriteEndElement();
        xmlTW.WriteEndDocument();
        xmlTW.Flush();
        byte[] buffer = ms.ToArray();
        Response.Write(Encoding.UTF8.GetString(buffer));
        Response.End();
        xmlTW.Close();
        ms.Close();
        ms.Dispose();
  要注意的是:
   1.XML格式是大小写敏感的,这就意味着,XML元素的起始和终止标签必须匹配,拼写和大小写都必须一致。
  2.RSS2.0的根元素是< rss>元素,这个元素可以有一个版本号的属性,例如:

< rssversion="2.0">

< /rss>

< rss>元素只有一个子元素< channel>,用来描述聚合的内容。在< channel>元素里面有三个必需的子元素,用来描述Web站点的信息。这三个元素是:

title—定义聚合文件的名称,一般来说,还会包括Web站点的名称;
link—Web站点的URL;
description—Web站点的一段简短的描述。
除此之外,还有一些可选元素来描述站点信息。这些元素的更多信息请参见RSS2.0规范。

每一个新闻项目放在一个单独的< item>元素中。< channel>元素可以有任意数量的< item>元素。每个< item>元素可以有多种的子元素,唯一的要求是最少必须包含< title>元素和< description>元素其中一个作为子元素。以下列出了一些相关的< item>子元素:

 

title—新闻项目的标题;

link—新闻项目的URL;

description—新闻项目的大纲;

author—新闻项目的作者;

pubDate—新闻项目的发布日期

3.< item>子元素尤其要注意的是pubDate的格式,RSS要求日期必须按照RFC822日期和时间规范进行格式化,此格式要求:开头是一个可选的3字母星期缩写加一个逗号,

 

.

 

接着必须是日加上3字母缩写的月份和年份,最后是一个带时区名的时间。

我们可以用Stirng.foemat()来转化如期格式,就如我上面那个例子。
最终结果:
 

[SQL]sql server和oracle行转列的一种典型方法

mikel阅读(1047)

前言:网上有不少文章是讲行转列的,但是大部分都是直接贴代码,忽视了中间过程,本人自己思考了下为什么要这样实现,并且做了如下的笔记,对有些懂的人来说可能没有价值,希望对还不懂的人有一点借鉴意义。

对于有些业务来说,数据在表中的存储和其最终的Grid表现恰好相当于把源表倒转,那么这个时候我们就碰到了如何把行转化为列的问题,为了简化问题,我们且看如下查询出来的数据,您不必关心表的设计以及SQL语句:

image

假设用到的SQL语句为:

Select [姓名],[时代],[金钱]
  
FROM [test].[dbo].[people]  

 

这个表存储了两个人在不同时代(时代是固定的三个:年轻、中年和老年)拥有的金币,其中:

张三在年轻、中年和老年时期分别拥有1000、5000、800个金币;

李四在年轻、中年和老年时期分别拥有1200、6000、500个金币。

现在我们想把两人在不同阶段拥有的金币用类似如下的表格来展现:

姓名 年轻 中年 老年
张三 1000 5000 800
李四 1200 6000 500

 

我们现在考虑用最简单和直接的办法来实现,其实关键是如何创建那些需要增加的列,且如何设定其值,现在我们来创建“年轻”列,关键的问题是,这一列的值如何设定?合法的逻辑应该是这样:如果该行不是“年轻”时代,那么其“金钱”我们认为是0,那么sql语句如何写呢?

如果是用的sql server,那么肯定要用到case了:

 

case  [时代] when '年轻' then [金钱] else 0 end as 年轻

case when  [时代]= '年轻' then [金钱] else 0 end as 年轻

 

如果用的是oracle,那么要用到decode函数,decode(1+1,3,'错',2,'是',5,'错','都不满足下返回的值'),这 个函数将返回“是”,具体用法限于篇幅这里不再介绍,相信大家从这个式子可以大概了解到其意思,用decode创建“年轻”列的句子是:完整的sql语句如下所示:

decode(时代,'年轻',金钱,0)) 年轻

 


Select [姓名],[时代],[金钱]
case  [时代] when '年轻' then [金钱] else 0 end as 年轻,
case  [时代] when '中年' then [金钱] else 0 end as 中年,
case  [时代] when '老年' then [金钱] else 0 end as 老年 
  
FROM [test].[dbo].[people] 

 

现在我们来看看其执行结果:

image

相信看到这个结果,大家都知道下一步该做什么,那就是分组:按姓名分组,并且对三个时代的金钱进行求和:

select [姓名],sum([年轻]as 年轻,sum([中年]as 中年,sum([老年]as 老年 from
(
Select [姓名],[时代],[金钱]
case  [时代] when '年轻' then [金钱] else 0 end as 年轻,
case  [时代] when '中年' then [金钱] else 0 end as 中年,
case  [时代] when '老年' then [金钱] else 0 end as 老年 
  
FROM [test].[dbo].[people]) t
  
group by [姓名]

这里用到了子查询,是为了逻辑更清晰一点,其实可以不用子查询;至于oracle下的sql语句,除了要使用decode之外,其余几乎一致,本人正是在oracle中实现之后才研究了下sql server下的实现方式。

最后看看结果:

image

事实上,当列不固定的时候,比如除了“年轻”、“中年”、“老年”以外还有其他的未知的时代,实现思路其实基本一致,只是需要动态生成sql而已。

[MVC]ASP.NET MVC Action Filter - Caching and Compr

mikel阅读(825)

原文地址: ASP.NET MVC Action Filter – Caching and Compression

下载源码: Source.zip

关于Action Filter你可以参考我的另外一篇文章: ASP.NET MVC : Action过滤器(Filtering)

 

缓存在开发高扩充性WEB程序的时候扮演着很重要的角色.我们可以将HTTP请求在一个定义的时间内缓存在用户的浏览器中,如果用户在定义的时间内请求同一个URL,那么用户的请求将会从用户浏览器的缓存中加载,而不是从服务器.你可以在ASP.NET MVC应用程序中使用下面的Action Filter来实现同样的事情:

using System;
using System.Web;
using System.Web.Mvc;
public class CacheFilterAttribute : ActionFilterAttribute
{
/// <summary>
/// Gets or sets the cache duration in seconds. The default is 10 seconds.
/// </summary>
/// <value>The cache duration in seconds.</value>
public int Duration
{
get;
set;
}
public CacheFilterAttribute()
{
Duration = 10;
}
public override void OnActionExecuted(FilterExecutedContext filterContext)
{
if (Duration <= 0) return;
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
TimeSpan cacheDuration = TimeSpan.FromSeconds(Duration);
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(cacheDuration));
cache.SetMaxAge(cacheDuration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
}
}

你可以好像下面一样在你的Controller Action 方法中使用这个Filter :

 
[CacheFilter(Duration = 60)]
public void Category(string name, int? page)

下面是在firebug中当 缓存Filter 没有应用的时候的截图 :

NoCache

下面的截图是应用了 Cache Filter 时候的截图 :

Cache

 

另外一个很重要的事情就是压缩.现在的浏览器都可以接收压缩后的内容,这可以节省大量的带宽.你可以在你的ASP.NET MVC 程序中应用下面的Action Filter 来压缩你的Response :

 
using System.Web;
using System.Web.Mvc;
public class CompressFilter : ActionFilterAttribute
{
public override void OnActionExecuting(FilterExecutingContext filterContext)
{
HttpRequestBase request = filterContext.HttpContext.Request;
string acceptEncoding = request.Headers["Accept-Encoding"];
if (string.IsNullOrEmpty(acceptEncoding)) return;
acceptEncoding = acceptEncoding.ToUpperInvariant();
HttpResponseBase response = filterContext.HttpContext.Response;
if (acceptEncoding.Contains("GZIP"))
{
response.AppendHeader("Content-encoding", "gzip");
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
}
else if (acceptEncoding.Contains("DEFLATE"))
{
response.AppendHeader("Content-encoding", "deflate");
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
}
}
}

然后将这个Filter应用到你的Controller Action 中 :

[CompressFilter]
public void Category(string name, int? page)

下面是没有应用压缩的时候的截图 :

Uncompressed

下面的截图是应用了压缩Filter后的情形 :

Compressed

你当然也可以将这两个Filter都应用到同一个Action方法上,就好像下面所示 :

 
[CompressFilter(Order = 1)]
[CacheFilter(Duration = 60, order = 2)]
public void Category(string name, int? page)

下面是截图 :

Both

Enjoy!!!

下载源码: Source.zip

作者:QLeelulu
出处:http://QLeelulu.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利
 

[C#]List(T) 的实现实体对象操作

mikel阅读(718)

实体对象类:

新闻发布实体类
     [MapTo(
"us_News")]
    
public abstract class NewsBLL 
    {
        [MapTo(
"FId")]
        
public abstract string Id { get;}
        
/// <summary>
        
/// 标题
        
/// </summary>
        [MapTo("FCaption")]
        
public abstract string Caption {getset;}
        
/// <summary>
        
/// 新闻类别PK-ss_Item.ID
        
/// </summary>
        [MapTo("FType")]
        [ManyToOne(LocalKey 
= "FType", ForeignKey = "FId")]
        
public abstract DictItemBLL Type {getset;}
        
/// <summary>
        
/// 新闻内容
        
/// </summary>
        [MapTo("FContent")]
        
public abstract string Content {getset;}
        
/// <summary>
        
/// 发布人
        
/// </summary>
        [MapTo("FUserID")]
        
public abstract string UserID {getset;}        
     }
新闻类别类
    [MapTo(
"ss_Item")]
    
public abstract class DictItemBLL 
    {
        [MapTo(
"FId")]
        
public abstract int Id { get;}
        
/// <summary>
        
/// 分组编号
        
/// </summary>
        [MapTo("FClassID")]
        
public abstract int ClassID {getset;}
        
/// <summary>
        
/// 父节点ID
        
/// </summary>
        [MapTo("FParentID")]
        
public abstract int ParentID {getset;}
        
/// <summary>
        
/// 字典名称
        
/// </summary>
        [MapTo("FName")]
        
public abstract string Name {getset;}
        
#endregion
    }

1,使用Find搜索单个匹配值

 

    NewsBLL news = list.Find(delegate(NewsBLL x) 
           { 
               
return x.Type.Id == 10001//搜索新闻列表中类别(Type)子对象中的 Id=10001的单个对象
           });

2,使用FindAll搜索多个匹配值

    List<NewsBLL> newsList = list.Find(delegate(NewsBLL x) 
           { 
               
return x.Type.Id == 10001//搜索新闻列表中类别(Type)子对象中的 Id=10001的多个对象
           });

3,是用Contains检查满足条件的值是否存在

 

    bool isContains= list.Find(delegate(NewsBLL x) 
           { 
               
return x.Type.Id == 10001 && x.UserID=="ejiyuan"//搜索新闻列表中类别(Type)子对象中的 Id=10001,并且发布人等于ejiyuan的是否存在
           });

4,使用ForEach 对每个列表对象进行操作

 

    list.Find(delegate(NewsBLL x) 
           { 
               x.Caption 
= "10001";  //将列表中所有标题都修改为10001

           });

5,使用sort排序,按类别 id排序

            list.Sort(delegate(NewsBLL x,DocumentBLL y)
            {
                
if (x.Type.Id < y.Type.Id )
                {
                    
return 1;
                }
                
else if (x.Type.Id == y.Type.Id )
                {
                    
return 0;
                }
                
else return 1;
            });

 

[C#]从.NET中委托写法的演变谈开去(下):性能相关

mikel阅读(697)

上一篇文章中,我们详细讲述了C# 3.0中Lambda表达式(构造委托)的使用方式,它在语义上的优势及对编程的简化——这些内容已经属于委托的“扩展内容”。不如这次谈得更远一些,就来讨论一下上文中“编程方式”的性能相关话题。

循环分离及其性能

  在上文的第一个示例中, 我们演示了如何使用Lambda表达式配合.NET 3.5中定义的扩展方法来方便地处理集合中的元素(筛选,转化等等)。不过有朋友可能会提出,那个“普通写法”并非是性能最高的实现方法。方便起见,也为 了突出“性能”方面的问题,我们把原来的要求简化一下:将序列中的偶数平方输出为一个列表。按照那种“普通写法”可能就是:

static List<int> EvenSquare(IEnumerable<int> source)
{
var evenList = new List<int>();
foreach (var i in source)
{
if (i % 2 == 0) evenList.Add(i);
}
var squareList = new List<int>();
foreach (var i in evenList) squareList.Add(i * i);
return squareList;
}

  从理论上来说,这样的写法的确比以下的做法在性能要差一些:

static List<int> EvenSquareFast(IEnumerable<int> source)
{
List<int> result = new List<int>();
foreach (var i in source)
{
if (i % 2 == 0) result.Add(i * i);
}
return result;
}

  在第二种写法直接在一次遍历中进行筛选,并且直接转化。而第一种写法会则根据“功能描述”将做法分为两步,先筛选后转化,并使用一个临时列表进 行保存。在向临时列表中添加元素的时候,List<int>可能会在容量不够的时候加倍并复制元素,这便造成了性能损失。虽然我们通过“分 析”可以得出结论,不过实际结果还是使用CodeTimer来测试一番比较妥当:

List<int> source = new List<int>();
for (var i = 0; i < 10000; i++) source.Add(i);
// 预热
EvenSquare(source);
EvenSquareFast(source);
CodeTimer.Initialize();
CodeTimer.Time("Normal", 10000, () => EvenSquare(source));
CodeTimer.Time("Fast", 10000, () => EvenSquareFast(source));

  我们准备了一个长度为10000的列表,并使用EvenSquare和EvenSquareFast各执行一万次,结果如下:

Normal
Time Elapsed:   3,506ms
CPU Cycles:     6,713,448,335
Gen 0:          624
Gen 1:          1
Gen 2:          0
Fast
Time Elapsed:   2,283ms
CPU Cycles:     4,390,611,247
Gen 0:          312
Gen 1:          0
Gen 2:          0

  结果同我们料想中的一致,EvenSquareFast无论从性能还是GC上都领先于EvenSquare方法。不过,在实际情况下,我们该选择哪种做法呢?如果是我的话,我会倾向于选择EvenSquare,理由是“清晰”二字。

  EvenSquare虽然使用了额外的临时容器来保存中间结果(因此造成了性能和GC上的损失),但是它的逻辑和我们需要的功能较为匹配,我们 可以很容易地看清代码所表达的含义。至于其中造成的性能损失在实际项目中可以说是微乎其微的。因为实际上我们的大部分性能是消耗在每个步骤的功能上,例如 每次Int32.Parse所消耗的时间便是一个简单乘法的几十甚至几百倍。因此,虽然我们的测试体现了超过50%的性能差距,不过由于这只是“纯遍历” 所消耗的时间,因此如果算上每个步骤的耗时,性能差距可能就会变成10%,5%甚至更低。

  当然,如果是如上述代码那样简单的逻辑,则使用EvenSquareFast这样的实现方式也没有任何问题。事实上,我们也不必强求将所有步骤 完全合并(即仅仅使用1次循环)或完全分开。我们可以在可读性与性能之间寻求一种平衡,例如将5个步骤使用两次循环来完能是更合适的方式。

  说到“分解循环”,其实这类似于Martin Fowler在他的重构网站所上列出的重构方式之一:“Split Loop”。虽然Split Loop和我们的场景略有不同,但是它也是为了代码的可读性而避免将多种逻辑放在一个循环内。将循环拆开之后,还可以配合“Extract Method”或“Replace Temp with Query”等方式实现进一步的重构。自然,它也提到拆分后的性能影响:

You often see loops that are doing two different things at once, because they can do that with one pass through a loop. Indeed most programmers would feel very uncomfortable with this refactoring as it forces you to execute the loop twice – which is double the work.

But like so many optimizations, doing two different things in one loop is less clear than doing them separately. It also causes problems for further refactoring as it introduces temps that get in the way of further refactorings. So while refactoring, don't be afraid to get rid of the loop. When you optimize, if the loop is slow that will show up and it would be right to slam the loops back together at that point. You may be surprised at how often the loop isn't a bottleneck, or how the later refactorings open up another, more powerful, optimization.

  这段文字提到,当拆分之后,您可能会发现更好的优化方式。高德纳爷爷也认为“过早优化是万恶之源”。这些说法都在“鼓励”我们将程序写的更清晰而不是“看起来”更有效率

扩展方法的延迟特性

  对于上面的简化需求,使用Lambda表达式和.NET 3.5中内置的扩展方法便可以写成这样:

static List<int> EvenSquareLambda(IEnumerable<int> source)
{
return source.Where(i => i % 2 == 0).Select(i => i + 1).ToList();
}

  应该已经有许多朋友了解了.NET 3.5中处理集合时扩展方法具有“延迟”的效果,也就是说Where和Select中的委托(两个Lambda表达式)只有在调用ToList方法的时候 才会执行。这是优点也是陷阱,在使用这些方法的时候我们还是需要了解这些方法的效果如何。不过这些方法其实都没有任何任何“取巧”之处,换句话说,它们的 行为和我们正常思维的结果是一致的。如果您想得明白,能够自己写出类似的方法,或者能够“自圆其说”,十有八九也不会有什么偏差。但是如果您想不明白它们 是如何构造的,还是通过实验来确定一下吧。实验的方式其实很简单,只要像我们之前验证“重复计算”陷阱那种方法就可以了,也就是观察委托的执行时机和顺序进行判断。

  好,回到我们现在的问题。我们知道了“延迟”效果,我们知道了Where和Select会在ToList的时候才会进行处理。不过,它们的处理 方式是什么样的,是像我们的“普通方法”那样“创建临时容器(如List<T>),并填充返回”吗?对于这点我们不多作分析,还是通过“观察 委托执行的时机和顺序”来寻找答案。使用这种方式的关键,便是在委托执行时打印出一些信息。为此,我们需要这样一个Wrap方法(您自己做试验时也可以使 用这个方法):

static Func<T, TResult> Wrap<T, TResult>(
Func<T, TResult> func,
string messgaeFormat)
{
return i =>
{
var result = func(i);
Console.WriteLine(messgaeFormat, i, result);
return result;
};
}

  Wrap方法的目的是将一个Func<T, TResult>委托对象进行封装,并返回一个类型相同的委托对象。每次执行封装后的委托时,都会执行我们提供的委托对象,并根据我们传递的messageFormat格式化输出。例如:

var wrapper = Wrap<int, int>(i => i + 1, "{0} + 1 = {1}");
for (var i = 0; i < 3; i++) wrapper(i);

  则会输出:

0 + 1 = 1
1 + 1 = 2
2 + 1 = 3

  那么,我们下面这段代码会打印出什么内容呢?

List<int> source = new List<int>();
for (var i = 0; i < 10; i++) source.Add(i);
var finalSource = source
.Where(Wrap<int, bool>(i => i % 3 == 0, "{0} can be divided by 3? {1}"))
.Select(Wrap<int, int>(i => i * i, "The square of {0} equals {1}."))
.Where(Wrap<int, bool>(i => i % 2 == 0, "The result {0} can be devided by 2? {1}"));
Console.WriteLine("===== Start =====");
foreach (var item in finalSource)
{
Console.WriteLine("===== Print {0} =====", item);
}

  我们准备一个列表,其中包含0到9共十个元素,并将其进行Where…Select…Where的处理,您可以猜出经过foreach之后屏幕上的内容吗?

===== Start =====
0 can be divided by 3? True
The square of 0 equals 0.
The result 0 can be devided by 2? True
===== Print 0 =====
1 can be divided by 3? False
2 can be divided by 3? False
3 can be divided by 3? True
The square of 3 equals 9.
The result 9 can be devided by 2? False
4 can be divided by 3? False
5 can be divided by 3? False
6 can be divided by 3? True
The square of 6 equals 36.
The result 36 can be devided by 2? True
===== Print 36 =====
7 can be divided by 3? False
8 can be divided by 3? False
9 can be divided by 3? True
The square of 9 equals 81.
The result 81 can be devided by 2? False

  列表中元素的执行顺序是这样的:

  1. 第一个元素“0”经过Where…Select…Where,最后被Print出来。
  2. 第二个元素“1”经过Where,中止。
  3. 第三个元素“2”经过Where,中止。
  4. 第四个元素“4”经过Where…Select…Where,中止。
  5. ……

  这说明了,我们使用.NET框架自带的Where或Select方法,最终的效果和上一节中的“合并循环”类似。因为,如果创建了临时容器保存 元素的话,就会在第一个Where中把所有元素都交由第一个委托(i => i % 3 == 0)执行,然后再把过滤后的元素交给Select中的委托(i => i * i)执行。请注意,在这里“合并循环”的效果对外部是隐藏的,我们的代码似乎还是一步一步地处理集合。换句话说,我们使用“分解循环”的清晰方式,但获得 了“合并循环”的高效实现。这就是.NET框架这些扩展方法的神奇之处1

  在我们进行具体的性能测试之前,我们再来想一下,这里出现了那么多IEnumerable对象实现了哪个GoF 23中的模式呢?枚举器?看到IEnumerable就说枚举器也太老生常谈了。其实这里同样用到了“装饰器”模式。每次Where或Select之后其 实都是使用了一个新的IEnumerable对象来封装原有的对象,这样我们遍历新的枚举器时便会获得“装饰”后的效果。因此,以后如果有人问您 “.NET框架中有哪些的装饰器模式的体现”,除了人人都知道的Stream之外,您还可以回答说“.NET 3.5中System.Linq.Enumerable类里的一些扩展方法”,多酷。

扩展方法的性能测试

  经过上节的分析,我们知道了Where和Select等扩展方法统一了“分解循环”的外表和“合并循环”的内在,也就是兼顾了“可读性”和“性能”。我们现在就使用下面的代码来验证这一点:

List<int> source = new List<int>();
for (var i = 0; i < 10000; i++) source.Add(i);
EvenSquare(source);
EvenSquareFast(source);
EvenSquareLambda(source);
CodeTimer.Initialize();
CodeTimer.Time("Normal", 10000, () => EvenSquare(source));
CodeTimer.Time("Fast", 10000, () => EvenSquareFast(source));
CodeTimer.Time("Lambda", 10000, () => EvenSquareLambda(source));

  结果如下:

Normal
Time Elapsed:   3,127ms
CPU Cycles:     6,362,621,144
Gen 0:          624
Gen 1:          3
Gen 2:          0
Fast
Time Elapsed:   2,031ms
CPU Cycles:     4,070,470,778
Gen 0:          312
Gen 1:          0
Gen 2:          0
Lambda
Time Elapsed:   2,675ms
CPU Cycles:     5,672,592,948
Gen 0:          312
Gen 1:          156
Gen 2:          0

  从时间上看,“扩展方法”实现的性能介于“分解循环”和“合并循环”两者之间。而GC方面,“扩展方法”实现也优于“分解循环”(您同意这个看法吗?)。因此我们可以得出以下结论:

  性能 可读性
分解循环 No. 3 No. 2
合并循环 No. 1 No. 3
扩展方法 No. 2 No. 1

  至于选择哪种方式,就需要您自行判断了。

  值得注意的是,无论是“延迟”还是“分解循环”的效果,刚才我们都是针对于Where和Select来谈的。事实上,还有并不是所有的扩展方法都有类似的特性,例如:

  • 非延迟:ToArray、ToList、Any,All,Count……
  • 非分解循环:OrderBy,GroupBy,ToDictionary……

  不过别担心,正如上节所说,是否“延迟”,是否“分解循环”都是非常明显的。如果您可以写出类似的方法,或者能够“自圆其说”,一般您的判断也 不会有什么错误。例如,OrderBy为什么不是“分解循环”的呢?因为在交由下一步枚举之前,它必须将上一步中的所有元素都获取出来才能进行排序。如果 您无法“很自然”地想象出这些“原因”,那么就写一段程序来自行验证一番吧。

其他性能问题

  一般来说,这些扩展方法本身不太会出现性能问题,但是任何东西都可能被滥用,这才是程序中的性能杀手。例如:

IEnumerable<int> source = ...;
for (var i = 0; i < source.Count(); i++)
{
...
}

  这段代码的问题,在于每次循环时都需要不断地计算出source中元素的数量,这意味着不断地完整遍历、遍历。对于一些如Count或Any这 样“立即执行”的方法,我们在使用时脑子里一定要留有一些概念:它会不会出现性能问题。这些问题的确很容易识别,但是我的确看到过这样的错误。即使在出现 这些扩展方法之前,我也看到过某些朋友编写类似的代码,如在for循环中不断进行str.Split(',').Length。

  还是再强调一下“重复计算”这个问题吧,它可能更容易被人忽视。如果您“重复计算”的集合是内存中的列表,这对性能来说可能影响不大。但是,试想您每次重复计算时,都要重复去外部数据源(如数据库)获取原始数据,那么此时造成的性能问题便无法忽视了。

总结

  这个系列的文章就写到这里,似乎想谈的也谈的差不多了。这三篇文章是由《关于最近面试的一点感想》引起的,我写文章一开始的目的也是希望证明“委托也是个值得一提的内容”。了解委托的写法,了解这些变化并非仅仅是“茴有几种写法”。这里引用钧梓昊逑同学的评论,我认为说得很有道理:

我觉得问某件事物在不同的版本中有什么区别还是可以比较容易判断出他的掌握和理解程度的。新版本中每一个新特性的加入并不是随意加的,为什么要加, 加之前有什么不足,加了之后有什么好处,在什么时候什么场景下用这些新特性,用了有什么风险和弊端,等等。如果能非常流利得回答,就算不是很正确也至少说 明他去认真思考过了,因为这些东西可能是网上书上所没有的。

  所以,我在面试时也会提出“delegate写法演变”或类似的问题。甚至我认为这是一个非常好的入手点,因为在.NET中如此经典的演变并不 多见。如果一个人可以把这些内容都理清,我有理由相信他对待技术的态度是非常值得肯定的。反观原文后许多带有嘲讽的评论,在这背后我不知道他们是真正了解 了委托而认为这些内容不值一提,还是“自以为”理解了全部内容,却不知道在这看似简单的背后还隐藏着庞大的深层的思维和理念。

  我想,我们还是不要轻易地去“轻视”什么东西吧,例如在面试时轻视对方提出的问题,看重框架而轻视语言,看重所谓“底层”而轻视“应用”。最后,我打算引用韦恩卑鄙同学的话来结束全文:

一般说C语言比C#强大的,C语言也就写到Hello World而已。

相关文章

  • 从.NET中委托写法的演变谈开去(上):委托与匿名方法
  • 从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势
  • 从.NET中委托写法的演变谈开去(下):性能相关
  •  

    注1:虽然Where和Select具有“延迟”效果,但是内部实现是“分解循环”还是“合并循环”则是另一种选择。您能否尝试在“延迟”的前提下,提供“分解循环”和“合并循环”两种Where或Select的实现呢?

    [C#]从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势

    mikel阅读(543)

    上一篇文章中我们简单探讨了.NET 1.x和.NET 2.0中委托表现形式的变化,以及.NET 2.0中匿名方法的优势、目的及注意事项。那么现在我们来谈一下.NET 3.5(C# 3.0)中,委托的表现形式又演变成了什么样子,还有什么特点和作用。

    .NET 3.5中委托的写法(Lambda表达式)

      Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。例如,以下便是一个使用Lambda表达式定义了委托的示例1

    Func<int, int, int> max = (int a, int b) =>
    {
    if (a > b)
    {
    return a;
    }
    else
    {
    return b;
    }
    };
    

      与上文使用delegate定义匿名方法的作用相同,Lambda表达式的作用也是为了定义一个匿名方法。因此,下面使用delegate的代码和上面是等价的:

    Func<int, int, int> max = delegate(int a, int b)
    {
    if (a > b)
    {
    return a;
    }
    else
    {
    return b;
    }
    };
    

      那么您可能就会问,这样看来Lambda表达式又有什么意义呢?Lambda表达式的意义便是它可以写的非常简单,例如之前的Lambda表达式可以简写成这样:

    Func<int, int, int> max = (a, b) =>
    {
    if (a > b)
    {
    return a;
    }
    else
    {
    return b;
    }
    };
    

      由于我们已经注明max的类型是Func<int, int, int>,因此C#编译器可以明确地知道a和b都是int类型,于是我们就可以省下参数之前的类型信息。这个特性叫做“类型推演”,也就是指编译器可以自动知道某些成员的类型2。请不要轻易认为这个小小的改进意义不大,事实上,您会发现Lambda表达式的优势都是由这一点一滴的细节构成的。那么我们再来一次改变:

    Func<int, int, int> max = (a, b) => a > b ? a : b;

      如果Lambda表达式的body是一个表达式(expression),而不是语句(statement)的话,那么它的body就可以省略大括号和return关键字。此外,如果Lambda表达式只包含一个参数的话,则参数列表的括号也可以省略,如下:

    Func<int, bool> positive = a => a > 0;

      如今的写法是不是非常简单?那么我们来看看,如果是使用delegate关键字来创建的话会成为什么样子:

    Func<int, bool> positive = delegate(int a)
    {
    return a > 0;
    };
    

      您马上就可以意识到,这一行和多行的区别,这几个关键字和括号的省略,会使得编程世界一下子变得大为不同。

      当然,Lambda表达式也并不是可以完全替代delegate写法,例如带ref和out关键字的匿名方法,就必须使用.NET 2.0中的delegate才能构造出来了。

    使用示例一

      Lambda表达式的增强在于“语义”二字。“语义”是指代码所表现出来的含义,说的更通俗一些,便是指一段代码给阅读者的“感觉”如何。为了说明这个例子,我们还是使用示例来说明问题。

      第一个例子是这样的:“请写一个方法,输入一个表示整型的字符串列表,并返回一个列表,包含其中偶数的平方,并且需要按照平方后的结果排序”。很简单,不是吗?相信您一定可以一蹴而就:

    static List<int> GetSquaresOfPositive(List<string> strList)
    {
    List<int> intList = new List<int>();
    foreach (var s in strList) intList.Add(Int32.Parse(s));
    List<int> evenList = new List<int>();
    foreach (int i in intList)
    {
    if (i % 2 == 0) evenList.Add(i);
    }
    List<int> squareList = new List<int>();
    foreach (int i in evenList) squareList.Add(i * i);
    squareList.Sort();
    return squareList;
    }
    

      我想问一下,这段代码给您的感觉是什么?它给我的感觉是:做了很多事情。有哪些呢?

    1. 新建一个整数列表intList,把参数strList中所有元素转化为整型保存起来。
    2. 新建一个整数列表evenList,把intList中的偶数保存起来。
    3. 新建一个整数列表squareList,把evenList中所有数字的平方保存起来。
    4. 将squareList排序。
    5. 返回squareList。

      您可能会问:“当然如此,还能怎么样?”。事实上,如果使用了Lambda表达式,代码就简单多了:

    static List<int> GetSquaresOfPositiveByLambda(List<string> strList)
    {
    return strList
    .Select(s => Int32.Parse(s)) // 转成整数
    .Where(i => i % 2 == 0) // 找出所有偶数
    .Select(i => i * i) // 算出每个数的平方
    .OrderBy(i => i) // 按照元素自身排序
    .ToList(); // 构造一个List
    }
    

      配合.NET 3.5中定义的扩展方法,这段代码可谓“一气呵成”(在实际编码过程中,老赵更倾向于把这种简短的“递进式”代码写作一行)。那么这行代码的“语义”又有 什么变化呢?在这里,“语义”的变化在于代码的关注点从“怎么做”变成了“做什么”。这就是Lambda表达式的优势。

      在第一个方法中,我们构造了多个容器,然后做一些转化,过滤,并且向容器填充内容。其实这些都是“怎么做”,也就是所谓的“how (to do)”。但是这些代码并不能直接表示我们想要做的事情,我们想要做的事情其实是“得到XXX”,“筛选出YYY”,而不是“创建容器”,“添加元素”等 操作。

      在使用Lambda表达式的实现中,代码变得“声明式(declarative)”了许多。所谓“声明式”,便是“声称代码在做什么”,而不像 “命令式(imperative)”的代码在“操作代码怎么做”。换句话说,“声明式”关注的是“做什么”,是指“what (to do)”。上面这段声明式的代码,其语义则变成了:

    1. 把字符串转化为整数
    2. 筛选出所有偶数
    3. 把每个偶数平方一下
    4. 按照平方结果自身排序 
    5. 生成一个列表

      至于其中具体是怎么实现的,有没有构造新的容器,又是怎么向容器里添加元素的……这些细节,使用Lambda表达式的代码一概不会关心——这又不是我们想要做的事情,为什么要关心它呢?

      虽然扩展方法功不可没,但我认为,Lambda表达式在这里的重要程度尤胜前者,因为它负责了最关键的“语义”。试想,“i => i * i”给您的感觉是什么呢?是构造了一个委托吗(当然,您一定知道在这里其实构造了一个匿名方法)?至少对我来说,它的含义是“把i变成i * i”;同样,“i => i % 2 == 0”给我的感觉是“(筛选标准为)i模2等于零”,而不是“构造一个委托,XXX时返回true,否则返回false”;更有趣的是,OrderBy(i => i)给我的感觉是“把i按照i自身排序”,而不是“一个返回i自身的委托”。这一切,都是在“声明”这段代码在“做什么”,而不是“怎么做”。

      没错,“类型推演”,“省略括号”和“省略return关键字”可能的确都是些“细小”的功能,但也正是这些细微之处带来了编码方式上的关键性改变。

    使用示例二

      使用Lambda表达式还可以节省许多代码(相信您从第一个示例中也可以看出来了)。不过我认为,最省代码的部分更应该可能是其“分组”和“字典转化”等功能。因此,我们来看第二个示例。

      这个示例可能更加贴近现实。不知您是否关注过某些书籍后面的“索引”,它其实就是“列出所有的关键字,根据其首字母进行分组,并且要求对每组内部的关键字进行排序”。简单说来,我们需要的其实是这么一个方法:

    static Dictionary<char, List<string>> GetIndex(IEnumerable<string> keywords) { ... }

      想想看,您会怎么做?其实不难(作为示例,我们这里只关注小写英文,也不关心重复关键字这种特殊情况):

    static Dictionary<char, List<string>> GetIndex(IEnumerable<string> keywords)
    {
    // 定义字典
    var result = new Dictionary<char, List<string>>();
    // 填充字典
    foreach (var kw in keywords)
    {
    var firstChar = kw[0];
    List<string> groupKeywords;
    if (!result.TryGetValue(firstChar, out groupKeywords))
    {
    groupKeywords = new List<string>();
    result.Add(firstChar, groupKeywords);
    }
    groupKeywords.Add(kw);
    }
    // 为每个分组排序
    foreach (var groupKeywords in result.Values)
    {
    groupKeywords.Sort();
    }
    return result;
    }
    

      那么如果利用Lambda表达式及.NET框架中定义的扩展方法,代码又会变成什么样呢?请看:

    static Dictionary<char, List<string>> GetIndexByLambda(IEnumerable<string> keywords)
    {
    return keywords
    .GroupBy(k => k[0]) // 按照首字母分组
    .ToDictionary( // 构造字典
    g => g.Key, // 以每组的Key作为键
    g => g.OrderBy(k => k).ToList()); // 对每组排序并生成列表
    }
    

      光从代码数量上来看,前者便是后者的好几倍。而有关“声明式”,“what”等可读性方面的优势就不再重复了,个人认为它比上一个例子给人的“震撼”有过之而无不及。

      试想,如果我们把GetIndexByLambda方法中的Lambda表达式改成.NET 2.0中delegate形式的写法:

    static Dictionary<char, List<string>> GetIndexByDelegate(IEnumerable<string> keywords)
    {
    return keywords
    .GroupBy(delegate(string k) { return k[0]; })
    .ToDictionary(
    delegate(IGrouping<char, string> g) { return g.Key; },
    delegate(IGrouping<char, string> g)
    {
    return g.OrderBy(delegate(string s) { return s; }).ToList();
    });
    }
    

      您愿意编写这样的代码吗?

      因此,Lambda表达式在这里还是起着决定性的作用。事实上正是因为有了Lambda表达式,.NET中的一些函数式编程特性才被真正推广开 来。“语言特性”决定“编程方式”的确非常有道理。这一点上Java是一个很好的反例:从理论上说,Java也有“内联”的写法,但是C#的使用快感在 Java那边还只能是个梦。试想GetIndexByLambda在Java中会是什么情况3

    public Dictionary<Char, List<String>> GetIndexInJava(Enumerable<String> keywords)
    {
    return keywords
    .GroupBy(
    new Func<String, Char> {
    public Char execute(String s) { return s.charAt(0); }
    })
    .ToDictionary(
    new Func<Grouping<Char, String>, Char> {
    public Char execute(IGrouping<Char, String> g) { return g.getKey(); }
    },
    new Func<Grouping<Char, String>, List<string>> {
    public List<String> execute(IGrouping<Char, String> g)
    {
    return g
    .OrderBy(
    new Func<String, String> {
    public String execute(String s) { return s; }
    })
    .ToList();
    }
    });
    }

      一股语法噪音的 气息扑面而来,让人无法抵挡。由于Java中的匿名类型语法(即上面这种内联写法)连类型信息(new Func<String, Char>{ … }这样的代码)都无法省去,因此给人非常繁琐的感觉。面对这样的代码,您可能会有和我一样的想法:“还不如最普通的写法啊”。没错,这种函数式编程的风 格,由于缺乏语言特性支持,实在不适合在Java语言中使用。事实上,这种内联写法很早就出现了(至少在02、03年我还在使用Java的时候就已经有 了),但是那么多年下来一点改进都没有。而Lambda表达式出现之后,社区中立即跟进了大量项目,如MoqFluent NHibernate等等,充分运用了C# 3.0的这一新特性。难道这还不够说明问题吗?

      对了,再次推荐一下Scala语言,它的代码可以写的和C#一样漂亮。我不是Java平台的粉丝,更是Java语言的忠实反对者,但是我对Java平台上的Scala语言和开源项目都抱有强烈的好感。

      既然谈到了函数式编程,那么就顺便再多说几句。其实这两个例子都有浓厚的函数式编程影子在里面,例如,对于函数试编程来说,Where常被叫做 filter,Select常被叫做map。而.NET 3.5中定义的另一些方法在函数式编程里都有体现(如Aggregate相当于fold)。如果您对这方面感兴趣,可以关注Matthew Poswysocki提出的Functional C#类库。

    总结

      既可以提高可读性,又能够减少代码数量,我实在找不出任何理由拒绝Lambda表达式。

      哦,对了,您可能会提到“性能”,这的确也是一个重要的方面,不过关于这个话题我们下次再谈。受篇幅限制,原本计划的“上”“下”两篇这次又不得不拆开了。至于其他的内容,也等讨论完性能问题之后再说吧。

      当然,世界上没有东西是完美的,如果您觉得Lambda表达式在某些时候会给您带来“危害”,那么也不妨使用delegate代替Lambda 表达式。例如,为了代码清晰,在某些时候还是显式地指明参数类型比较好。不过对我而言,在任何情况下我都会使用Lambda表达式——最多使用“(int a, string b) =>”的形式咯,我想总比“delegate(int a, string b)”要统一、省事一些吧。

    相关文章

     

    注1:严格说来,这里的body是一个“语句(statement)”,而不是“表达式 (expression)”。因为一个委托其实是一个方法,因此使用Lambda来表示一个委托,其中必然要包含“语句”。不过在目前的C# 中,Lambda表达式还有一个作用是构造一颗“表达式树”,而目前的C#编译器只能构造“表达式树”而不是“语句树”。

    注2:事实上,在.NET 2.0使用delegate关键字定义匿名方法时已经可以有些许“类型推演”的意味了——虽然还是必须写明参数的类型,但是我们已经可以省略委托的类型了,不是吗?

    注3:除非我们补充Func、Enumerable,Dictionary,Grouping等类型及API,否则这段代码在Java中是无法编译通过的。事实上,这段Java代码是我在记事本中写出来的。不过这个形式完全正确。

    [C#]从.NET中委托写法的演变谈开去(上):委托与匿名方法

    mikel阅读(636)

      在《关于最近面试的一点感想》 一文中,Michael同学谈到他在面试时询问对方“delegate在.net framework1.1,2.0,3.5各可以怎么写”这个问题。于是乎,有朋友回复道“请问楼主,茴香豆的茴有几种写法”,“当代孔乙己”,独乐,众 乐。看了所有的评论,除了某些朋友认为“的确不该不知道这个问题”之外,似乎没有什么人在明确支持楼主。

      不过我支持,为什么?因为我也提过出这样的问题。

      这样,我们暂且不提应聘“高级开发人员”的人,在“自称熟悉各版本.NET框架”的前提下,是否应该知道这个答案。我们也暂且不提Michael同学提问的“目的”是什么。老赵就先单独针对这个问题进行解释,然后谈谈自己为什么会提出这个问题吧。

       可能有一件事情需要说在前面,那就是:委托本身其实从来没有改变过,改变的一直都是委托的“写法”。因此更确切地说,改变的只是“编译器”。而本文所有 内容都用C#来实现,其实谈得也都是C#编译器本身——但是其实VB.NET也有变化啊。再由于.NET版本和C#版本的关系也是非常密切的,因此全文就 使用.NET版本进行指代了。

    .NET 1.x中委托的写法

      委托,如果不追究细节,从表面上来看我们可以将其通俗地理解为一个安全的“函数指针”。当然,这个函数指针其实也是一个对象,有自己的成员,也会封装了被调用方的上下文等等。至于委托的定义和使用方式,则是这样的:

    public delegate int SomeDelegate(string arg1, bool arg2);
    public static int SomeMethod(string arg1, bool arg2) { return 0; }
    public class SomeClass
    {
    public int SomeMethod(string a1, bool a2) { return 0; }
    public event SomeDelegate SomeEvent;
    }
    static void Main(string[] args)
    {
    SomeClass someClass = new SomeClass();
    SomeDelegate someDelegate = new SomeDelegate(someClass.SomeMethod);
    someClass.SomeEvent += new SomeDelegate(SomeMethod);
    }
    

      可见,在.NET 1.x中需要使用new DelegateType(…)的方式来创建一个委托对象。不过,作为委托对象内部的方法它既可以是实例方法,也可以是静态方法。此外,方法只需要匹配委托类型的签名和返回值即可,方法参数的名称不会成为约束。

      嗯,就是这么简单。

    .NET 2.0中委托的写法

      .NET中的委托引入了范型,且写法略有简化:

    public delegate TResult MyFunc<T1, T2, TResult>(T1 a1, T2 a2);
    public static int SomeMethod(string a1, bool a2) { return 0; }
    static void Main(string[] args)
    {
    MyFunc<string, bool, int> myFunc = SomeMethod;
    }
    

      在.NET 2.0中,new DelegateType已经可以省略,开发人员可以直接将方法赋值给一个委托对象的引用。当然,这个改进不值一提,.NET 2.0中委托写法的关键在于引入了“匿名方法”:

    public static void TestRequest(string url)
    {
    WebRequest request = HttpWebRequest.Create(url);
    request.BeginGetResponse(delegate(IAsyncResult ar)
    {
    using (WebResponse response = request.EndGetResponse(ar))
    {
    Console.WriteLine("{0}: {1}", url, response.ContentLength);
    }
    },
    null);
    }
    

      匿名方法,简单地说就是内联在方法内部的委托对象,它的关键便在于形成了一个闭包(委托执行时所需的上下文)。如上面的代码 中,BeginGetResponse的第一个参数(委托)可以直接使用TestRequest方法的参数url,以及方法内的“局部”变量 request。如果没有匿名函数这个特性的话,代码写起来就麻烦了,例如在.NET 1.x中您可能就必须这么写:

    折叠
    public static void TestRequest(string url)
    {
    WebRequest request = HttpWebRequest.Create(url);
    object[] context = new object[] { url, request };
    request.BeginGetResponse(TestAsyncCallback, context);
    }
    public static void TestAsyncCallback(IAsyncResult ar)
    {
    object[] context = (object[])ar.AsyncState;
    string url = (string)context[0];
    WebRequest request = (WebRequest)context[1];
    using (WebResponse response = request.EndGetResponse(ar))
    {
    Console.WriteLine("{0}: {1}", url, response.ContentLength);
    }
    }
    

      此时,我们往往会发现,开发人员需要花费大量的精力,为一小部分代码维护一大段上下文。例如在这段代码中,我们会将url和request对象 塞入一个object数组中,在回调函数中再通过危险的Cast操作恢复数据。如果您希望“强类型”,那么只能为每个回调创建一个新的上下文对象,维护起 来可能更加麻烦——要知道,在并行编程,异步调用越来越重要的今天,如果没有匿名方法自动保留上下文的特性,开发人员会为这些“额外工作”疲于奔命的。

      可能您会说,匿名方法的可读性不佳,因为需要“内联”。一个方法中内联太多,维护成本就上去了,所以匿名方法并不推荐使用。我想说的是,您错了。如果为了可维护性,要将方法独立拆开,也可以利用匿名方法的优势:

    public static void TestRequest(string url)
    {
    WebRequest request = HttpWebRequest.Create(url);
    request.BeginGetResponse(delegate(IAsyncResult ar)
    {
    TestAsyncCallback(ar, request, url);
    }, null);
    }
    public static void TestAsyncCallback(IAsyncResult ar, WebRequest request, string url)
    {
    using (WebResponse response = request.EndGetResponse(ar))
    {
    Console.WriteLine("{0}: {1}", url, response.ContentLength);
    }
    }
    

      如果借助.NET 3.5中的Lambda表达式,代码可以写的更简单易读:

    public static void TestRequest(string url)
    {
    WebRequest request = HttpWebRequest.Create(url);
    request.BeginGetResponse(ar => TestAsyncCallback(ar, request, url), null);
    }
    

    匿名方法的作用

      千万不要小看匿名方法的作用,有些时候您认为它的作用仅限于上文描述,只是因为没有在某些问题上踏前一步。例如,对于那些只需要“按需创建”,且要“线程安全”的对象,您会怎么做呢?没错,可以使用Double Check:

    private object m_mutex = new object();
    private bool m_initialized = false;
    private BigInstance m_instance = null;
    public BigInstance Instance
    {
    get
    {
    if (!this.m_initialized)
    {
    lock (this.m_mutex)
    {
    if (!this.m_initialized)
    {
    this.m_instance = new BigInstance();
    this.m_initialized = true;
    }
    }
    }
    return this.m_instance;
    }
    }
    

      嗯,做的很漂亮!那么……这样的属性再来一个,再来三个,再来五个呢?可能有些朋友就会开始大段地Copy & Paste,于是错误便难免了。这里有一件真人真事,以前某位同学在一堆这样的代码中迷茫了,说为什么用了这种方法,还是初始化了多次对象了?检查了半天 没有看出问题来。最后发现,原因是访问了错误的initialized变量(例如,在某个应该访问artistInitialized的地方访问了 articleInitialized)。可惜,大段时间已经被浪费了——更糟的是,心情也随之变差了。

      其实,Copy & Paste很明显没有遵守DRY原则啊。为什么不把它们封装在一处呢?例如:

    折叠
    public class Lazy<T>
    {
    public Lazy(Func<T> func)
    {
    this.m_initialized = false;
    this.m_func = func;
    this.m_mutex = new object();
    }
    private Func<T> m_func;
    private bool m_initialized;
    private object m_mutex;
    private T m_value;
    public T Value
    {
    get
    {
    if (!this.m_initialized)
    {
    lock (this.m_mutex)
    {
    if (!this.m_initialized)
    {
    this.m_value = this.m_func();
    this.m_func = null;
    this.m_initialized = true;
    }
    }
    }
    return this.m_value;
    }
    }
    }
    

      于是,之前的代码就可以简化成这样了:

    private Lazy<BigInstance> m_lazyInstance =
    new Lazy<BigInstance>(delegate { return new BigInstance(); });
    public BigInstance Instance { get { return this.m_lazyInstance.Value; } }
    

      还是太丑,上Lambda表达式!

    private Lazy<BigInstance> m_lazyInstance =
    new Lazy<BigInstance>(() => new BigInstance());
    public BigInstance Instance { get { return this.m_lazyInstance.Value; } }
    

      如果没有匿名方法,许多容易使用的编程模型和方式都难以开展。例如,我们就不会有CacheHelper,也不会有AsyncTaskDispatcher(上),也很难利用“延迟”所带来的便利,更难以出现微软并行扩展、CCR等优秀框架。可以这么说,如果您不善于使用委托,您如果不知道如何合适地使用匿名方法,您在不自知的情况下可能就已经编写了大量额外的代码了。

      老赵平时的工作之一,便是为项目提供各种扩展API,可以让程序员们更愉快地进行开发工作,得到更好的生产力,让代码变得更加美好。如今C#有了匿名方法、Lambda表达式、表达式树、扩展方法等优秀的语言特性,真让我有“如鱼得水”的感觉。因此,我对于Java这样不思进取的语言可以说深恶痛绝(Java朋友们赶快学习Scala吧)。在看阅读大量Java开源项目代码时,我常有这样的感觉:“如果是C#的话,利用匿名方法,这个类不就可以不写,那个类就可以省略……”。没错,为了保留回调函数的上下文而创建一些类,对于C#程序员来说,的确是一件有些不可思议的事情。

      至于Lambda表达式以及其他话题,我们下次再说吧。

    匿名方法的缺点

      匿名方法的优势在于自动形成闭包,而它的缺点也是让程序员“不自觉”地创建了闭包,这会让某些对象的生命周期加长。例如在一开始的 TestRequest方法中,表面上看起来url是参数,request是局部变量,有些朋友可能会认为它们在方法退出后就已经准备回收了。不过因为形 成了闭包,url和request已经“升级”为一个对象的域变量,它的生命周期延长了,延长至回调函数执行完毕。因此,一不注意可能就会产生一些莫名其妙的情况

      其实,这些都是“延迟”所带来的陷阱,作为一个优秀的开发人员,除了知道某个东西的作用和优势,也要知道它的问题,不是吗?

    总结

      您现在还觉得.NET中的“委托”是一个简单的,只适合“初学者”,一搜就都知道的概念吗?

      您可能会说“是”,不过对我来说这真不简单,我也是慢慢才意识到这些的。虽然关于委托的大量内容可以在互联网上搜索而来,但还是有太多东西是需要在大量编程实践中积累下来的——等一下,这不就是“高级开发人员”和“初学者”的主要区别之一吗?

      嘲笑孔乙己的朋友们,你们在一味鄙视“茴”的四种写法的同时,说不定也失去了一个了解中国传统文化的机会呢!

      剩下的下次再说吧,Lambda表达式还等着我们。