﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>博客园-真知灼见</title><link>http://www.cnblogs.com/babilone/</link><description /><language>zh-cn</language><lastBuildDate>Sat, 22 Nov 2008 17:54:54 GMT</lastBuildDate><pubDate>Sat, 22 Nov 2008 17:54:54 GMT</pubDate><ttl>60</ttl><item><title>IIS错误_安装程序无法复制文件文件解决办法</title><link>http://www.cnblogs.com/babilone/archive/2008/07/14/1242277.html</link><dc:creator>农民工人笑了</dc:creator><author>农民工人笑了</author><pubDate>Mon, 14 Jul 2008 02:35:00 GMT</pubDate><guid>http://www.cnblogs.com/babilone/archive/2008/07/14/1242277.html</guid><wfw:comment>http://www.cnblogs.com/babilone/comments/1242277.html</wfw:comment><comments>http://www.cnblogs.com/babilone/archive/2008/07/14/1242277.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/babilone/comments/commentRss/1242277.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/babilone/services/trackbacks/1242277.html</trackback:ping><description><![CDATA[<h2><span style="font-size: 12pt">问　　题：windows2003重装iis6.0的时候出现&#8220;安装程序无法复制文件文件IISApp.vbs&#8221;等，重装的时候遇到&#8220;安装程序无法复制文件IISApp.vbs&#8221;等一系列错误.</span></h2>
<div class="postBody">
<p><span style="font-size: 12pt">分　</span>　析：这就是典型的windows安全数据库出问题了，可以这样来做</p>
<p>参考文章：Windows安全资料库，在%WinDir%\Security\database里。台湾的资料库，大陆称为数据库。在Windows作业系统里带有专门的Esentutl.exe工具，这是一个DOS工具，可用来查看和修复Windows安全资料库。</p>
<p>比如我有一次在安装IIS的元件时，发生了错误：<br />
************************<br />
复制错误 安装程式无法复制档 iisapp.vbs。请确认下面指定的位置是正确的，或者更改它并在指定的驱动器中插入 'Service Pack 1 CD-ROM'。<br />
当复制来源: C:\Windows\ServicePackFiles\i386 [浏览] [重试] [取消]</p>
<p>************************<br />
这就是由于Windows安全资料库损坏所致。可用Esentutl.exe进行修复。</p>
<p>查看它的用法，用下面指令： esentutl /? 会显示如下提示：<br />
Microsoft(R) Windows(R) Database Utilities Version 5.2 Copyright (C) Microsoft Corporation. All Rights Reserved. DESCRIPTION: Maintenance utilities for Microsoft(R) Windows(R) databases. MODES OF OPERATION: Defragmentation: ESENTUTL /d [options] Recovery: ESENTUTL /r [options] Integrity: ESENTUTL /g [options] Checksum: ESENTUTL /k [options] Repair: ESENTUTL /p [options] File Dump: ESENTUTL /m[mode-modifier] &lt;&lt;&lt;&lt;&lt; Press a key for more help &gt;&gt;&gt;&gt;&gt;<br />
D=Defragmentation, R=Recovery, G=inteGrity, K=checKsum, P=rePair, M=file duMp =&gt;</p>
<p>可见，检查资料库的完整性，要用/g，比如： esentutl /g C:\Windows\security\database\secedit.sdb</p>
<p>若要修复这个Windows安全资料库，用如下指令： esentutl /p C:\Windows\security\database\secedit.sdb</p>
<p>解决方法：esentutl /g C:\Windows\security\database\secedit.sdb<br />
　　　　　esentutl /p C:\Windows\security\database\secedit.sdb</p>
</div>
<img src ="http://www.cnblogs.com/babilone/aggbug/1242277.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43629/" target="_blank">[新闻][译稿]微软将 jQuery IntelliSense整合到Visual Studio</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>C#[Serializable]在C#中的作用-NET 中的对象序列化</title><link>http://www.cnblogs.com/babilone/archive/2008/07/02/1234038.html</link><dc:creator>农民工人笑了</dc:creator><author>农民工人笑了</author><pubDate>Wed, 02 Jul 2008 07:20:00 GMT</pubDate><guid>http://www.cnblogs.com/babilone/archive/2008/07/02/1234038.html</guid><wfw:comment>http://www.cnblogs.com/babilone/comments/1234038.html</wfw:comment><comments>http://www.cnblogs.com/babilone/archive/2008/07/02/1234038.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/babilone/comments/commentRss/1234038.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/babilone/services/trackbacks/1234038.html</trackback:ping><description><![CDATA[<p class="g_w_100 g_t_wrap g_t_center g_t_bold g_t_24 g_c_pdin c07" id="blogtitle_fks_084065093080087066084086095095087085083065093086083064">&nbsp;&nbsp;&nbsp; 为什么要使用序列化？最重要的两个原因是：将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本；按值将对象从一个应用程序域发送至另一个应用程序域。例如，序列化可用于在 ASP.NET 中保存会话状态，以及将对象复制到 Windows 窗体的剪贴板中。它还可用于按值将对象从一个应用程序域远程传递至另一个应用程序域。本文简要介绍了 Microsoft .NET 中使用的序列化。</p>
<div class="g_blog_list">
<div>
<p>一.简介<br />
&nbsp;&nbsp;&nbsp; 序列化是指将对象实例的状态存储到存储媒体的过程。在此过程中，先将对象的公共字段和私有字段以及类的名称（包括类所在的程序集）转换为字节流，然后再把字节流写入数据流。在随后对对象进行反序列化时，将创建出与原对象完全相同的副本。<br />
&nbsp;&nbsp;&nbsp; 在面向对象的环境中实现序列化机制时，必须在易用性和灵活性之间进行一些权衡。只要您对此过程有足够的控制能力，就可以使该过程在很大程度上自动进行。例如，简单的二进制序列化不能满足需要，或者，由于特定原因需要确定类中那些字段需要序列化。以下各部分将探讨 .NET 框架提供的可靠的序列化机制，并着重介绍使您可以根据需要自定义序列化过程的一些重要功能。<br />
二.持久存储<br />
&nbsp;&nbsp;&nbsp; 我们经常需要将对象的字段值保存到磁盘中，并在以后检索此数据。尽管不使用序列化也能完成这项工作，但这种方法通常很繁琐而且容易出错，并且在需要跟踪对象的层次结构时，会变得越来越复杂。可以想象一下编写包含大量对象的大型业务应用程序的情形，程序员不得不为每一个对象编写代码，以便将字段和属性保存至磁盘以及从磁盘还原这些字段和属性。序列化提供了轻松实现这个目标的快捷方法。<br />
&nbsp;&nbsp;&nbsp; 公共语言运行时 (CLR) 管理对象在内存中的分布，.NET 框架则通过使用反射提供自动的序列化机制。对象序列化后，类的名称、程序集以及类实例的所有数据成员均被写入存储媒体中。对象通常用成员变量来存储对其他实例的引用。类序列化后，序列化引擎将跟踪所有已序列化的引用对象，以确保同一对象不被序列化多次。.NET 框架所提供的序列化体系结构可以自动正确处理对象图表和循环引用。对对象图表的唯一要求是，由正在进行序列化的对象所引用的所有对象都必须标记为 Serializable（请参阅基本序列化）。否则，当序列化程序试图序列化未标记的对象时将会出现异常。当反序列化已序列化的类时，将重新创建该类，并自动还原所有数据成员的值。<br />
三.按值封送<br />
&nbsp;&nbsp;&nbsp; 对象仅在创建对象的应用程序域中有效。除非对象是从 MarshalByRefObject 派生得到或标记为 Serializable，否则，任何将对象作为参数传递或将其作为结果返回的尝试都将失败。如果对象标记为 Serializable，则该对象将被自动序列化，并从一个应用程序域传输至另一个应用程序域，然后进行反序列化，从而在第二个应用程序域中产生出该对象的一个精确副本。此过程通常称为按值封送。<br />
&nbsp;&nbsp;&nbsp; 如果对象是从 MarshalByRefObject 派生得到，则从一个应用程序域传递至另一个应用程序域的是对象引用，而不是对象本身。也可以将从 MarshalByRefObject 派生得到的对象标记为 Serializable。远程使用此对象时，负责进行序列化并已预先配置为 SurrogateSelector 的格式化程序将控制序列化过程，并用一个代理替换所有从 MarshalByRefObject 派生得到的对象。如果没有预先配置为 SurrogateSelector，序列化体系结构将遵从下面的标准序列化规则（请参阅序列化过程的步骤）。<br />
四.基本序列化<br />
<font color="#ff0000">使用二进制格式化程序执行序列化时，一个类的所有成员变量都将被序列化，即使是那些已被标记为私有的变量。</font>在此方面，二进制序列化不同于 XMLSerializer 类，后者只序列化公共字段。<br />
&nbsp;&nbsp;&nbsp; 要使一个类可序列化，最简单的方法是使用 Serializable 属性对它进行标记，如下所示：<br />
[Serializable]<br />
public class MyObject {<br />
&nbsp;&nbsp; public int n1 = 0;<br />
&nbsp;&nbsp; public int n2 = 0;<br />
&nbsp;&nbsp; public String str = null;<br />
}<br />
以下代码片段说明了如何将此类的一个实例序列化为一个文件：<br />
MyObject obj = new MyObject();<br />
obj.n1 = 1;<br />
obj.n2 = 24;<br />
obj.str = "一些字符串";<br />
IFormatter formatter = new BinaryFormatter();<br />
Stream stream = new FileStream("MyFile.bin", FileMode.Create, <br />
FileAccess.Write, FileShare.None);<br />
formatter.Serialize(stream, obj);<br />
stream.Close();<br />
&nbsp;&nbsp;&nbsp; 本例使用二进制格式化程序进行序列化。您只需创建一个要使用的流和格式化程序的实例，然后调用格式化程序的 Serialize 方法。流和要序列化的对象实例作为参数提供给此调用。类中的所有成员变量（甚至标记为 private 的变量）都将被序列化，但这一点在本例中未明确体现出来。在这一点上，二进制序列化不同于只序列化公共字段的 XML 序列化程序。<br />
将对象还原到它以前的状态也非常容易。首先，创建格式化程序和流以进行读取，然后让格式化程序对对象进行反序列化。以下代码片段说明了如何进行此操作。<br />
IFormatter formatter = new BinaryFormatter();<br />
Stream stream = new FileStream("MyFile.bin", FileMode.Open, <br />
FileAccess.Read, FileShare.Read);<br />
MyObject obj = (MyObject) formatter.Deserialize(fromStream);<br />
stream.Close();<br />
// 下面是证明<br />
Console.WriteLine("n1: {0}", obj.n1);<br />
Console.WriteLine("n2: {0}", obj.n2);<br />
Console.WriteLine("str: {0}", obj.str);<br />
&nbsp;&nbsp;&nbsp; 上面所使用的 BinaryFormatter 效率很高，能生成非常紧凑的字节流。所有使用此格式化程序序列化的对象也可使用它进行反序列化，对于序列化将在 .NET 平台上进行反序列化的对象，此格式化程序无疑是一个理想工具。需要注意的是，对对象进行反序列化时并不调用构造函数。对反序列化添加这项约束，是出于性能方面的考虑。但是，这违反了对象编写者通常采用的一些运行时约定，因此，开发人员在将对象标记为可序列化时，应确保考虑了这一特殊约定。<br />
如果要求具有可移植性，请使用 SoapFormatter。所要做的更改只是将以上代码中的格式化程序换成 SoapFormatter，而 Serialize 和 Deserialize 调用不变。对于上面使用的示例，该格式化程序将生成以下结果。<br />
&lt;SOAP-ENV:Envelope<br />
&nbsp;&nbsp; xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance<br />
&nbsp;&nbsp; xmlns:xsd="<a href="http://www.w3.org/2001/XMLSchema">http://www.w3.org/2001/XMLSchema</a>" <br />
&nbsp;&nbsp; xmlns:SOAP- ENC=http://schemas.xmlsoap.org/soap/encoding/<br />
&nbsp;&nbsp; xmlns:SOAP- ENV=http://schemas.xmlsoap.org/soap/envelope/<br />
&nbsp;&nbsp; SOAP-ENV:encodingStyle=<br />
&nbsp;&nbsp; "<a href="http://schemas.microsoft.com/soap/encoding/clr/1.0">http://schemas.microsoft.com/soap/encoding/clr/1.0</a><br />
<a href="http://schemas.xmlsoap.org/soap/encoding/">http://schemas.xmlsoap.org/soap/encoding/</a>"<br />
&nbsp;&nbsp; xmlns:a1="<a href="http://schemas.microsoft.com/clr/assem/ToFile">http://schemas.microsoft.com/clr/assem/ToFile</a>"&gt;<br />
&nbsp;&nbsp; &lt;SOAP-ENV:Body&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp; &lt;a1:MyObject id="ref-1"&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;n1&gt;1&lt;/n1&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;n2&gt;24&lt;/n2&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;str id="ref-3"&gt;一些字符串&lt;/str&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp; &lt;/a1:MyObject&gt;<br />
&nbsp;&nbsp; &lt;/SOAP-ENV:Body&gt;<br />
&lt;/SOAP-ENV:Envelope&gt;<br />
&nbsp;&nbsp;&nbsp; 需要注意的是，无法继承 Serializable 属性。如果从 MyObject 派生出一个新的类，则这个新的类也必须使用该属性进行标记，否则将无法序列化。例如，如果试图序列化以下类实例，将会显示一个 SerializationException，说明 MyStuff 类型未标记为可序列化。<br />
public class MyStuff : MyObject <br />
{<br />
&nbsp;&nbsp; public int n3;<br />
}<br />
&nbsp;&nbsp;&nbsp; 使用序列化属性非常方便，但是它存在上述的一些限制。有关何时标记类以进行序列化（因为类编译后就无法再序列化），请参考有关说明（请参阅下面的序列化规则）。<br />
五.选择性序列化<br />
&nbsp;&nbsp;&nbsp; 类通常包含不应被序列化的字段。例如，假设某个类用一个成员变量来存储线程 ID。当此类被反序列化时，序列化此类时所存储的 ID 对应的线程可能不再运行，所以对这个值进行序列化没有意义。可以通过使用 NonSerialized 属性标记成员变量来防止它们被序列化，如下所示：<br />
[Serializable]<br />
public class MyObject <br />
{<br />
&nbsp;&nbsp; public int n1;<br />
&nbsp;&nbsp; [NonSerialized] public int n2;<br />
&nbsp;&nbsp; public String str;<br />
}<br />
六.自定义序列化<br />
&nbsp;&nbsp;&nbsp; 可以通过在对象上实现 ISerializable 接口来自定义序列化过程。这一功能在反序列化后成员变量的值失效时尤其有用，但是需要为变量提供值以重建对象的完整状态。要实现 ISerializable，需要实现 GetObjectData 方法以及一个特殊的构造函数，在反序列化对象时要用到此构造函数。以下代码示例说明了如何在前一部分中提到的 MyObject 类上实现 ISerializable。<br />
[Serializable]<br />
public class MyObject : ISerializable <br />
{<br />
&nbsp;&nbsp; public int n1;<br />
&nbsp;&nbsp; public int n2;<br />
&nbsp;&nbsp; public String str;<br />
&nbsp;&nbsp; public MyObject()<br />
&nbsp;&nbsp; {<br />
&nbsp;&nbsp; }<br />
&nbsp;&nbsp; protected MyObject(SerializationInfo info, StreamingContext context)<br />
&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp; n1 = info.GetInt32("i");<br />
&nbsp;&nbsp;&nbsp;&nbsp; n2 = info.GetInt32("j");<br />
&nbsp;&nbsp;&nbsp;&nbsp; str = info.GetString("k");<br />
&nbsp;&nbsp; }<br />
&nbsp;&nbsp; public virtual void GetObjectData(SerializationInfo info, <br />
StreamingContext context)<br />
&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp; info.AddValue("i", n1);<br />
&nbsp;&nbsp;&nbsp;&nbsp; info.AddValue("j", n2);<br />
&nbsp;&nbsp;&nbsp;&nbsp; info.AddValue("k", str);<br />
&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp; 在序列化过程中调用 GetObjectData 时，需要填充方法调用中提供的SerializationInfo 对象。只需按名称/值对的形式添加将要序列化的变量。其名称可以是任何文本。只要已序列化的数据足以在反序列化过程中还原对象，便可以自由选择添加至 SerializationInfo 的成员变量。如果基对象实现了 ISerializable，则派生类应调用其基对象的 GetObjectData 方法。 <br />
&nbsp;&nbsp;&nbsp; 需要强调的是，将 ISerializable 添加至某个类时，需要同时实现 GetObjectData 以及特殊的构造函数。如果缺少 GetObjectData，编译器将发出警告。但是，由于无法强制实现构造函数，所以，缺少构造函数时不会发出警告。如果在没有构造函数的情况下尝试反序列化某个类，将会出现异常。在消除潜在安全性和版本控制问题等方面，当前设计优于SetObjectData 方法。例如，如果将 SetObjectData 方法定义为某个接口的一部分，则此方法必须是公共方法，这使得用户不得不编写代码来防止多次调用 SetObjectData 方法。可以想象，如果某个对象正在执行某些操作，而某个恶意应用程序却调用此对象的 SetObjectData 方法，将会引起一些潜在的麻烦。<br />
&nbsp;&nbsp;&nbsp; 在反序列化过程中，使用出于此目的而提供的构造函数将 SerializationInfo 传递给类。对象反序列化时，对构造函数的任何可见性约束都将被忽略，因此，可以将类标记为 public、protected、internal 或 private。一个不错的办法是，在类未封装的情况下，将构造函数标记为 protect。如果类已封装，则应标记为 private。要还原对象的状态，只需使用序列化时采用的名称，从 SerializationInfo 中检索变量的值。如果基类实现了 ISerializable，则应调用基类的构造函数，以使基础对象可以还原其变量。<br />
&nbsp;&nbsp;&nbsp; 如果从实现了 ISerializable 的类派生出一个新的类，则只要新的类中含有任何需要序列化的变量，就必须同时实现构造函数以及 GetObjectData 方法。以下代码片段显示了如何使用上文所示的 MyObject 类来完成此操作。<br />
[Serializable]<br />
public class ObjectTwo : MyObject<br />
{<br />
&nbsp;&nbsp; public int num;<br />
&nbsp;&nbsp; public ObjectTwo() : base()<br />
&nbsp;&nbsp; {<br />
&nbsp;&nbsp; }<br />
&nbsp;&nbsp; protected ObjectTwo(SerializationInfo si, StreamingContext context) : <br />
base(si,context)<br />
&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp; num = si.GetInt32("num");<br />
&nbsp;&nbsp; }<br />
&nbsp;&nbsp; public override void GetObjectData(SerializationInfo si, <br />
StreamingContext context)<br />
&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp; base.GetObjectData(si,context);<br />
&nbsp;&nbsp;&nbsp;&nbsp; si.AddValue("num", num);<br />
&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp; 切记要在反序列化构造函数中调用基类，否则，将永远不会调用基类上的构造函数，并且在反序列化后也无法构建完整的对象。对象被彻底重新构建，但是在反系列化过程中调用方法可能会带来不良的副作用,因为被调用的方法可能引用了在调用时尚未反序列化的对象引用。如果正在进行反序列化的类实现了 IDeserializationCallback，则反序列化整个对象图表后，将自动调用 OnSerialization 方法。此时，引用的所有子对象均已完全还原。有些类不使用上述事件侦听器，很难对它们进行反序列化，散列表便是一个典型的例子。在反序列化过程中检索关键字/值对非常容易，但是，由于无法保证从散列表派生出的类已反序列化，所以把这些对象添加回散列表时会出现一些问题。因此，建议目前不要在散列表上调用方法。<br />
七.序列化过程的步骤<br />
&nbsp;&nbsp;&nbsp; 在格式化程序上调用 Serialize 方法时，对象序列化按照以下规则进行：<br />
检查格式化程序是否有代理选取器。如果有，检查代理选取器是否处理指定类型的对象。如果选取器处理此对象类型，将在代理选取器上调用 ISerializable.GetObjectData。 <br />
如果没有代理选取器或有却不处理此类型，将检查是否使用 Serializable 属性对对象进行标记。如果未标记，将会引发 SerializationException。 <br />
如果对象已被正确标记，将检查对象是否实现了 ISerializable。如果已实现，将在对象上调用 GetObjectData。 <br />
如果对象未实现 Serializable，将使用默认的序列化策略，对所有未标记为 NonSerialized 的字段都进行序列化。 八.版本控制<br />
&nbsp;&nbsp;&nbsp; .NET 框架支持版本控制和并排执行，并且，如果类的接口保持一致，所有类均可跨版本工作。由于序列化涉及的是成员变量而非接口，所以，在向要跨版本序列化的类中添加成员变量，或从中删除变量时，应谨慎行事。特别是对于未实现 ISerializable 的类更应如此。若当前版本的状态发生了任何变化（例如添加成员变量、更改变量类型或更改变量名称），都意味着如果同一类型的现有对象是使用早期版本进行序列化的，则无法成功对它们进行反序列化。<br />
如果对象的状态需要在不同版本间发生改变，类的作者可以有两种选择：<br />
实现 ISerializable。这使您可以精确地控制序列化和反序列化过程，在反序列化过程中正确地添加和解释未来状态。 <br />
使用 NonSerialized 属性标记不重要的成员变量。仅当预计类在不同版本间的变化较小时，才可使用这个选项。例如，把一个新变量添加至类的较高版本后，可以将该变量标记为 NonSerialized，以确保该类与早期版本保持兼容。 <br />
九.序列化规则<br />
&nbsp;&nbsp;&nbsp; 由于类编译后便无法序列化，所以在设计新类时应考虑序列化。需要考虑的问题有：是否必须跨应用程序域来发送此类？是否要远程使用此类？用户将如何使用此类？也许他们会从我的类中派生出一个需要序列化的新类。只要有这种可能性，就应将类标记为可序列化。除下列情况以外，最好将所有类都标记为可序列化：<br />
&nbsp;&nbsp;&nbsp; 所有的类都永远也不会跨越应用程序域。如果某个类不要求序列化但需要跨越应用程序域，请从 MarshalByRefObject 派生此类。 <br />
&nbsp;&nbsp;&nbsp; 类存储仅适用于其当前实例的特殊指针。例如，如果某个类包含非受控的内存或文件句柄，请确保将这些字段标记为 NonSerialized 或根本不序列化此类。 <br />
某些数据成员包含敏感信息。在这种情况下，建议实现 ISerializable 并仅序列化所要求的字段。</p>
</div>
</div>
    <img src ="http://www.cnblogs.com/babilone/aggbug/1234038.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43628/" target="_blank">[新闻]微软：不裁员也不削减研发开支</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>C# 2.0：使用匿名方法、迭代程序和局部类来创建优雅的代码</title><link>http://www.cnblogs.com/babilone/articles/1192120.html</link><dc:creator>农民工人笑了</dc:creator><author>农民工人笑了</author><pubDate>Sun, 11 May 2008 04:38:00 GMT</pubDate><guid>http://www.cnblogs.com/babilone/articles/1192120.html</guid><wfw:comment>http://www.cnblogs.com/babilone/comments/1192120.html</wfw:comment><comments>http://www.cnblogs.com/babilone/articles/1192120.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/babilone/comments/commentRss/1192120.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/babilone/services/trackbacks/1192120.html</trackback:ping><description><![CDATA[本文基于 Microsoft Visual Studio 2005 的预发布版本，它以前的代码名称为&#8220;Whidbey&#8221;。此处所包含的任何信息都可能会改变。 <br />
<br />
本文讨论： <br />
<br />
&#8226; 遍历集合 <br />
<br />
&#8226; 跨文件类定义 <br />
<br />
&#8226; 与委托一起使用的匿名方法 <br />
<br />
&#8226; Visual Studio 2005 中的其他 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 新功能 <br />
<br />
<br />
本文使用下列技术： <br />
<br />
&#8226; <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 和 Visual Studio <br />
<br />
<br />
可以在此下载代码： <br />
<br />
&#8226; C20.exe (164KB) <br />
<br />
<br />
<br />
<br />
本页内容 <br />
迭代程序 <br />
迭代程序实现 <br />
递归迭代 <br />
局部类型 <br />
匿名方法 <br />
将参数传递到匿名方法 <br />
匿名方法实现 <br />
一般匿名方法 <br />
匿名方法示例 <br />
委托推理 <br />
属性和索引可见性 <br />
静态类 <br />
全局命名空间限定符 <br />
内联警告 <br />
小结 <br />
<br />
热衷于 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 语言的人会喜欢上 Visual <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2005。Visual Studio 2005 为 Visual <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2005 带来了大量令人兴奋的新功能，例如泛型、迭代程序、局部类和匿名方法等。虽然泛型是人们最常谈到的也是预期的功能，尤其是在熟悉模板的 C++ 开发人员中间，但是其他的新功能同样是对Microsoft .NET开发宝库的重要补充。与 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 的第一个版本相比，增加这些功能和语言将会提高您的整体生产效率，从而使您能够以更快的速度写出更加简洁的代码。有关泛型的一些背景知识，您应该看一看提要栏&#8220;什么是泛型？&#8221;。 <br />
<br />
迭代程序 <br />
在 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 1.1 中，您可以使用 foreach 循环来遍历诸如数组、集合这样的数据结构： <br />
<br />
string[] cities = {"New York","Paris","London"}; <br />
foreach(string city in cities) <br />
{ <br />
Console.WriteLine(city); <br />
} <br />
<br />
实际上，您可以在 foreach 循环中使用任何自定义数据集合，只要该集合类型实现了返回 IEnumerator 接口的 GetEnumerator 方法即可。通常，您需要通过实现 IEnumerable 接口来完成这些工作： <br />
<br />
public interface IEnumerable <br />
{ <br />
IEnumerator GetEnumerator(); <br />
} <br />
public interface IEnumerator <br />
{ <br />
object Current{get;} <br />
bool MoveNext(); <br />
void Reset(); <br />
} <br />
<br />
在通常情况下，用于通过实现 IEnumerable 来遍历集合的类是作为要遍历的集合类型的嵌套类提供的。此迭代程序类型维持了迭代的状态。将嵌套类作为枚举器往往较好，因为它可以访问其包含类的所有私有成员。当然，这是迭代程序设计模式，它对迭代客户端隐藏了底层数据结构的实际实现细节，使得能够在多种数据结构上使用相同的客户端迭代逻辑，如图 1 所示。 <br />
<br />
<br />
<br />
图 1 迭代程序设计模式 <br />
<br />
此外，由于每个迭代程序都保持单独的迭代状态，所以多个客户端可以执行单独的并发迭代。通过实现 IEnumerable，诸如数组和队列这样的数据结构可以支持这种超常规的迭代。在 foreach 循环中生成的代码调用类的 GetEnumerator 方法简单地获得一个 IEnumerator 对象，然后将其用于 while 循环，从而通过连续调用它的 MoveNext 方法和当前属性遍历集合。如果您需要显式地遍历集合，您可以直接使用 IEnumerator（不用求助于 foreach 语句）。 <br />
<br />
但是使用这种方法有一些问题。首先，如果集合包含值类型，则需要对它们进行装箱和拆箱才能获得项，因为 IEnumerator.Current 返回一个对象。这将导致潜在的性能退化和托管堆上的压力增大。即使集合包含引用类型，仍然会产生从对象向下强制类型转换的不利结果。虽然大多数开发人员不熟悉这一特性，但是在 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 1.0 中，实际上不必实现 IEnumerator 或 IEnumerable 就可以为每个循环实现迭代程序模式。编译器将选择调用强类型化版本，以避免强制类型转换和装箱。结果是，即使在 1.0 版本中，也可能没有导致性能损失。 <br />
<br />
为了更好地阐明这个解决方案并使其易于实现，Microsoft .NET 框架 2.0 在 System.Collections.Generics 命名空间中定义了一般的类型<a title="安全教程" href="http://www.myour.name/WengZhang/Security/index.html">安全</a>的 IEnumerable &lt;ItemType&gt; 和 IEnumerator &lt;ItemType&gt; 接口： <br />
<br />
public interface IEnumerable&lt;ItemType&gt; <br />
{ <br />
IEnumerator&lt;ItemType&gt; GetEnumerator(); <br />
} <br />
public interface IEnumerator&lt;ItemType&gt; : IDisposable <br />
{ <br />
ItemType Current{get;} <br />
bool MoveNext(); <br />
} <br />
<br />
除了利用泛型之外，新的接口与其前身还略有差别。与 IEnumerator 不同，IEnumerator &lt;ItemType&gt; 是从 IDisposable 派生而来的，并且没有 Reset 方法。图 2 中的代码显示了实现 IEnumerable &lt;string&gt; 的简单 city 集合，而图 3 显示了编译器在跨越 foreach 循环的代码时如何使用该接口。图 2 中的实现使用了名为 MyEnumerator 的嵌套类，它将一个引用作为构造参数返回给要枚举的集合。MyEnumerator 清楚地知道 city 集合（本例中的一个数组）的实现细节。MyEnumerator 类使用 m_Current 成员变量维持当前的迭代状态，此成员变量用作数组的索引。 <br />
<br />
第二个问题也是更难以解决的问题，就是迭代程序的实现。虽然对于简单的例子（如图 3所示），实现是相当简单的，但是对于更高级的数据结构，实现将非常复杂，例如二叉树，它需要递归遍历，并需在递归时维持迭代状态。另外，如果需要各种迭代选项，例如需要在一个链接表中从头到尾和从尾到头选项，则此链接表的代码就会因不同的迭代程序实现而变得臃肿。这正是设计 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 迭代程序所要解决的问题。通过使用迭代程序，您可以让 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 编译器为您生成 IEnumerator 的实现。<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 编译器能够自动生成一个嵌套类来维持迭代状态。您可以在一般集合或特定于类型的集合中使用迭代程序。您需要做的只是告诉编译器在每个迭代中产生的是什么。如同手动提供迭代程序一样，您需要公开 GetEnumerator 方法，此方法通常是通过实现 IEnumerable 或 IEnumerable &lt;ItemType&gt; 来公开的。 <br />
<br />
您可以使用新的 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 的 yield return 语句告诉编译器产生什么。例如，下面的代码显示了如何在 city 集合中使用 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 迭代程序来代替图 2 中的手动实现： <br />
<br />
public class CityCollection : IEnumerable&lt;string&gt; <br />
{ <br />
string[] m_Cities = {"New York","Paris","London"}; <br />
public IEnumerator&lt;string&gt; GetEnumerator() <br />
{ <br />
for(int i = 0; i&lt;m_Cities.Length; i++) <br />
yield return m_Cities[i]; <br />
} <br />
} <br />
<br />
您还可以在非一般集合中使用 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 迭代程序： <br />
<br />
public class CityCollection : IEnumerable <br />
{ <br />
string[] m_Cities = {"New York","Paris","London"}; <br />
public IEnumerator GetEnumerator() <br />
{ <br />
for(int i = 0; i&lt;m_Cities.Length; i++) <br />
yield return m_Cities[i]; <br />
} <br />
} <br />
<br />
此外，您还可以在完全一般的集合中使用 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 迭代程序，如图 4 所示。当使用一般集合和迭代程序时，编译器从声明集合（本例中的 string）所用的类中型知道 foreach 循环内 IEnumerable &lt;ItemType&gt; 所用的特定类型： <br />
<br />
LinkedList&lt;int,string&gt; list = new LinkedList&lt;int,string&gt;(); <br />
/* Some initialization of list, then */ <br />
foreach(string item in list) <br />
{ <br />
Trace.WriteLine(item); <br />
} <br />
<br />
这与任何其他从一般接口进行的派生相似。如果出于某些原因想中途停止迭代，请使用 yield break 语句。例如，下面的迭代程序将仅仅产生数值 １、２ 和 ３： <br />
<br />
public IEnumerator&lt;int&gt; GetEnumerator() <br />
{ <br />
for(int i = 1;i&lt; 5;i++) <br />
{ <br />
yield return i; <br />
if(i &gt; 2) <br />
yield break; <br />
} <br />
} <br />
<br />
您的集合可以很容易地公开多个迭代程序，每个迭代程序都用于以不同的方式遍历集合。例如，要以倒序遍历 CityCollection 类，提供了名为 Reverse 的 IEnumerable &lt;string&gt; 类型的属性： <br />
<br />
public class CityCollection <br />
{ <br />
string[] m_Cities = {"New York","Paris","London"}; <br />
public IEnumerable&lt;string&gt; Reverse <br />
{ <br />
get <br />
{ <br />
for(int i=m_Cities.Length-1; i&gt;= 0; i--) <br />
yield return m_Cities[i]; <br />
} <br />
} <br />
} <br />
<br />
这样就可以在 foreach 循环中使用 Reverse 属性： <br />
<br />
CityCollection collection = new CityCollection(); <br />
foreach(string city in collection.Reverse) <br />
{ <br />
Trace.WriteLine(city); <br />
} <br />
<br />
对于在何处以及如何使用 yield return 语句是有一些限制的。包含 yield return 语句的方法或属性不能再包含其他 return 语句，因为这样会错误地中断迭代。不能在匿名方法中使用 yield return 语句，也不能将 yield return 语句放到带有 catch 块的 try 语句中（也不能放在 catch 块或 finally 块中）。 <br />
<br />
返回页首 <br />
迭代程序实现 <br />
编译器生成的嵌套类维持迭代状态。当在 foreach 循环中（或在直接迭代代码中）首次调用迭代程序时，编译器为 GetEnumerator 生成的代码将创建一个带有 reset 状态的新的迭代程序对象（嵌套类的一个实例）。在 foreach 每次循环调用迭代程序的 MoveNext 方法时，它都从前一次 yield return 语句停止的地方开始执行。只要 foreach 循环执行，迭代程序就会维持它的状态。然而，迭代程序对象（以及它的状态）在多个 foreach 循环之间并不保持一致。因此，再次调用 foreach 是<a title="安全教程" href="http://www.myour.name/WengZhang/Security/index.html">安全</a>的，因为您将使新的迭代程序对象开始新的迭代。这就是为什么 IEnumerable &lt;ItemType&gt; 没有定义 Reset 方法的原因。 <br />
<br />
但是嵌套迭代程序类是如何实现的呢？并且如何管理它的状态呢？编译器将一个标准方法转换成一个可以被多次调用的方法，此方法使用一个简单的状态机在前一个 yield return 语句之后恢复执行。您需要做的只是使用 yield return 语句指示编译器产生什么以及何时产生。编译器具有足够的智能，它甚至能够将多个 yield return 语句按照它们出现的顺序连接起来： <br />
<br />
public class CityCollection : IEnumerable&lt;string&gt; <br />
{ <br />
public IEnumerator&lt;string&gt; GetEnumerator() <br />
{ <br />
yield return "New York"; <br />
yield return "Paris"; <br />
yield return "London"; <br />
} <br />
} <br />
<br />
让我们看一看在下面几行代码中显示的该类的 GetEnumerator 方法： <br />
<br />
public class MyCollection : IEnumerable&lt;string&gt; <br />
{ <br />
public IEnumerator&lt;string&gt; GetEnumerator() <br />
{ <br />
//Some iteration code that uses yield return <br />
} <br />
} <br />
<br />
当编译器遇到这种带有 yield return 语句的类成员时，它会插入一个名为 GetEnumerator$&lt;random unique number&gt;__IEnumeratorImpl 的嵌套类的定义，如图 5 中 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 伪代码所示。（记住，本文所讨论的所有特征 — 编译器生成的类和字段的名称 — 是会改变的，在某些情况下甚至会发生彻底的变化。您不应该试图使用反射来获得这些实现细节并期望得到一致的结果。）嵌套类实现了从类成员返回的相同 IEnumerable 接口。编译器使用一个实例化的嵌套类型来代替类成员中的代码，将一个指向集合的引用赋给嵌套类的 &lt;this&gt; 成员变量，类似于图 2 中所示的手动实现。实际上，该嵌套类是一个提供了 IEnumerator 的实现的类。 <br />
<br />
返回页首 <br />
递归迭代 <br />
当在像二叉树或其他任何包含相互连接的节点的复杂图形这样的数据结构上进行递归迭代时，迭代程序才真正显示出了它的优势。通过递归迭代手动实现一个迭代程序是相当困难的，但是如果使用 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 迭代程序，这将变得很容易。请考虑图 6 中的二叉树。这个二叉树的完整实现是本文所提供的源代码的一部分。这个二叉树在节点中存储了一些项。每个节点均拥有一个一般类型 T（名为Item）的值。每个节点均含有指向左边节点的引用和指向右边节点的引用。比 Item 小的值存储在左边的子树中，比 Item 大的值存储在右边的子树中。这个树还提供了 Add 方法，通过使用参数限定符添加一组开放式的 T 类型的值： <br />
<br />
public void Add(params T[] items); <br />
<br />
这棵树提供了一个 IEnumerable &lt;T&gt; 类型的名为 InOrder 的公共属性。InOrder 调用递归的私有帮助器方法 ScanInOrder，把树的根节点传递给 ScanInOrder。ScanInOrder 定义如下： <br />
<br />
IEnumerable&lt;T&gt; ScanInOrder(Node&lt;T&gt; root); <br />
<br />
它返回 IEnumerable &lt;T&gt; 类型的迭代程序的实现，此实现按顺序遍历二叉树。对于 ScanInOrder 需要注意的一件事情是，它通过递归遍历这个二叉树的方式，即使用 foreach 循环来访问从递归调用返回的 IEnumerable &lt;T&gt;。在顺序 (in-order) 迭代中，每个节点都首先遍历它左边的子树，接着遍历该节点本身的值，然后遍历右边的子树。对于这种情况，需要三个 yield return 语句。为了遍历左边的子树，ScanInOrder 在递归调用（它以参数的形式传递左边的节点）返回的 IEnumerable &lt;T&gt;上使用 foreach 循环。一旦 foreach 循环返回，就已经遍历并产生了左边子树的所有节点。然后，ScanInOrder 产生作为迭代的根传递给其节点的值，并在 foreach 循环中执行另一个递归调用，这次是在右边的子树上。通过使用属性 InOrder，可以编写下面的 foreach 循环来遍历整个树： <br />
<br />
BinaryTree&lt;int&gt; tree = new BinaryTree&lt;int&gt;(); <br />
tree.Add(4,6,2,7,5,3,1); <br />
foreach(int num in tree.InOrder) <br />
{ <br />
Trace.WriteLine(num); <br />
} <br />
// Traces 1,2,3,4,5,6,7 <br />
<br />
可以通过添加其他的属性用相似的方式实现前序 (pre-order) 和后序 (post-order) 迭代。虽然以递归方式使用迭代程序的能力显然是一个强大的功能，但是在使用时应该保持谨慎，因为可能会出现严重的性能问题。每次调用 ScanInOrder 都需要实例化编译器生成的迭代程序，因此，递归遍历一个很深的树可能会导致在幕后生成大量的对象。在对称二叉树中，大约有 n 个迭代程序实例，其中 n 为树中节点的数目。在任一特定的时刻，这些对象中大约有 log(n) 个是活的。在具有适当大小的树中，许多这样的对象会使树通过 0 代 (Generation 0) 垃圾回收。也就是说，通过使用栈或队列维护一列将要被检查的节点，迭代程序仍然能够方便地遍历递归数据结构（例如树）。 <br />
<br />
返回页首 <br />
局部类型 <br />
<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 1.1 要求将类的全部代码放在一个文件中。而 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 允许将类或结构的定义和实现分开放在多个文件中。通过使用 new partial 关键字来标注分割，可以将类的一部分放在一个文件中，而将另一个部分放在一个不同的文件中。例如，可以将下面的代码放到文件 MyClass1.cs 中： <br />
<br />
public partial class MyClass <br />
{ <br />
public void Method1() <br />
{...} <br />
} <br />
<br />
在文件 MyClass2.cs 中，可以插入下面的代码： <br />
<br />
public partial class MyClass <br />
{ <br />
public void Method2() <br />
{...} <br />
public int Number; <br />
} <br />
<br />
实际上，可以将任一特定的类分割成任意多的部分。局部类型支持可以用于类、结构和接口，但是不能包含局部枚举定义。 <br />
<br />
局部类型是一个非常有用的功能。有时，我们需要修改机器生成的文件，例如 Web 服务客户端包装类。然而，当重新生成此包装类时，对该文件的修改将会被丢弃。通过使用局部类，可以将这些改变分开放在单独的文件中。<a title="asp.NET教程" href="http://www.myour.name/WengZhang/aspdotNET/index.html">asp.NET</a> 2.0 将局部类用于 code-beside 类（从 code-behind 演变而来），单独存储页面中机器生成的部分。Windows 窗体使用局部类来存储 InitializeComponent 方法的可视化设计器输出以及成员控件。通过使用局部类型，两个或者更多的开发人员可以工作在同一个类型上，同时都可以从源控制中签出其文件而不互相影响。 <br />
<br />
您可以问自己，如果多个不同的部分对同一个类做出了相互矛盾的定义会出现什么样的后果？答案很简单。一个类（或一个结构）可能具有两个不同的方面或性质：累积性的 (accumulative) 和非累积性的 (non-accumulative)。累积性的方面是指类可以选择添加它的各个部分，比如接口派生、属性、索引器、方法和成员变量。 <br />
<br />
例如，下面的代码显示了一个部分是如何添加接口派生和实现的： <br />
<br />
public partial class MyClass <br />
{} <br />
public partial class MyClass : IMyInterface <br />
{ <br />
public void Method1() <br />
{...} <br />
public void Method2() <br />
{...} <br />
} <br />
<br />
非累积性的方面是指一个类型的所有部分都必须一致。无论这个类型是一个类还是一个结构，类型可见性（公共或内部）和基类都是非累积性的方面。例如，下面的代码不能编译，因为并非 MyClass 的所有部分都出现在基类中： <br />
<br />
public class MyBase <br />
{} <br />
public class SomeOtherClass <br />
{} <br />
public partial class MyClass : MyBase <br />
{} <br />
public partial class MyClass : MyBase <br />
{} <br />
//Does not compile <br />
public partial class MyClass : SomeOtherClass <br />
{} <br />
<br />
除了所有的部分都必须定义相同的非累积性部分以外，只有一个部分能够重写虚方法或抽象方法，并且只有一个部分能够实现接口成员。 <br />
<br />
<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 是这样来支持局部类型的：当编译器构建程序集时，它将来自多个文件的同一类型的各个部分组合起来，并用 Microsoft 中间语言 (Microsoft intermediate language, MSIL) 将这些部分编译成单一类型。生成的 MSIL 中不含有哪一部分来自哪个文件的记录。正如在 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 1.1 中一样，MSIL 不含有哪个文件用于定义哪个类型的记录。另外值得注意的是，局部类型不能跨越程序集，并且通过忽略其定义中的 partial 限定符，一个类型可以拒绝包含其他部分。 <br />
<br />
因为编译器所做的只是将各个部分累积，所以一个单独的文件可以包含多个部分，甚至是包含同一类型的多个部分（尽管这样做的意义值得怀疑）。 <br />
<br />
在 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 中，开发人员通常根据文件所包含的类来为文件命名，这样可以避免将多个类放在同一个文件中。在使用局部类型时，我建议在文件名中指示此文件包含哪个类型的哪些部分（例如 MyClassP1.cs、MyClassP2.cs），或者采用其他一致的方式从外形上指示源文件的内容。例如，Windows 窗体设计人员将用于该窗体的局部类的一部分存放在 Form1.cs 中，并将此文件命名为 Form1.Designer.cs。 <br />
<br />
局部类的另一个不利之处是，当开始接触一个不熟悉的代码基时，您所维护类型的各个部分可能遍布在整个项目的文件中。在这种情况下，建议您使用 Visual Studio Class View，因为它可以将一个类型的所有部分积累起来展示给您，并允许您通过单击它的成员来导航各个不同的部分。导航栏也提供了这个功能。 <br />
<br />
返回页首 <br />
匿名方法 <br />
<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 支持用于调用一个或多个方法的委托 (delegate)。委托提供运算符和方法来添加或删除目标方法，它也可以在整个 .NET 框架中广泛地用于事件、回调、异步调用、多线程等。然而，仅仅为了使用一个委托，有时您不得不创建一个类或方法。在这种情况下，不需要多个目标，并且调用的代码通常相对较短而且简单。在 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 中，匿名方法是一个新功能，它允许定义一个由委托调用的匿名（也就是没有名称的）方法。 <br />
<br />
例如，下面是一个常规 SomeMethod 方法的定义和委托调用： <br />
<br />
class SomeClass <br />
{ <br />
delegate void SomeDelegate(); <br />
public void InvokeMethod() <br />
{ <br />
SomeDelegate del = new SomeDelegate(SomeMethod); <br />
del(); <br />
} <br />
void SomeMethod() <br />
{ <br />
MessageBox.Show("Hello"); <br />
} <br />
} <br />
<br />
可以用一个匿名方法来定义和实现这个方法： <br />
<br />
class SomeClass <br />
{ <br />
delegate void SomeDelegate(); <br />
public void InvokeMethod() <br />
{ <br />
SomeDelegate del = delegate() <br />
{ <br />
MessageBox.Show("Hello"); <br />
}; <br />
del(); <br />
} <br />
} <br />
<br />
匿名方法被定义为内嵌 (in-line) 方法，而不是作为任何类的成员方法。此外，无法将方法属性应用到一个匿名方法，并且匿名方法也不能定义一般类型或添加一般约束。 <br />
<br />
您应该注意关于匿名方法的两件值得关注的事情：委托保留关键字的重载使用和委托指派。稍后，您将看到编译器如何实现一个匿名方法，而通过查看代码，您就会相当清楚地了解编译器必须推理所使用的委托的类型，实例化推理类型的新委托对象，将新的委托包装到匿名方法中，并将其指派给匿名方法定义中使用的委托（前面的示例中的 del）。 <br />
<br />
匿名方法可以用在任何需要使用委托类型的地方。您可以将匿名方法传递给任何方法，只要该方法接受适当的委托类型作为参数即可： <br />
<br />
class SomeClass <br />
{ <br />
delegate void SomeDelegate(); <br />
public void SomeMethod() <br />
{ <br />
InvokeDelegate(delegate(){MessageBox.Show("Hello");}); <br />
} <br />
void InvokeDelegate(SomeDelegate del) <br />
{ <br />
del(); <br />
} <br />
} <br />
<br />
如果需要将一个匿名方法传递给一个接受抽象 Delegate 参数的方法，例如： <br />
<br />
void InvokeDelegate(Delegate del); <br />
<br />
则首先需要将匿名方法强制转换为特定的委托类型。 <br />
<br />
下面是一个将匿名方法作为参数传递的具体而实用的示例，它在没有显式定义 ThreadStart 委托或线程方法的情况下启动一个新的线程： <br />
<br />
public class MyClass <br />
{ <br />
public void LauchThread() <br />
{ <br />
Thread workerThread = new Thread(delegate() <br />
{ <br />
MessageBox.Show("Hello"); <br />
}); <br />
workerThread.Start(); <br />
} <br />
} <br />
<br />
在前面的示例中，匿名方法被当作线程方法来使用，这会导致消息框从新线程中显示出来。 <br />
<br />
返回页首 <br />
将参数传递到匿名方法 <br />
当定义带有参数的匿名方法时，应该在 delegate 关键字后面定义参数类型和名称，就好像它是一个常规方法一样。方法签名必须与它指派的委托的定义相匹配。当调用委托时，可以传递参数的值，与正常的委托调用完全一样： <br />
<br />
class SomeClass <br />
{ <br />
delegate void SomeDelegate(string str); <br />
public void InvokeMethod() <br />
{ <br />
SomeDelegate del = delegate(string str) <br />
{ <br />
MessageBox.Show(str); <br />
}; <br />
del("Hello"); <br />
} <br />
} <br />
<br />
如果匿名方法没有参数，则可以在 delegate 关键字后面使用一对空括号： <br />
<br />
class SomeClass <br />
{ <br />
delegate void SomeDelegate(); <br />
public void InvokeMethod() <br />
{ <br />
SomeDelegate del = delegate() <br />
{ <br />
MessageBox.Show("Hello"); <br />
}; <br />
del(); <br />
} <br />
} <br />
<br />
然而，如果您将 delegate 关键字与后面的空括号一起忽略，则您将定义一种特殊的匿名方法，它可以指派给具有任何签名的任何委托： <br />
<br />
class SomeClass <br />
{ <br />
delegate void SomeDelegate(string str); <br />
public void InvokeMethod() <br />
{ <br />
SomeDelegate del = delegate <br />
{ <br />
MessageBox.Show("Hello"); <br />
}; <br />
del("Parameter is ignored"); <br />
} <br />
} <br />
<br />
明显地，如果匿名方法并不依赖于任何参数，而且您想要使用这种与委托签名无关的方法代码，则您只能使用这样的语法。注意，当调用委托时，您仍然需要提供参数，因为编译器为从委托签名中推理的匿名方法生成无名参数，就好像您曾经编写了下面的代码（在 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 伪码中）一样： <br />
<br />
SomeDelegate del = delegate(string) <br />
{ <br />
MessageBox.Show("Hello"); <br />
}; <br />
<br />
此外，不带参数列表的匿名方法不能与指出参数的委托一起使用。 <br />
<br />
匿名方法可以使用任何类成员变量，并且它还可以使用定义在其包含方法范围之内的任何局部变量，就好像它是自己的局部变量一样。图 7 对此进行了展示。一旦知道如何为一个匿名方法传递参数，也就可以很容易地定义匿名事件处理，如图 8 所示。 <br />
<br />
因为 += 运算符仅仅将一个委托的内部调用列表与另一个委托的内部调用列表连接起来，所以可以使用 += 来添加一个匿名方法。注意，在匿名事件处理的情况下，不能使用 -= 运算符来删除事件处理方法，除非将匿名方法作为处理程序加入，要这样做，可以首先将匿名方法存储为一个委托，然后通过事件注册该委托。在这种情况下，可以将 -= 运算符与相同的委托一起使用，来取消将匿名方法作为处理程序进行注册。 <br />
<br />
返回页首 <br />
匿名方法实现 <br />
编译器为匿名方法生成的代码很大程度上依赖于匿名方法使用的参数或变量的类型。例如，匿名方法使用其包含方法的局部变量（也叫做外部变量）还是使用类成员变量和方法参数？无论是哪一种情况，编译器都会生成不同的 MSIL。如果匿名方法不使用外部变量（也就是说，它只使用自己的参数或者类成员），则编译器会将一个私有方法添加到该类中，以便赋予方法一个唯一的名称。该方法的名称具有以下格式： <br />
<br />
&lt;return type&gt; __AnonymousMethod$&lt;random unique number&gt;(&lt;params&gt;) <br />
<br />
和其他编译器生成的成员一样，这都是会改变的，并且最有可能在最终版本发布之前改变。方法签名将成为它指派的委托的签名。 <br />
<br />
编译器只是简单地将匿名方法定义和赋值转换成推理委托类型的标准实例，以包装机器生成的私有方法： <br />
<br />
SomeDelegate del = new SomeDelegate(__AnonymousMethod$00000000); <br />
<br />
非常有趣的是，机器产生的私有方法并不显示在 IntelliSense 中，也不能显式地调用它，因为其名称中的美元符号对于 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 方法来说是一个非法标记（但它是一个有效的 MSIL 标记）。 <br />
<br />
当匿名方法使用外部变量时，情况会更加困难。如果这样，编译器将用下面的格式添加具有唯一名称的私有嵌套类： <br />
<br />
__LocalsDisplayClass$&lt;random unique number&gt; <br />
<br />
嵌套类有一个名为 &lt;this&gt; 的指向包含类的引用，它是一个有效的 MSIL 成员变量名。嵌套类包含与匿名方法使用的每个外部变量对应的公共成员变量。编译器向嵌套类定义中添加一个具有唯一名称的公共方法，格式如下： <br />
<br />
&lt;return type&gt; __AnonymousMethod$&lt;random unique number&gt;(&lt;params&gt;) <br />
<br />
方法签名将成为被指派的委托的签名。编译器用代码替代匿名方法定义，此代码创建一个嵌套类的实例，并进行必要的从外部变量到该实例的成员变量的赋值。最后，编译器创建一个新的委托对象，以便包装嵌套类实例的公共方法，然后调用该委托来调用此方法。图 9 用 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 伪代码展示了编译器为图 7 中定义的匿名方法生成的代码。 <br />
<br />
返回页首 <br />
一般匿名方法 <br />
匿名方法可以使用一般参数类型，就像其他方法一样。它可以使用在类范围内定义的一般类型，例如： <br />
<br />
class SomeClass&lt;T&gt; <br />
{ <br />
delegate void SomeDelegate(T t); <br />
public void InvokeMethod(T t) <br />
{ <br />
SomeDelegate del = delegate(T item){...} <br />
del(t); <br />
} <br />
} <br />
<br />
因为委托可以定义一般参数，所以匿名方法可以使用在委托层定义的一般类型。可以指定用于方法签名的类型，在这种情况下，方法签名必须与其所指派的委托的特定类型相匹配： <br />
<br />
class SomeClass <br />
{ <br />
delegate void SomeDelegate&lt;T&gt;(T t); <br />
public void InvokeMethod() <br />
{ <br />
SomeDelegate&lt;int&gt; del = delegate(int number) <br />
{ <br />
MessageBox.Show(number.ToString()); <br />
}; <br />
del(3); <br />
} <br />
} <br />
<br />
返回页首 <br />
匿名方法示例 <br />
虽然乍一看匿名方法的使用可能像一种另类的编程技术，但是我发现它是相当有用的，因为在只要一个委托就足够的情况下，使用它就可以不必再创建一个简单方法。图 10 展示了一个有用的匿名方法的实际例子 — SafeLabel Windows 窗体控件。 <br />
<br />
Windows 窗体依赖于基本的 Win32 消息。因此，它继承了典型的 Windows 编程要求：只有创建窗口的线程可以处理它的消息。在 .NET 框架 2.0 中，调用错误的线程总会触发一个 Windows 窗体方面的异常。因此，当在另一个线程中调用窗体或控件时，必须将该调用封送到正确的所属线程中。Windows 窗体有内置的支持，可以用来摆脱这个困境，方法是用 Control 基类实现 ISynchronizeInvoke 接口，其定义如下： <br />
<br />
public interface ISynchronizeInvoke <br />
{ <br />
bool InvokeRequired {get;} <br />
IAsyncResult BeginInvoke(Delegate method,object[] args); <br />
object EndInvoke(IAsyncResult result); <br />
object Invoke(Delegate method,object[] args); <br />
} <br />
<br />
Invoke 方法接受针对所属线程中的方法的委托，并且将调用从正在调用的线程封送到该线程。因为您可能并不总是知道自己是否真的在正确的线程中执行，所以通过使用 InvokeRequired 属性，您可以进行查询，从而弄清楚是否需要调用 Invoke 方法。问题是，使用 ISynchronizeInvoke 将会大大增加编程模型的复杂性，因此较好的方法常常是将带有 ISynchronizeInvoke 接口的交互封装在控件或窗体中，它们会自动地按需使用 ISynchronizeInvoke。 <br />
<br />
例如，为了替代公开 Text 属性的 Label 控件，您可以定义从 Label 派生的 SafeLabel 控件，如图 10 所示。SafeLabel 重写了其基类的 Text 属性。在其 get 和 set 中，它检查 Invoke 是否是必需的。如果是这样，则它需要使用一个委托来访问此属性。该实现仅仅调用了基类属性的实现，不过是在正确的线程上。因为 SafeLabel 只定义这些方法，所以它们可以通过委托进行调用，它们是匿名方法很好的候选者。SafeLabel 传递这样的委托，以便将匿名方法作为其 Text 属性的<a title="安全教程" href="http://www.myour.name/WengZhang/Security/index.html">安全</a>实现包装到 Invoke 方法中。 <br />
<br />
返回页首 <br />
委托推理 <br />
<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 编译器从匿名方法指派推理哪个委托类型将要实例化的能力是一个非常重要的功能。实际上，它还提供了另一个叫做委托推理的 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 功能。委托推理允许直接给委托变量指派方法名，而不需要先使用委托对象包装它。例如下面的 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 1.1 代码： <br />
<br />
class SomeClass <br />
{ <br />
delegate void SomeDelegate(); <br />
public void InvokeMethod() <br />
{ <br />
SomeDelegate del = new SomeDelegate(SomeMethod); <br />
del(); <br />
} <br />
void SomeMethod() <br />
{...} <br />
} <br />
<br />
现在，您可以编写下面的代码来代替前面的代码片断： <br />
<br />
class SomeClass <br />
{ <br />
delegate void SomeDelegate(); <br />
public void InvokeMethod() <br />
{ <br />
SomeDelegate del = SomeMethod; <br />
del(); <br />
} <br />
void SomeMethod() <br />
{...} <br />
} <br />
<br />
当将一个方法名指派给委托时，编译器首先推理该委托的类型。然后，编译器根据此名称检验是否存在一个方法，并且它的签名是否与推理的委托类型相匹配。最后，编译器创建一个推理委托类型的新对象，以便包装此方法，并将其指派给该委托。如果该类型是一个具体的委托类型（即除了抽象类型 Delegate 之外的其他类型），则编译器只能推理委托类型。委托推理的确是一个非常有用的功能，它可以使代码变得简练而优雅。 <br />
<br />
我相信，作为 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 中的惯例，您会使用委托推理，而不是以前的委托实例化方法。例如，下面的代码说明了如何在不显式地创建一个 ThreadStart 委托的情况下启动一个新的线程： <br />
<br />
public class MyClass <br />
{ <br />
void ThreadMethod() <br />
{...} <br />
public void LauchThread() <br />
{ <br />
Thread workerThread = new Thread(ThreadMethod); <br />
workerThread.Start(); <br />
} <br />
} <br />
<br />
当启动一个异步调用并提供一个完整的回调方法时，可以使用一对委托推理，如图 11 所示。首先，指定异步调用的方法名来异步调用一个匹配的委托。然后调用 BeginInvoke，提供完整的回调方法名而不是 AsyncCallback 类型的委托。 <br />
<br />
返回页首 <br />
属性和索引可见性 <br />
<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 允许为属性或索引器的 get 和 set 访问器指定不同的可见性。例如，在通常情况下，可能想将 get 访问器公开为 public，而把 set 访问器公开为 protected。为此，可以为 set 关键字添加 protected 可见性限定符。类似地，可以将索引器的 set 方法定义为 protected（请参见图 12）。 <br />
<br />
当使用属性可见性时有几项规定。首先，应用在 set 或 get 上的可见性限定词只能是此属性本身可见性的严格子集。换句话说，如果此属性是 public，那么您就可以指定 internal、protected、protected internal、private。如果此属性可见性是 protected，就不能将 get 或 set 公开为 public。此外，只能分别为 get 或 set 指定可见性，而不能同时为它们指定可见性。 <br />
<br />
返回页首 <br />
静态类 <br />
有些类只有静态方法或静态成员（静态类），这是非常常见的。在这种情况下，实例化这些类的对象没有意义。例如，Monitor 类或类工厂（例如 .NET 框架 1.1 中的 Activator 类）都是静态类。在 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 1.1 中，如果想要阻止开发人员实例化类的对象，您可以只提供一个私有的默认构造函数。如果没有任何公共的构造函数，就不可以实例化类的对象： <br />
<br />
public class MyClassFactory <br />
{ <br />
private MyClassFactory() <br />
{} <br />
static public object CreateObject() <br />
{...} <br />
} <br />
<br />
然而，因为 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 编译器仍然允许您添加实例成员（尽管可能从来都不使用它们），所以是否在类中只定义静态成员完全由您决定。<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 通过允许将类限定为 static 来支持静态类： <br />
<br />
public static class MyClassFactory <br />
{ <br />
static public T CreateObject&lt;T&gt;() <br />
{...} <br />
} <br />
<br />
<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 编译器不允许您将一个非静态成员添加到一个静态类中，也不允许您创建此静态类的实例，就好像它是一个抽象类一样。此外，您不能从一个静态类派生子类。这就如同编译器在静态类定义中加入了 abstract 和 sealed 一样。注意，可以定义静态类而不能定义静态结构，并且可以添加静态构造函数。 <br />
<br />
返回页首 <br />
全局命名空间限定符 <br />
很可能有这样一个嵌套的命名空间，它的名称与一些其他的全局命名空间相匹配。在这种情况下，<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 1.1 编译器在解析命名空间引用时会出现问题。请考虑下例： <br />
<br />
namespace MyApp <br />
{ <br />
namespace System <br />
{ <br />
class MyClass <br />
{ <br />
public void MyMethod() <br />
{ <br />
System.Diagnostics.Trace.WriteLine("It Works!"); <br />
} <br />
} <br />
} <br />
} <br />
<br />
在 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 1.1 中，调用 Trace 类会产生编译错误（没有全局命名空间限定符 ::）。出现这种错误的原因在于，当编译器尝试解析对 System 命名空间的引用时，它使用直接包含范围，此范围包含 System 命名空间但不包含 Diagnostics 命名空间。<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 允许您使用全局命名空间限定符 :: 来表示编译器应该在全局范围内进行搜索。可以将 :: 限定符应用于命名空间和类型，如图 13 所示。 <br />
<br />
返回页首 <br />
内联警告 <br />
<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 1.1 允许使用项目设置或者通过向编译器发布命令行参数来禁止特殊的编译器警告。其中的问题在于，这是一个全局取消，因此这样做会取消一些您仍然需要的警告。<a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 允许使用 #pragma 警告指令显式地取消和恢复编译器警告： <br />
<br />
// Disable 'field never used' warning <br />
#pragma warning disable 169 <br />
public class MyClass <br />
{ <br />
int m_Number; <br />
} <br />
#pragma warning restore 169 <br />
<br />
在生产代码中通常并不鼓励禁止警告。禁止警告只是为了进行某些分析，比如，当您尝试隔离一个问题时，或者当您设计代码并且想要得到代码合适的初始结构而不必先行对其加以完善时。而在所有其他的情况下，都要避免取消编译器警告。注意，您不能通过编程的方式来重写项目设置，这意味着您不能使用 pragma 警告指令来恢复全局取消的警告。 <br />
<br />
返回页首 <br />
小结 <br />
本文所提到的 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 2.0 中的一些新功能是专门的解决方案，旨在处理特定的问题，同时可以简化整体编程模型。如果您关注工作效率和质量，您就需要让编译器生成尽可能多的实现，减少重复性的编程任务，使最后得到的代码简洁易读。新的功能带给您的正是这些，并且我相信，它们象征着 <a title="C#教程" href="http://www.myour.name/WengZhang/csharp/index.html">C#</a> 时代的到来，它会使自己成为服务于 .NET 专业开发人员的优秀工具。 <br />
<br />
Juval Lowy 是一名软件架构师，他提供 .NET 设计和移植方面的咨询和培训。他还是硅谷的 Microsoft 地区总裁 (Microsoft Regional Director)。他最新出版的一本书是 Programming .NET Components (O'Reilly, 2003)。可以在 http://www.idesign.net 上与 Juval 联系。
 <img src ="http://www.cnblogs.com/babilone/aggbug/1192120.html?type=2" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43625/" target="_blank">[新闻]2008年11月22日科技博客精选</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>GDI+中GIF图片的显示 </title><link>http://www.cnblogs.com/babilone/archive/2008/05/11/1192117.html</link><dc:creator>农民工人笑了</dc:creator><author>农民工人笑了</author><pubDate>Sun, 11 May 2008 04:33:00 GMT</pubDate><guid>http://www.cnblogs.com/babilone/archive/2008/05/11/1192117.html</guid><wfw:comment>http://www.cnblogs.com/babilone/comments/1192117.html</wfw:comment><comments>http://www.cnblogs.com/babilone/archive/2008/05/11/1192117.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/babilone/comments/commentRss/1192117.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/babilone/services/trackbacks/1192117.html</trackback:ping><description><![CDATA[<p><font face="Verdana">一、GIF格式介绍 </font></p>
<font face="Verdana">
<p><br />
1.概述 </p>
<p>　　GIF(Graphics Interchange Format，图形交换格式)文件是由 CompuServe公司开发的图形文件格式，版权所有，任何商业目的使用均须 CompuServe公司授权。 <br />
　　GIF图象是基于颜色列表的（存储的数据是该点的颜色对应于颜色列表的索引值），最多只支持8位（256色）。GIF文件内部分成许多存储块， 用来存储多幅图象或者是决定图象表现行为的控制块， 用以实现动画和交互式应用。GIF文件还通过LZW压缩算法压缩图象数据来减少图象尺寸。 </p>
<p>2.GIF文件存储结构 </p>
<p>　　GIF文件内部是按块划分的，包括控制块（ Control Block ）和数据块（Data Sub-blocks）两种。控制块是控制数据块行为的，根据不同的控制块包含一些不同的控制参数； 数据块只包含一些8-bit的字符流，由它前面的控制块来决定它的功能，每个数据块大小从0到255个字节， 数据块的第一个字节指出这个数据块大小（字节数）， 计算数据块的大小时不包括这个字节，所以一个空的数据块有一个字节，那就是数据块的大小0x00。 下表是一个数据块的结构： </p>
<p>BYTE 7 6 5 4 3 2 1 0 BIT&nbsp; <br />
0 块大小 <br />
&nbsp;Block Size - 块大小，不包括这个这个字节（不计算块大小自身）&nbsp; <br />
1&nbsp; Data Values - 块数据，8-bit的字符串&nbsp; <br />
2&nbsp;&nbsp; <br />
...&nbsp;&nbsp; <br />
254&nbsp;&nbsp; <br />
255&nbsp;&nbsp; </p>
<p>　　一个GIF文件的结构可分为文件头(File Header)、GIF数据流(GIF Data Stream)和文件终结器(Trailer)三个部分。文件头包含GIF文件署名(Signature)和版本号(Version)；GIF数据流由控制标识符、图象块(Image Block)和其他的一些扩展块组成；文件终结器只有一个值为0x3B的字符（'';''）表示文件结束。下表显示了一个GIF文件的组成结构： </p>
<p>&nbsp;GIF署名 文件头&nbsp;&nbsp; <br />
&nbsp;版本号&nbsp; <br />
&nbsp;逻辑屏幕标识符 GIF数据流&nbsp;&nbsp; <br />
&nbsp;全局颜色列表&nbsp;&nbsp; <br />
&nbsp;...&nbsp;&nbsp; <br />
&nbsp;图象标识符 图象块 　　　　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　　　　&nbsp; <br />
&nbsp;图象局部颜色列表图&nbsp; <br />
　　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　　　　 基于颜色列表的图象数据&nbsp;&nbsp; <br />
&nbsp; <br />
&nbsp;...&nbsp;&nbsp; <br />
&nbsp;GIF结尾 文件结尾&nbsp;&nbsp; </p>
<p>　　下面就具体介绍各个部分: </p>
<p>文件头部分(Header) </p>
<p>GIF署名(Signature)和版本号(Version) </p>
<p>GIF署名用来确认一个文件是否是GIF格式的文件，这一部分由三个字符组成："GIF";文件版本号也是由三个字节组成,可以为"87a"或"89a".具体描述见下表: </p>
<p>BYTE 7 6 5 4 3 2 1 0 BIT&nbsp; <br />
1 ''G'' GIF文件标识&nbsp; <br />
2 ''I''&nbsp; <br />
3 ''F''&nbsp; <br />
4 ''8'' GIF文件版本号：87a - 1987年5月 <br />
　　　　　　　 89a - 1989年7月&nbsp; <br />
5 ''7''或''9''&nbsp; <br />
6 ''a''&nbsp; </p>
<p>GIF数据流部分(GIF Data Stream) </p>
<p>逻辑屏幕标识符(Logical Screen Descriptor) </p>
<p>这一部分由7个字节组成，定义了GIF图象的大小(Logical Screen Width &amp; Height)、颜色深度(Color Bits)、背景色(Blackground Color Index)以及有无全局颜色列表(Global Color Table)和颜色列表的索引数(Index Count)，具体描述见下表： </p>
<p>BYTE 7 6 5 4 3 2 1 0 BIT&nbsp;&nbsp; <br />
1 逻辑屏幕宽度 像素数，定义GIF图象的宽度&nbsp; <br />
2&nbsp; <br />
3 逻辑屏幕高度 像素数，定义GIF图象的高度&nbsp; <br />
4&nbsp; <br />
5 m cr s pixel 具体描述见下...&nbsp; <br />
6 背景色 背景颜色(在全局颜色列表中的索引，如果没有全局颜色列表，该值没有意义)&nbsp; <br />
7 像素宽高比 像素宽高比(Pixel Aspect Radio)&nbsp; </p>
<p>m - 全局颜色列表标志(Global Color Table Flag)，当置位时表示有全局颜色列表，pixel值有意义. <br />
cr - 颜色深度(Color ResoluTion)，cr+1确定图象的颜色深度. <br />
s - 分类标志(Sort Flag)，如果置位表示全局颜色列表分类排列. <br />
pixel - 全局颜色列表大小，pixel+1确定颜色列表的索引数（2的pixel+1次方）. </p>
<p>全局颜色列表(Global Color Table) </p>
<p>全局颜色列表必须紧跟在逻辑屏幕标识符后面，每个颜色列表索引条目由三个字节组成，按R、G、B的顺序排列。 </p>
<p>BYTE 7 6 5 4 3 2 1 0 BIT&nbsp; <br />
1 索引1的红色值&nbsp;&nbsp; <br />
2 索引1的绿色值&nbsp;&nbsp; <br />
3 索引1的蓝色值&nbsp;&nbsp; <br />
4 索引2的红色值&nbsp;&nbsp; <br />
5 索引2的绿色值&nbsp;&nbsp; <br />
6 索引2的蓝色值&nbsp;&nbsp; <br />
7 ... 　　　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　　　　&nbsp; </p>
<p>图象标识符(Image Descriptor) <br />
~~~~~~~~~~~~~~~~~~~~~~~~~ <br />
一个GIF文件内可以包含多幅图象，一幅图象结束之后紧接着下是一幅图象的标识符，图象标识符以0x2C('','')字符开始， 定义紧接着它的图象的性质，包括图象相对于逻辑屏幕边界的偏移量、图象大小以及有无局部颜色列表和颜色列表大小， 由10个字节组成： </p>
<p>BYTE 7 6 5 4 3 2 1 0 BIT&nbsp;&nbsp; <br />
1 0 0 1 0 1 1 0 0 图象标识符开始，固定值为'',''&nbsp; <br />
2 X方向偏移量 必须限定在逻辑屏幕尺寸范围内&nbsp; <br />
3&nbsp; <br />
4 Y方向偏移量&nbsp; <br />
5&nbsp; <br />
6 图象宽度&nbsp; <br />
7&nbsp; <br />
8 图象高度&nbsp; <br />
9&nbsp; <br />
10 m i s r pixel m - 局部颜色列表标志(Local Color Table Flag)&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 置位时标识紧接在图象标识符之后有一个局部颜色列表，供紧跟在它之后的一幅图象使用；值否时使用全局颜色列表， 忽略pixel值。 <br />
i - 交织标志(Interlace Flag)，置位时图象数据使用交织方式排列 （详细描述...），否则使用顺序排列。 <br />
s - 分类标志(Sort Flag)，如果置位表示紧跟着的局部颜色列表分类排列. <br />
r - 保留，必须初始化为0. <br />
pixel - 局部颜色列表大小(Size of Local Color Table)，pixel+1就为颜色列表的位数&nbsp; </p>
<p>局部颜色列表(Local Color Table) </p>
<p>如果上面的局部颜色列表标志置位的话，则需要在这里（紧跟在图象标识符之后）定义一个局部颜色列表以供紧接着它的图象使用，注 意使用前应线保存原来的颜色列表，使用结束之后回复原来保存的全局颜色列表。如果一个GIF文件即没有提供全局颜色列表，也没有提供局部颜色列表， 可以自己创建一个颜色列表，或使用系统的颜色列表。局部颜色列表的排列方式和全局颜色列表一样：RGBRGB...... </p>
<p>基于颜色列表的图象数据(Table-Based Image Data) </p>
<p>由两部分组成：LZW编码长度(LZW Minimum Code Size)和图象数据(Image Data)。 </p>
<p>BYTE 7 6 5 4 3 2 1 0 BIT&nbsp; <br />
1 LZW编码长度 LZW编码初始码表大小的位数，详细描述见LZW编码...&nbsp; <br />
　 <br />
　 <br />
&nbsp; <br />
... <br />
&nbsp;图象数据，由一个或几个数据块(Data Sub-blocks)组成&nbsp; </p>
<p>数据块 <br />
&nbsp; </p>
<p>... <br />
&nbsp; </p>
<p>GIF图象数据使用了LZW压缩算法（详细介绍请看后面的『LZW算法和GIF数据压缩』），大大减小了图象数据的大小。图象数据在压缩前有两种排列格式：连续的和交织的(由图象标识符的交织标志控制)。连续方式按从左到右、从上到下的顺序排列图象的光栅数据；交织图象按下面的方法处理光栅数据： </p>
<p>创建四个通道(pass)保存数据，每个通道提取不同行的数据： <br />
第一通道(Pass 1)提取从第0行开始每隔8行的数据； <br />
第二通道(Pass 2)提取从第4行开始每隔8行的数据； <br />
第三通道(Pass 3)提取从第2行开始每隔4行的数据； <br />
第四通道(Pass 4)提取从第1行开始每隔2行的数据； </p>
<p>下面的例子演示了提取交织图象数据的顺序： </p>
<p>行 　通道1　 　通道2　 　通道3　 　通道4　&nbsp;&nbsp; <br />
0&nbsp; -------------------------------------------------------- 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
1 --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
2&nbsp; --------------------------------------------------------&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp; <br />
3&nbsp; --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
4&nbsp; --------------------------------------------------------&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp; <br />
5&nbsp; --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
6&nbsp; --------------------------------------------------------&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp; <br />
7&nbsp; --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
8&nbsp; -------------------------------------------------------- 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
9&nbsp; --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
10 --------------------------------------------------------&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp; <br />
11 --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
12 --------------------------------------------------------&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp; <br />
13 --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
14 --------------------------------------------------------&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp; <br />
15 --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
16 -------------------------------------------------------- 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
17 --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
18 --------------------------------------------------------&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp; <br />
19 --------------------------------------------------------&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp; <br />
20 --------------------------------------------------------&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p>　 </p>
<p>图形控制扩展(Graphic Control Extension) </p>
<p>这一部分是可选的（需要89a版本），可以放在一个图象块(图象标识符)或文本扩展块的前面， 用来控制跟在它后面的第一个图象（或文本）的渲染(Render)形式，组成结构如下： </p>
<p>BYTE 7 6 5 4 3 2 1 0 BIT&nbsp; <br />
1 扩展块标识 Extension Introducer - 标识这是一个扩展块，固定值0x21&nbsp; <br />
2 图形控制扩展标签 Graphic Control Label - 标识这是一个图形控制扩展块，固定值0xF9&nbsp; <br />
3 块大小 Block Size - 不包括块终结器，固定值4&nbsp; <br />
4 保留 处置方法 i <br />
&nbsp;t <br />
&nbsp;i - 用户输入标志；t - 透明色标志。详细描述见下...&nbsp; <br />
5 延迟时间 Delay Time - 单位1/100秒，如果值不为1，表示暂停规定的时间后再继续往下处理数据流&nbsp; <br />
6&nbsp; <br />
7 透明色索引 Transparent Color Index - 透明色索引值&nbsp; <br />
8 块终结器 Block Terminator - 标识块终结，固定值0&nbsp; </p>
<p>处置方法(Disposal Method)：指出处置图形的方法，当值为： <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0 - 不使用处置方法 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 - 不处置图形，把图形从当前位置移去 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2 - 回复到背景色 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3 - 回复到先前状态 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4-7 - 自定义 <br />
用户输入标志(Use Input Flag)：指出是否期待用户有输入之后才继续进行下去，置位表示期待，值否表示不期待。用户输入可以是按回车键、鼠标点击等， 可以和延迟时间一起使用，在设置的延迟时间内用户有输入则马上继续进行，或者没有输入直到延迟时间到达而继续 <br />
透明颜色标志(Transparent Color Flag)：置位表示使用透明颜色 </p>
<p>注释扩展(Comment Extension) </p>
<p>这一部分是可选的（需要89a版本），可以用来记录图形、版权、描述等任何的非图形和控制的纯文本数据(7-bit ASCII字符)，注释扩展并不影响对图象数据流的处理，解码器完全可以忽略它。 存放位置可以是数据流的任何地方，最好不要妨碍控制和数据块，推荐放在数据流的开始或结尾。具体组成： </p>
<p>BYTE 7 6 5 4 3 2 1 0 BIT&nbsp; <br />
1 扩展块标识 Extension Introducer - 标识这是一个扩展块，固定值0x21&nbsp; <br />
2 注释块标签 Comment Label - 标识这是一个注释块，固定值0xFE&nbsp; <br />
&nbsp; <br />
... <br />
&nbsp;Comment Data - 一个或多个数据块(Data Sub-Blocks)组成&nbsp; </p>
<p>注释块 <br />
&nbsp; </p>
<p>... <br />
&nbsp; <br />
&nbsp;块终结器 Block Terminator - 标识注释块结束，固定值0&nbsp; </p>
<p>图形文本扩展(Plain Text Extension) </p>
<p>这一部分是可选的（需要89a版本），用来绘制一个简单的文本图象，这一部分由用来绘制的纯文本数据（7-bit ASCII字符）和控制绘制的参数等组成。绘制文本借助于一个文本框（Text Grid）来定义边界，在文本框中划分多个单元格，每个字符占用一个单元，绘制时按从左到右、从上到下的顺序依次进行， 直到最后一个字符或者占满整个文本框（之后的字符将被忽略，因此定义文本框的大小时应该注意到是否可以容纳整个文本）， 绘制文本的颜色索引使用全局颜色列表，没有则可以使用一个已经保存的前一个颜色列表。另外，图形文本扩展块也属于图形块(Graphic Rendering Block)，可以在它前面定义图形控制扩展对它的表现形式进一步修改。图形文本扩展的组成： </p>
<p><br />
BYTE 7 6 5 4 3 2 1 0 BIT&nbsp; <br />
1 扩展块标识 Extension Introducer - 标识这是一个扩展块，固定值0x21&nbsp; <br />
2 图形控制扩展标签 Plain Text Label - 标识这是一个图形文本扩展块，固定值0x01&nbsp; <br />
3 块大小 Block Size - 块大小，固定值12&nbsp; <br />
4 文本框左边界位置 Text Glid Left Posotion - 像素值，文本框离逻辑屏幕的左边界距离&nbsp; <br />
5&nbsp; <br />
6 文本框上边界位置 Text Glid Top Posotion - 像素值，文本框离逻辑屏幕的上边界距离&nbsp; <br />
7&nbsp; <br />
8 文本框高度 Text Glid Width -像素值&nbsp; <br />
9&nbsp; <br />
10 文本框高度 Text Glid Height - 像素值&nbsp; <br />
11&nbsp; <br />
12 字符单元格宽度 Character Cell Width - 像素值，单个单元格宽度&nbsp; <br />
13 字符单元格高度 Character Cell Height- 像素值，单个单元格高度&nbsp; <br />
14 文本前景色索引 Text Foreground Color Index - 前景色在全局颜色列表中的索引&nbsp; <br />
15 文本背景色索引 Text Blackground Color Index - 背景色在全局颜色列表中的索引&nbsp; <br />
N&nbsp; <br />
... <br />
&nbsp;Plain Text Data - 一个或多个数据块(Data Sub-Blocks)组成，保存要在显示的字符串。&nbsp; </p>
<p>文本数据块 <br />
&nbsp; </p>
<p>... <br />
&nbsp; <br />
N+1 块终结 Block Terminator - 标识注释块结束，固定值0&nbsp; </p>
<p>推荐：1.由于文本的字体(Font)和尺寸(Size)没有定义，解码器应该根据情况选择最合适的； <br />
2.如果一个字符的值小于0x20或大于0xF7，则这个字符被推荐显示为一个空格(0x20)； <br />
3.为了兼容性，最好定义字符单元格的大小为8x8或8x16（宽度x高度）。 </p>
<p>应用程序扩展(Application Extension) </p>
<p>这是提供给应用程序自己使用的（需要89a版本），应用程序可以在这里定义自己的标识、信息等，组成： </p>
<p><br />
BYTE 7 6 5 4 3 2 1 0 BIT&nbsp; <br />
1 扩展块标识 Extension Introducer - 标识这是一个扩展块，固定值0x21&nbsp; <br />
2 图形控制扩展标签 Application Extension Label - 标识这是一个应用程序扩展块，固定值0xFF&nbsp; <br />
3 块大小 Block Size - 块大小，固定值11&nbsp; <br />
4 应用程序标识符 Application Identifier - 用来鉴别应用程序自身的标识(8个连续ASCII字符)&nbsp; <br />
5&nbsp; <br />
6&nbsp; <br />
7&nbsp; <br />
8&nbsp; <br />
9&nbsp; <br />
10&nbsp; <br />
11&nbsp; <br />
12 应用程序鉴别码 Application Authentication Code - 应用程序定义的特殊标识码(3个连续ASCII字符)&nbsp; <br />
13&nbsp; <br />
14&nbsp; <br />
N&nbsp; <br />
... <br />
&nbsp;应用程序自定义数据块 - 一个或多个数据块(Data Sub-Blocks)组成，保存应用程序自己定义的数据&nbsp; </p>
<p>应用程序数据 <br />
&nbsp; </p>
<p>... <br />
&nbsp; <br />
N+1 块终结器 lock Terminator - 标识注释块结束，固定值0&nbsp; </p>
<p>文件结尾部分 </p>
<p>文件终结器(Trailer) </p>
<p>这一部分只有一个值为0的字节，标识一个GIF文件结束. </p>
<p><br />
BYTE 7 6 5 4 3 2 1 0&nbsp;&nbsp; <br />
1 文件终结 <br />
&nbsp;GIF Trailer - 标识GIF文件结束，固定值0x3B&nbsp; </p>
<p>二、在GDI+中绘制GIF </p>
<p>GDI+中绘制一个图片的代码如下:&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void CMyWnd::OnPaint() <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CPaintDC dc(this); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Graphics graphics(&amp;dc); // Create a GDI+ graphics object <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Image image(L"Test.Gif"); // Construct an image <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; graphics.DrawImage(&amp;image, 0, 0, image.GetWidth(), image.GetHeight()); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
Gif分为两种，一种是静态的，对于这种格式的Gif，在GDI+中无需采用任何方法就能够直接显示(上面的代码就属于这种情况)。另一种是动态的， 这种文件能够显示简单的动画。动态的实际上由多幅静态的组成，在显示Gif时，每幅图片按照一定的时间间隔依次进行显示，从而实现了动画效果。&nbsp; <br />
我把GIF封装成了一个类ImageEx,这个类继承了GDI+中的Image类。我们首先要做的工作是判断GIF是动态的还是静态的。&nbsp; <br />
bool ImageEx::TestForAnimatedGIF() <br />
{ <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UINT count = 0; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; count = GetFrameDimensionsCount(); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GUID* pDimensionIDs = new GUID[count]; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 得到子帧的对象列表 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetFrameDimensionsList(pDimensionIDs, count); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp; //获取总帧数 <br />
&nbsp;&nbsp;&nbsp;&nbsp; m_nFrameCount = GetFrameCount(&amp;pDimensionIDs[0]); <br />
&nbsp;&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp; // 假设图像具有属性条目 PropertyItemEquipMake. <br />
&nbsp;&nbsp;&nbsp;&nbsp; // 获取此条目的大小. <br />
&nbsp;&nbsp;&nbsp;&nbsp; int nSize = GetPropertyItemSize(PropertyTagFrameDelay); <br />
&nbsp;&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp; // 为属性条目分配空间. <br />
&nbsp;&nbsp;&nbsp;&nbsp; m_pPropertyItem = (PropertyItem*) malloc(nSize); <br />
&nbsp;&nbsp;&nbsp;&nbsp; GetPropertyItem(PropertyTagFrameDelay, nSize, m_pPropertyItem); <br />
&nbsp;&nbsp;&nbsp;&nbsp; delete pDimensionIDs; <br />
&nbsp;&nbsp;&nbsp;&nbsp; return m_nFrameCount &gt; 1; <br />
&nbsp;&nbsp;&nbsp;&nbsp; <br />
} <br />
m_pPropertyItem-&gt;value 是一个长整形数组, 每个长整形代表每帧的延时。由于获取的属性不同，GetPropertyItem会获得不同大小的对象， 因此要由用户来获得的对象大小，开辟与删除 GetPropertyItem相关的内存。对象的大小是通过GetPropertyItemSize 获取的，其参数是你所感兴趣的属性条目。 一旦获取了帧的数量与延时，我们就可生成一个线程来调用 DrawFrameGIF（）来显示。&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool ImageEx::DrawFrameGIF() <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ::WaitForSingleObject(m_hPause, INFINITE); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GUID pageGuid = FrameDimensionTime; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long hmWidth = GetWidth(); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long hmHeight = GetHeight(); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HDC hDC = GetDC(m_hWnd); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (hDC) <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Graphics graphics(hDC); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; graphics.DrawImage(this, m_rc.left, m_rc.top, hmWidth, hmHeight); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ReleaseDC(m_hWnd, hDC); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SelectActiveFrame(&amp;pageGuid, m_nFramePosition++);&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (m_nFramePosition == m_nFrameCount) <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_nFramePosition = 0; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long lPause = ((long*) m_pPropertyItem-&gt;value)[m_nFramePosition] * 10; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD dwErr = WaitForSingleObject(m_hExitEvent, lPause); <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return dwErr == WAIT_OBJECT_0; <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />
<br />
<br />
C# 中绘制透明背景的GIF图片<br />
</p>
<p><font face="Verdana">&nbsp;&nbsp;protected override void OnPaint(PaintEventArgs e)<br />
&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;//base.OnPaint(e);<br />
&nbsp;&nbsp;&nbsp;Bitmap img = new Bitmap(@"c:\123.gif");<br />
&nbsp;&nbsp;&nbsp;System.Drawing.Imaging.PropertyItem item = img.PropertyItems[0];<br />
&nbsp;&nbsp;&nbsp;//img.MakeTransparent(Color.Red);<br />
&nbsp;&nbsp;&nbsp;Guid guid = (Guid)img.FrameDimensionsList.GetValue(0);<br />
&nbsp;&nbsp;&nbsp;int count = img.GetFrameCount(new System.Drawing.Imaging.FrameDimension(guid));<br />
&nbsp;&nbsp;&nbsp;//img.Size;<br />
&nbsp;&nbsp;&nbsp;System.Drawing.Imaging.FrameDimension frameDimension = new System.Drawing.Imaging.FrameDimension(guid);<br />
&nbsp;&nbsp;&nbsp;for (int i = 0; i &lt; count; i++)<br />
&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;//选中一桢&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp;&nbsp;int selectResult = img.SelectActiveFrame(frameDimension, i);<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (selectResult == 0)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//img.MakeTransparent(Color.FromArgb(255, 255, 255));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Bitmap dd = System.Drawing.Image.FromHbitmap(img.GetHbitmap());<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dd.MakeTransparent();&nbsp;&nbsp;&nbsp;&nbsp;//使图片背景透明<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.Graphics.DrawImage(dd, new Rectangle(10, 10, 20, 50));</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.Threading.Thread.Sleep(item.Value[0 * item.Type] * 10);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (i == count - 1)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i = 0;<br />
&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;}</font></p>
<p>
<p><font face="Verdana"></font></p>
<br />
</font></p>
 <img src ="http://www.cnblogs.com/babilone/aggbug/1192117.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43625/" target="_blank">[新闻]2008年11月22日科技博客精选</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>泛型</title><link>http://www.cnblogs.com/babilone/archive/2008/05/11/1192099.html</link><dc:creator>农民工人笑了</dc:creator><author>农民工人笑了</author><pubDate>Sun, 11 May 2008 04:21:00 GMT</pubDate><guid>http://www.cnblogs.com/babilone/archive/2008/05/11/1192099.html</guid><wfw:comment>http://www.cnblogs.com/babilone/comments/1192099.html</wfw:comment><comments>http://www.cnblogs.com/babilone/archive/2008/05/11/1192099.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/babilone/comments/commentRss/1192099.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/babilone/services/trackbacks/1192099.html</trackback:ping><description><![CDATA[<p style="text-indent: 2em">20．泛型</p>
<p style="text-indent: 2em">20.1泛型类声明</p>
<p style="text-indent: 2em">泛型类声明是一个需要提供类型参数以形成实际类型的类的声明。</p>
<p style="text-indent: 2em">类声明可以有选择地定义类型参数。</p>
<p style="text-indent: 2em">class-declaration: （类声明）</p>
<p style="text-indent: 2em">attributesopt class-modifiersopt&nbsp; class identifieropt&nbsp; type-parameter-listopt&nbsp; class &#8211;baseopt&nbsp; type-parameter-constraints-clauseopt&nbsp; class-body;opt&nbsp; (特性可选&nbsp; 类修饰符可选&nbsp; 类标识符可选&nbsp; 类型参数列表可选&nbsp; 基类可选&nbsp;&nbsp;&nbsp; 类型参数约束语句可选&nbsp;&nbsp;&nbsp; 类体; 可选&nbsp; )</p>
<p style="text-indent: 2em">&nbsp;除非提供了类型参数列表，类声明可以不提供类型参数化约束语句。</p>
<p style="text-indent: 2em">提供了类型参数列表的类声明是一个泛型类声明。此外，任何嵌入到泛型类声明或泛型结构声明中的类，自身是一个泛型类声明，因为必须提供包含类型的类型参数以创建构造类型（constructed type）；</p>
<p style="text-indent: 2em">泛型类通过使用构造类型而被引用（&#167;20.5）。给定泛型类声明</p>
<p style="text-indent: 2em">class List&lt;T&gt;{}</p>
<p style="text-indent: 2em">这是构造类型的一些例子，List&lt;T&gt;，List&lt;int&gt;和List&lt;List&lt;string&gt;&gt;。构造类型可以使用一个或多个参数，例如List&lt;T&gt;被称为开放构造类型（open constructed type）。不使用类型参数的构造类型，例如List&lt;int&gt;被称为封闭构造类型（closed constructed type）。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">泛型类型不可以被&#8220;重载&#8221;；也就是说，和普通类型一样在一个作用域内，泛型类型必须被唯一地命名。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">class C{}</p>
<p style="text-indent: 2em">class C&lt;V&gt;{}//错误，C定义了两次</p>
<p style="text-indent: 2em">class C&lt;U,V&gt;{}//错误，C定义了两次</p>
<p style="text-indent: 2em">然而在非限定类型名字查找（&#167;20.9.3）中使用的类型查找规则和成员访问(&#167;20.9.4），确实考虑到了类型参数的个数。</p>
<p style="text-indent: 2em">20.1.1类型参数</p>
<p style="text-indent: 2em">类型参数可以在一个类声明上提供。每个类型参数是一个简单的标识符，它指示了用来创建一个构造类型的类型参数的占位符。类型参数是在后面将要被提供的类型的形式占位符。相反，类型参数&#167;20.5.1）只是在构造类型被引用时，实际类型的一个替代。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">type-parameter-list：（类型参数列表：）</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;type-parameters&gt; （&lt;类型参数&gt;）</p>
<p style="text-indent: 2em">type-parameters：（类型参数：）</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type-parameter（类型参数）</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type-parameters type-parameter（类型参数，类型参数）</p>
<p style="text-indent: 2em">type-parameter：（类型参数：）</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; attributesopt identifier（特性可选 标识符）</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">在类声明中的每个类型参数在类的声明空间（&#167;3.3）定义了一个名字。由此，它不能和另一个类型参数或在类中声明的成员有同样的名字。类型参数不能和类型自身有同样的名字。</p>
<p style="text-indent: 2em">在一个类中的类型参数的作用域（&#167;3.7），包括基类 、 类型参数约束语句和类体。不像类的成员，它没有扩展到派生类。在其作用域之内，类型参数可以被用作一个类型。</p>
<p style="text-indent: 2em">type（类型）：</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value-type（值类型）</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reference-type（引用类型）</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type-parameter（类型参数）</p>
<p style="text-indent: 2em">由于类型参数可以被许多不同的实际类型实参所实例化，类型参数与其他类型相比将略微有一些不同的操作和限制。包括如下内容。</p>
<p style="text-indent: 2em">类型参数不能用于直接声明一个基类型或者接口 </p>
<p style="text-indent: 2em">对于在类型参数上的成员查找规则，如果约束存在，则依赖于应用到该类型参数的约束。更详细地说明参看&#167;20.7.4。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">类型参数可行的转换依赖于应用到该类型参数上的约束（如果有的话）。详细地说明参看&#167;20.7.4。 </p>
<p style="text-indent: 2em">字面null不能被转换到由类型参数所给定的类型，除非类型参数是由一个类约束（&#167;20.7.4）所约束。然而可以使用一个默认值表达式（&#167;20.8.1）代替。此外，由一个类型参数给定的类型的值可以使用&#8220;==&#8221;和&#8220;!=&#8221;（&#167;20.8.4）与null进行比较。 </p>
<p style="text-indent: 2em">如果类型参数通过一个构造函数约束（constructor-constraint）（&#167;20.7）而约束，new表达式只能用过一个类型参数而被使用。 </p>
<p style="text-indent: 2em">类型参数不能用于特性内的任何地方。 </p>
<p style="text-indent: 2em">类型参数不能用于成员访问，或者表示一个静态成员或者嵌套类型的类型名字（&#167;20.9.1、&#167;20.9.4）。 </p>
<p style="text-indent: 2em">在不安全代码中，类型参数不能被用作托管类型（&#167;18.2）。</p>
<p style="text-indent: 2em">作为一种类型，类型参数纯粹只是一个编译时构件。在运行时，每个类型参数被绑定到运行时类型，它是通过泛型类型声明所提供的类型实参所指定的。为此，在运行时，使用类型参数声明的变量类型是一个封闭类型（closed type）（&#167;20.5.2）。所有语句和表达式在运行时执行所使用的类型参数，都是由那个参数作为类型实参而提供的实际类型。</p>
<p style="text-indent: 2em">20.1.2实例类型</p>
<p style="text-indent: 2em">每个类声明都有与之关联的构造类型，即实例类型(instance type)。对于一个泛型类声明，实例类型通过创建一个来自于类型声明的构造类型（&#167;20.4）而形成，它使用对应于类型参数的每一个类型实参。由于实例化类型使用类型参数，在类型参数作用域内（类声明之内），它是唯一有效的。实例类型在类声明中是this的类型。对于非泛型类，实例类型只是一个声明类型。下面展示了几个声明类，以及它们的实例类型。</p>
<p style="text-indent: 2em">class A&lt;T&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //实例类型：A&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; class B{}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //实例类型：A&lt;T&gt;.B</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; class C&lt;U&gt;{}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //实例类型：A&lt;T&gt;.C&lt;U&gt;</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">class D{}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //实例类型：D</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">20.1.3基类规范</p>
<p style="text-indent: 2em">在类声明中指定的基类可以是一个构造类型（&#167;20.5）。一个基类其自身不能是一个类型参数，但在其作用域内可以包含类型参数。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">class Extend&lt;V&gt;: V{}//错误，类型参数被用作基类</p>
<p style="text-indent: 2em">泛型类声明不能使用System.Attribute作为直接或间接基类。</p>
<p style="text-indent: 2em">在一个类声明中指定的基接口可以是构造接口类型（&#167;20.5）。基接口自身不能是类型参数，但在其作用域内可以包含类型参数，下面的代码演示了如何实现和扩展构造类型。</p>
<p style="text-indent: 2em">class C&lt;U,V&gt;{}</p>
<p style="text-indent: 2em">Interface I1&lt;V&gt;{}</p>
<p style="text-indent: 2em">class D:C&lt;string&nbsp; , int&gt;,I1&lt;string&gt;{}</p>
<p style="text-indent: 2em">class E&lt;T&gt;:C&lt;int,T&gt; ,I1&lt;T&gt;{}</p>
<p style="text-indent: 2em">泛型类型声明的基接口必须满足&#167;20.3.1中所描述的唯一性规则。</p>
<p style="text-indent: 2em">从基类或接口重写或实现方法的类的方法，必须提供特定类型的合适方法。下面的代码演示了方法如何被重写和实现。这将会在&#167;20.1.10中进一步解释。</p>
<p style="text-indent: 2em">class C&lt;U,V&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public virtual void&nbsp; M1(U x , List&lt;V&gt; y){&#8230;}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">interface I1&lt;V&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; V&nbsp; M2(V x);</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">class D:C&lt;string&nbsp; , int&gt;,I1&lt;string&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public override void M1(string x , List&lt;int&gt; y){&#8230;}</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public string M2(string x){&#8230;}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">20.1.4泛型类的成员</p>
<p style="text-indent: 2em">泛型类的所有成员都可以直接地或者作为构造类型的一部分，从任何封闭类（enclosing class）中使用类型参数。当特定的封闭构造类型在运行时被使用时，类型参数的每次使用都由构造类型所提供的实际类型实参所代替。例如</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">class C&lt;V&gt;</p>
<p style="text-indent: 2em">{&nbsp;&nbsp; </p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public V f1;&nbsp;&nbsp;&nbsp; </p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public C&lt;V&gt; f2=null;</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public C(V x){</p>
<p style="text-indent: 2em">this.f1 = x;</p>
<p style="text-indent: 2em">this.f2 = this;</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">class Application</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; static void Main(){</p>
<p style="text-indent: 2em">C&lt;int&gt; x1= new C&lt;int &gt;(1);</p>
<p style="text-indent: 2em">Console.WriteLine(x1.f1);&nbsp; //打印1</p>
<p style="text-indent: 2em">C&lt;double&gt; x2 = new C&lt;double&gt;(3.1415);</p>
<p style="text-indent: 2em">Console.WriteLine(x2.f1); //打印 3.1415</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">在实例函数成员之内，this的类型就是声明的实例类型（&#167;20.1.2）。</p>
<p style="text-indent: 2em">除了使用类型参数作为类型和成员，在泛型类声明中也遵循和非泛型类成员相同的规则。适用于特定种类成员的附加规则将在后面几节进行讨论。</p>
<p style="text-indent: 2em">20.1.5泛型类中的静态字段</p>
<p style="text-indent: 2em">在一个泛型类声明中的静态变量，在相同封闭构造类型（&#167;20.5.2）所有实例中被共享，但在不同封闭构造类型的实例中[1]，是不被共享的。这些规则不管静态变量的类型包含那种类型参数都适用。</p>
<p style="text-indent: 2em">例如</p>
<p style="text-indent: 2em">class C&lt;V&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; static int count = 0;</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public C()</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">count++;</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">public static int Count{</p>
<p style="text-indent: 2em">get{return count;}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">class Application</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static void Main()</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">C&lt;int&gt; x1 = new C&lt;int&gt;();</p>
<p style="text-indent: 2em">Console.WriteLine(C&lt;int&gt;.Count);//打印 1</p>
<p style="text-indent: 2em">C&lt;double&gt; x2 = new C&lt;double&gt;();</p>
<p style="text-indent: 2em">Console.WriteLine(C&lt;int&gt;.Count);//打印 1</p>
<p style="text-indent: 2em">C&lt;int&gt; x3 = new C&lt;int&gt;();</p>
<p style="text-indent: 2em">Console.WriteLine(C&lt;int&gt;.Count);//打印 2</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">--------------------------------------------------------------------------------</p>
<p style="text-indent: 2em">[1] 这是很容易理解的，因为在运行时，不同的封闭构造类型，是属于不同的类型，比如List&lt;int&gt; 和List&lt;string&gt; 这二者的实例是不能共享静态变量的。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">20.1.6泛型类中的静态构造函数</p>
<p style="text-indent: 2em">在泛型类中的静态构造函数被用于初始化静态字段，为每个从特定泛型类声明中创建的不同的封闭构造类型，执行其他初始化。泛型类型声明的类型参数在作用域之内，可以在静态构造函数体内被使用。</p>
<p style="text-indent: 2em">如果下列情形之一发生，一个新的封闭构造类类型将被首次初始化。</p>
<p style="text-indent: 2em">一个封闭构造类型的实例被创建时 </p>
<p style="text-indent: 2em">封闭构造类型的任何静态成员被引用时</p>
<p style="text-indent: 2em">为了初始化一个新的封闭的构造类类型，首先那个特定封闭类型的一组新静态字段（&#167;20.1.5）将会被创建。每个静态字段都被初始化为其默认值（&#167;5.2）。接着，静态字段初始化器（&#167;10.4.5.1）将为这些静态字段执行。最后静态构造函数将被执行。</p>
<p style="text-indent: 2em">由于静态构造函数将为每个封闭构造类类型执行一次，那么在不能通过约束（&#167;20.7）检查的类型参数上实施运行时检查，将会很方便。例如，下面的类型使用一个静态构造函数检查一个类型参数是否是一个引用类型。</p>
<p style="text-indent: 2em">class Gen&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; static Gen(){</p>
<p style="text-indent: 2em">if((object)T.default != null){</p>
<p style="text-indent: 2em">throw new ArgumentException(&#8220;T must be a reference type&#8221;);</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">20.1.7 访问受保护的成员</p>
<p style="text-indent: 2em">在一个泛型类声明中，对于继承的受保护的实例成员的访问是允许的，通过从泛型类构造的任何类型的实例就可以做到。尤其是，用于访问&#167;3.5.3中指定的protected和protected internal实例成员的规则，对于泛型使用如下的规则进行了扩充。</p>
<p style="text-indent: 2em">在一个泛型类G中，对于一个继承的受保护的实例成员M，使用E.M的基本表达式是允许的，前提是E的类型是一个从G构造的类类型，或继承于一个从G构造的类类型的类类型。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">在例子</p>
<p style="text-indent: 2em">class C&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; protected T x;</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">class D&lt;T&gt; :C&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; static void F(){</p>
<p style="text-indent: 2em">D&lt;T&gt; dt = new D&lt;T&gt;();</p>
<p style="text-indent: 2em">D&lt;int&gt; di = new D&lt;int&gt;();</p>
<p style="text-indent: 2em">D&lt;string&gt; ds = new D&lt;string&gt;();</p>
<p style="text-indent: 2em">dt.x = T.default;</p>
<p style="text-indent: 2em">di.x = 123;</p>
<p style="text-indent: 2em">ds.x = &#8220;test&#8221;;</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">三个对x的赋值语句都是允许的，因为它们都通过从泛型构造的类类型的实例发生。</p>
<p style="text-indent: 2em">20.1.8在泛型类中重载</p>
<p style="text-indent: 2em">在一个泛型类声明中的方法、构造函数、索引器和运算符可以被重载。但为了避免在构造类中的歧义，这些重载是受约束的。在同一个泛型类声明中使用相同的名字声明的两个函数成员必须具有这样的参数类型，也就是封闭构造类型中不能出现两个成员使用相同的名字和签名。当考虑所有可能的封闭构造类型时，这条规则包含了在当前程序中目前不存在的类型是实参，但它仍然是可能出现的[1]。在类型参数上的类型约束由于这条规则的目的而被忽略了。</p>
<p style="text-indent: 2em">下面的例子根据这条规则展示了有效和无效的重载。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">nterface I1&lt;T&gt; {&#8230;}</p>
<p style="text-indent: 2em">interface I2&lt;T&gt;{&#8230;}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">class G1&lt;U&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; long F1(U u);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //无效重载，G&lt;int&gt;将会有使用相同签名的两个成员</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; int F1(int i);</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F2(U u1, U u2);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //有效重载，对于U没有类型参数</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F2(int I , string s);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //可能同时是int和string </p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F3(I1&lt;U&gt;a);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //有效重载</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F3(I2&lt;U&gt;a);</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F4(U a);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //有效重载</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F4(U[] a);}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">class G2&lt;U,V&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F5(U u , V v);&nbsp;&nbsp;&nbsp;&nbsp; //无效重载，G2&lt;int , int&gt;将会有两个签名相同的成员</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F5(V v, U u);</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F6(U u , I1&lt;V&gt; v);//无效重载，G2&lt;I1&lt;int&gt;,int&gt;将会有两个签名相同的成员</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F6(I1&lt;V&gt; v , U u);</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F7(U u1,I1&lt;V&gt; V2);//有效的重载，U不可能同时是V和I1&lt;V&gt;</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F7(V v1 , U u2);</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F8(ref U u);&nbsp;&nbsp; //无效重载</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F8(out V v);</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">class C1{&#8230;}</p>
<p style="text-indent: 2em">class C2{&#8230;}</p>
<p style="text-indent: 2em">class G3&lt;U , V&gt; where U:C1 where V:C2</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F9(U u);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //无效重载，当检查重载时，在U和V上的约束将被忽略</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; void F9(V v);</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">20.1.9参数数组方法和类型参数</p>
<p style="text-indent: 2em">类型参数可以被用在参数数组的类型中。例如，给定声明</p>
<p style="text-indent: 2em">class C&lt;V&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">static void F(int x, int y ,params V[] args);</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">方法的扩展形式的如下调用</p>
<p style="text-indent: 2em">C&lt;int&gt;.F(10, 20);</p>
<p style="text-indent: 2em">C&lt;object&gt;.F(10,20,30,40);</p>
<p style="text-indent: 2em">C&lt;string&gt;.F(10,20,&#8221;hello&#8221;,&#8221;goodbye&#8221;);</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">对应于如下形式：</p>
<p style="text-indent: 2em">C&lt;int&gt;.F(10,20, new int[]{});</p>
<p style="text-indent: 2em">C&lt;object&gt;.F(10,20,new object[]{30,40});</p>
<p style="text-indent: 2em">C&lt;string&gt;.F(10,20,new string[](&#8220;hello&#8221;,&#8221;goodbye&#8221;));</p>
<p style="text-indent: 2em">20.1.10重写和泛型类</p>
<p style="text-indent: 2em">在泛型类中的函数成员可以重写基类中的函数成员。如果基类是一个非泛型类型或封闭构造类型，那么任何重写函数成员不能有包含类型参数的组成类型。然而，如果一个基类是一个开放构造类型，那么重写函数成员可以使用在其声明中的类型参数。当重写基类成员时，基类成员必须通过替换类型实参而被确定，如&#167;20.5.4中所描述的。一旦基类的成员被确定，用于重写的规则和非泛型类是一样的。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">下面的例子演示了对于现有的泛型其重写规则是如何工作的。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">abstract class C&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public virtual T F(){&#8230;}</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public virtual C&lt;T&gt; G(){&#8230;}</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public virtual void H(C&lt;T&gt;&nbsp; x ){&#8230;}</p>
<p style="text-indent: 2em">} </p>
<p style="text-indent: 2em">class D:C&lt;string&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public override string F(){&#8230;}//OK</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public override C&lt;string&gt; G(){&#8230;}//OK</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public override void H(C&lt;T&gt; x); //错误，应该是C&lt;string&gt;</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">class E&lt;T,U&gt;:C&lt;U&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public override U F(){&#8230;}//OK</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public override C&lt;U&gt; G(){&#8230;}//OK</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public override void H(C&lt;T&gt; x){&#8230;}//错误，应该是C&lt;U&gt;</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">20.1.11泛型类中的运算符</p>
<p style="text-indent: 2em">泛型类声明可以定义运算符，它遵循和常规类相同的规则。类声明的实例类型（&#167;20.1.2）必须以一种类似于运算符的常规规则的方式，在运算符声明中被使用，如下</p>
<p style="text-indent: 2em">一元运算符必须接受一个实例类型的单一参数。一元运算符&#8220;++&#8221;和&#8220;—&#8221;必须返回实例类型。 </p>
<p style="text-indent: 2em">至少二元运算符的参数之一必须是实例类型。 </p>
<p style="text-indent: 2em">转换运算符的参数类型和返回类型都必须是实例类型。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">下面展示了在泛型类中几个有效的运算符声明的例子</p>
<p style="text-indent: 2em">class X&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public static X&lt;T&gt; operator ++(X(T) operand){&#8230;}</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public static int operator *(X&lt;T&gt; op1, int op2){&#8230;}</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public static explicit operator X&lt;T&gt;(T value){&#8230;}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">对于一个从源类型S到目标类型T的转换运算符，当应用&#167;10.9.3中的规则时，任何关联S或T的类型参数被认为是唯一类型，它们与其他类型没有继承关系，并且在这些类型参数上的任何约束都将被忽略。</p>
<p style="text-indent: 2em">在例子</p>
<p style="text-indent: 2em">class C&lt;T&gt;{&#8230;}</p>
<p style="text-indent: 2em">class D&lt;T&gt;:C&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public static implicit operator C&lt;int&gt;(D&lt;T&gt; value){&#8230;}//OK</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public static implicit operator C&lt;T&gt;(D&lt;T&gt; value){&#8230;}//错误</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">第一个运算符声明是允许的，由于&#167;10.9.3的原因，T和int被认为是没有关系的唯一类型。然而，第二个运算符是一个错误，因为C&lt;T&gt;是D&lt;T&gt;的基类。</p>
<p style="text-indent: 2em">给定先前的例子，为某些类型实参声明运算符，指定已经作为预定义转换而存在的转换是可能的。</p>
<p style="text-indent: 2em">struct Nullable&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public static implicit operator Nullable&lt;T&gt;(T value){&#8230;}</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public static explicit operator T(Nullable&lt;T&gt; value){&#8230;}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">当类型object作为T的类型实参被指定，第二个运算符声明了一个已经存在的转换（从任何类型到object是一个隐式的，也可以是显式的转换）。</p>
<p style="text-indent: 2em">在两个类型之间存在预定义的转换的情形下，在这些类型上的任何用户定义的转换都将被忽略。尤其是</p>
<p style="text-indent: 2em">如果存在从类型S到类型T的预定义的隐式转换（&#167;6.1），所有用户定义的转换（隐式的或显式的）都将被忽略。 </p>
<p style="text-indent: 2em">如果存在从类型S到类型T的预定义的显式转换，那么任何用户定义的从类型S到类型T的显式转换都将被忽略。但用户定义的从S到T的隐式转换仍会被考虑。</p>
<p style="text-indent: 2em">对于所有类型除了object，由Nullable&lt;T&gt;类型声明的运算符都不会与预定义的转换冲突。例如</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">void F(int I , Nullable&lt;int&gt; n){</p>
<p style="text-indent: 2em">i = n;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //错误</p>
<p style="text-indent: 2em">i = (int)n;&nbsp;&nbsp;&nbsp;&nbsp; //用户定义的显式转换</p>
<p style="text-indent: 2em">n = i;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //用户定义的隐式转换</p>
<p style="text-indent: 2em">n = (Nullable&lt;int&gt;)i;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //用户定义的隐式转换</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">然而，对于类型object，预定义的转换在所有情况隐藏了用户定义转换,除了一种情况：</p>
<p style="text-indent: 2em">void F(object o , Nullable&lt;object&gt; n){</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; o = n;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //预定义装箱转换</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; o= (object)n;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //预定义装箱转换</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; n= o;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //用户定义隐式转换</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; n = (Nullable&lt;object&gt;)o;&nbsp;&nbsp;&nbsp; //预定义取消装箱转换</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">20.1.12泛型类中的嵌套类型</p>
<p style="text-indent: 2em">泛型类声明可以包含嵌套类型声明。封闭类的类型参数可以在嵌套类型中使用。嵌套类型声明可以包含附加的类型参数，它只适用于该嵌套类型。</p>
<p style="text-indent: 2em">包含在泛型类声明中的每个类型声明是隐式的泛型类型声明。当编写一个嵌套在泛型类型内的类型的引用时，包含构造类型，包括它的类型实参，必须被命名。然而，在外部类中，内部类型可以被无限制的使用；当构造一个内部类型时，外部类的实例类型可以被隐式地使用。下面的例子展示了三个不同的引用从Inner创建的构造类型的方法，它们都是正确的；前两个是等价的。</p>
<p style="text-indent: 2em">class Outer&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; class Inner&lt;U&gt;</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; {</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static void F(T t , U u){&#8230;}</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; }</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; static void F(T t)</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">Outer&lt;T&gt;.Inner&lt;string &gt;.F(t,&#8221;abc&#8221;);//这两个语句有同样的效果</p>
<p style="text-indent: 2em">Inner&lt;string&gt;.F(t,&#8221;abc&#8221;);</p>
<p style="text-indent: 2em">Outer&lt;int&gt;.Inner&lt;string&gt;.F(3,&#8221;abc&#8221;);&nbsp;&nbsp;&nbsp; //这个类型是不同的</p>
<p style="text-indent: 2em">Outer.Inner&lt;string&gt;.F(t , &#8220;abc&#8221;);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //错误，Outer需要类型参数</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">尽管这是一种不好的编程风格，但嵌套类型中的类型参数可以隐藏一个成员，或在外部类型中声明的一个类型参数。</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">class Outer&lt;T&gt;</p>
<p style="text-indent: 2em">{</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; class Inner&lt;T&gt; //有效，隐藏了 Ouer的 T</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; {</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; public T t; //引用Inner的T</p>
<p style="text-indent: 2em">&nbsp;&nbsp;&nbsp; }</p>
<p style="text-indent: 2em">}</p>
<p style="text-indent: 2em">&nbsp;</p>
<p style="text-indent: 2em">20.1.13应用程序入口点</p>
<p style="text-indent: 2em">应用程序入口点不能在一个泛型类声明中。</p>
<p style="text-indent: 2em">20.2泛型结构声明</p>
<p style="text-indent: 2em">像类声明一样，结构声明可以有可选的类型参数。</p>
<p style="text-indent: 2em">struct-declaration:（结构声明：）</p>
<p style="text-indent: 2em">attributes opt struct-modifiers opt&nbsp; struct identifier&nbsp; type-parameter-list opt&nbsp; struct-interfaces opt type-parameter-constraints-clauses opt&nbsp; struct-body ;opt</p>
<p style="text-indent: 2em">（特性可选 结构修饰符可选 struct&nbsp; 标识符 类型参数列表可选 结构接口可选 类型参数约束语句可选&nbsp; 结构体;可选）</p>
<p style="text-indent: 2em">&nbsp;</p