<展现C#> 第九章 配置和调度

第九章 配置和调度

在上一章,你学到如何创建一个通用语言运行时(CLR)组件,且如何在一个简单的测试应用程序中使用它。虽然CLR组件就要准备装载了,但你还是应该思考以下技术之一:
。条件编译
。文档注释
。代码版本化

9.1 条件编译
没有代码的条件编译功能,我就不能继续工作。条件编译允许执行或包括基于某些条件的代码;例如,生成应用程序
的一个查错(DEBUG)版本、演示(DEMO)版本或零售(RELEASE)版本。可能被包括或被执行的代码的例子为许可证代
码、 屏幕保护或你出示的任何程序。
在C#中,有两种进行条件编译的方法:
。预处理用法
。条件属性
9.1.1 预处理用法
在C++中,在编译器开始编译代码之前,预处理步骤是分开的。在C#中,预处理被编译器自己模拟—— 没有分离的预
处理。它只不过是条件编译。
尽管C#编译器不支持宏,但它具有必需的功能,依据符号定义的条件,排除和包括代码。以下小节介绍了在C#中受支
持的各种标志,它们与在C++中看到的相似。
。定义符号
。依据符号排除代码
。引起错误和警告
9.1.1.1 定义符号
你不能使用随C#编译器一起的预处理创建“define 标志:符号:定义 ”宏,但是,你仍可以定义符号。根据某些符号
是否被定义,可以排除或包括代码。
第一种定义符号的办法是在C#源文件中使用 #define标志:
#define DEBUG
这样定义了符号DEBUG,且范围在它所定义的文件内。请注意,必须要先定义符号才能使用其它语句。例如,以下代码
段是不正确的:

using System;
#define DEBUG

编译器将标记上述代码为错误。你也可以使用编译器定义符号(用于所有的文件):
csc /define:DEBUG mysymbols.cs
如果你想用编译器定义多种符号,只需用分号隔开它们:
csc /define:RELEASE;DEMOVERSION mysymbols.cs
在C#源文件中,对这两种符号的定义分为两行 #define 标志。
有时,你可能想要取消源文件中(例如,较大项目的源文件)的某种符号。可以用 #undef 标志取消定义:
#undef DEBUG
#define的“定义标志:符号: 定义”规则同样适用于#undef: 它的范围在自己定义的文件之内,要放在任何语句如
using语句之前。
这就是全部有关用C#预处理定义符号和取消定义符号所要了解的知识。以下小节说明如何使用符号有条件地编译代
码。

9.1.1.2 依据符号包括和排除代码
最重要的“if标志:符号:包括代码”方式的目的为,依据符号是否被定义,有条件地包括和排除代码。清单9.1 包含
了已出现过的源码,但这次它依据符号被有条件地编译。

清单 9.1 利用 #if 标志有条件地包括代码

1: using System;
2:
3: public class SquareSample
4: {
5: public void CalcSquare(int nSideLength, out int nSquared)
6: {
7: nSquared = nSideLength * nSideLength;
8: }
9:
10: public int CalcSquare(int nSideLength)
11: {
12: return nSideLength*nSideLength;
13: }
14: }
15:
16: class SquareApp
17: {
18: public static void Main()
19: {
20: SquareSample sq = new SquareSample();
21:
22: int nSquared = 0;
23:
24: #if CALC_W_OUT_PARAM
25: sq.CalcSquare(20, out nSquared);
26: #else
27: nSquared = sq.CalcSquare(15);
28: #endif
29: Console.WriteLine(nSquared.ToString());
30: }
31: }

注意,在这个源文件中没有定义符号。当编译应用程序时,定义(或取消定义)符号:
csc /define:CALC_W_OUT_PARAM square.cs
根据“ if标志:符号:包括代码”的符号定义,不同的 CalcSquare 被调用了。用来对符号求值的模拟预处理标志为
#if、 #else和 #endif。它们产生的效果就象C#相应的if 语句那样。你也可以使用逻辑“与”(&&)、逻辑“或”
(¦¦)以及“否”(!)。它们的例子显示在清单9.2 中。

清单 9.2 使用#elif 在#if标志中创建多个分支

1: // #define DEBUG
2: #define RELEASE
3: #define DEMOVERSION
4:
5: #if DEBUG
6: #undef DEMOVERSION
7: #endif
8:
9: using System;
10:
11: class Demo
12: {
13: public static void Main()
14: {
15: #if DEBUG
16: Console.WriteLine("Debug version");
17: #elif RELEASE && !DEMOVERSION
18: Console.WriteLine("Full release version");
19: #else
20: Console.WriteLine("Demo version");
21: #endif
22: }
23: }

在这个“if标志:符号:包含代码”例子中,所有的符号都在C#源文件中被定义。注意第6行#undef语句增加的那部分。
由于不编译DEBUG代码的DEMO版本(任意选择),我确信它不会被某些人无意中定义了,而且总当DEBUG被定义时,就取消
DEMO版本的定义。
接着在第15~21行,预处理符号被用来包括各种代码。注意#elif标志的用法,它允许你把多个分支加到#if 标志。该
代码运用逻辑操作符“&&”和非操作符“!”。也可能用到逻辑操作符“¦¦”,以及等于和不等于操作
符。

9.1.1.3 引起错误并警告
另一种可能的“警告 标志错误 标志”预处理标志的使用,是依据某些符号(或根本不依据,如果你这样决定)引
起错误或警告。各自的标志分别为 #warning和#error,而清单9.3 演示了如何在你的代码中使用它们。
清单 9.3 使用预处理标志创建编译警告和错误

1: #define DEBUG
2: #define RELEASE
3: #define DEMOVERSION
4:
5: #if DEMOVERSION && !DEBUG
6: #warning You are building a demo version
7: #endif
8:
9: #if DEBUG && DEMOVERSION
10: #error You cannot build a debug demo version
11: #endif
12:
13: using System;
14:
15: class Demo
16: {
17: public static void Main()
18: {
19: Console.WriteLine("Demo application");
20: }
21: }

在这个例子中,当你生成一个不是DEBUG版本的DEMO版本时,就发出了一个编译警告(第5行~第7行)。当你企图生成
一个DEBUG DEMO版本时,就引起了一个错误,它阻止了可执行文件的生成。对比起前面只是取消定义令人讨厌的符号的例
子,这些代码告诉你,“警告 标志错误 标志”企图要做的工作被认为是错误的。这肯定是更好的处理办法。
9.1.1.4 条件属性
C++的预处理也许最经常被用来定义宏,宏可以解决一种程序生成时的函数调用,而却不能解决另一种程序生成时的任
何问题。这些例子包括 ASSERT和TRACE 宏,当定义了DEBUG符号时,它们对函数调用求值,当生成一个RELEASE版本时,求
值没有任何结果。

当了解到宏不被支持时,你也许会猜测,条件功能已经消亡了。幸亏我可以报道,不存在这种情况。你可以利用条件
属性,依据某些已定义符号来包括方法。:

[conditional("DEBUG")]
public void SomeMethod() { }

仅当符号DEBUG被定义时,这个方法被加到可执行文件。并且调用它,就象
SomeMethod();

当该方法不被包括时,它也被编译器声明。功能基本上和使用C++条件宏相同。
在例子开始之前,我想指出,条件方法必须具有void的返回类型,不允许其它返回类型。然而,你可以传递你想使用
的任何参数。
在清单9.4 中的例子演示了如何使用条件属性重新生成具有C++的TRACE宏一样的功能。为简单起见,结果直接输出到
屏幕。你也可以根据需要把它定向到任何地方,包括一个文件。

清单 9.4 使用条件属性实现方法

1: #define DEBUG
2:
3: using System;
4:
5: class Info
6: {
7: [conditional("DEBUG")]
8: public static void Trace(string strMessage)
9: {
10: Console.WriteLine(strMessage);
11: }
12:
13: [conditional("DEBUG")]
14: public static void TraceX(string strFormat,params object[] list)
15: {
16: Console.WriteLine(strFormat, list);
17: }
18: }
19:
20: class TestConditional
21: {
22: public static void Main()
23: {
24: Info.Trace("Cool!");
25: Info.TraceX("{0} {1} {2}","C", "U", 2001);
26: }
27: }

在Info类中,有两个静态方法,它们根据DEBUG符号被有条件地编译:Trace,接收一个参数,而TraceX则接收n个参
数。Trace的实现直接了当。然而,TraceX实现了一个你从没有见过的关键字:params。
params 关键字允许你指定一个方法参数,它实际上接收了任意数目的参数。其类似C/C++的省略参数。注意,它必须
是方法调用的最后一个参数,而且在参数列表中,你只能使用它一次。毕竟,它们的局限性极其明显。
使用params 关键字的意图就是要拥有一个Trace方法,该方法接收一个格式字符串以及无数个置换对象。幸好,还有
一个支持格式字符串和对象数组的 WriteLine方法(第16行)。
这个小程序产生的哪一个输出完全取决于DEBUG是否被定义。当DEBUG符号被定义时,方法都被编译和执行。如果DEBUG
不被定义,对Trace和TraceX的调用也随之消失。
条件方法是给应用程序和组件增加条件功能的一个真正强大的手段。用一些技巧,你就可以根据由逻辑“或”
(¦¦)以及逻辑“与”(&&)连接起来的多个符号,生成条件方法。然而,对于这些方案,我想给你推荐C#
文档。

9.2 在XML中的文档注释
很多程序员根本不喜欢的一项任务就是写作,包括写注释和写文档。然而,有了C#,你就找到改变老习惯的好理由:
你可以用代码的注释自动生成文档。
由编译器生成的输出结果是完美的XML。它可以作为组件文档的输入被使用,以及作为显示帮助并揭示组件内部细节的
工具。例如, Visual Studio 7 就是这样一种工具。
这一节专门为你说明如何最好地运用C#的文档功能。该例子涉及的范围很广,所以你不能有这样的借口,说它过于复
杂,以至很难领会如何加入文档注释。文档是软件极其重要的一部分,特别是要被其他开发者使用的组件的文档。
在以下小节中,文档注解用来说明RequestWebPage 类。我已分别在以下几小节中做出解释:
。描述一个成员
。添加备注和列表
。提供例子
。描述参数
。描述属性
。编译文档

9.2.1 描述一个成员
第一步,为一个成员添加一个简单的描述。你可以用

  1<summary> 标签这样做:   
  2/// <summary>This is .... </summary>   
  3  
  4  
  5每一个文档注释起始于由三个反斜杠组成的符号“///”。你可以把文档注释放在想要描述的成员之前:   
  6  
  7/// <summary>Class to tear a Webpage from a Webserver</summary>   
  8  
  9public class RequestWebPage   
 10  
 11使用<para>和 </para>标签,为描述添加段落。用<see>标签引用其它已有了注释的成员。   
 12/// <para>Included in the <see cref="RequestWebPage"></see> class</para>   
 13  
 14增加一个链接到RequestWebPage类的描述。注意,用于标签的语法是XML语法,这意味着标签大写化的问题,而且标签   
 15必须正确地嵌套。   
 16当为一个成员添加文档时,另一个有趣的标签是<seealso> 。它允许你描述可能使读者非常感兴趣的其它话题。   
 17  
 18/// <seealso cref="System.Net"></seealso>   
 19  
 20前面的例子告诉读者,他可能也想查阅System.Net 名字空间的文档。你一定要给超出当前范围的项目规定一个完全资   
 21格名。   
 22作为许诺,清单9.5 包含 RequestWebPage类中正在工作的文档的所有例子。看一下如何使用标签以及嵌套如何为组件   
 23产生文档。   
 24  
 25清单 9.5 利用 <summary>, <see>, <para>, and <seealso> 标签描述一个成员   
 26  
 271: using System;   
 282: using System.Net;   
 293: using System.IO;   
 304: using System.Text;   
 315:   
 326: /// <summary>Class to tear a Webpage from a Webserver</summary>   
 337: public class RequestWebPage   
 348: {   
 359: private const int BUFFER_SIZE = 128;   
 3610:   
 3711: /// <summary>m_strURL stores the URL of the Webpage</summary>   
 3812: private string m_strURL;   
 3913:   
 4014: /// <summary>RequestWebPage() is the constructor for the class   
 4115: /// <see cref="RequestWebPage"></see> when called without arguments.</summary>   
 4216: public RequestWebPage()   
 4317: {   
 4418: }   
 4519:   
 4620: /// <summary>RequestWebPage(string strURL) is the constructor for the class   
 4721: /// <see cref="RequestWebPage"></see> when called with an URL as parameter.</summary>   
 4822: public RequestWebPage(string strURL)   
 4923: {   
 5024: m_strURL = strURL;   
 5125: }   
 5226:   
 5327: public string URL   
 5428: {   
 5529: get { return m_strURL; }   
 5630: set { m_strURL = value; }   
 5731: }   
 5832:   
 5933: /// <summary>The GetContent(out string strContent) method:   
 6034: /// <para>Included in the <see cref="RequestWebPage"></see> class</para>   
 6135: /// <para>Uses variable <see cref="m_strURL"></see></para>   
 6236: /// <para>Used to retrieve the content of a Webpage. The URL   
 6337: /// of the Webpage (including http://) must already be   
 6438: /// stored in the private variable m_strURL.   
 6539: /// To do so, call the constructor of the RequestWebPage   
 6640: /// class, or set its property <see cref="URL"></see> to the URL string.</para>   
 6741: /// </summary>   
 6842: /// <seealso cref="System.Net"></seealso>   
 6943: /// <seealso cref="System.Net.WebResponse"></seealso>   
 7044: /// <seealso cref="System.Net.WebRequest"></seealso>   
 7145: /// <seealso cref="System.Net.WebRequestFactory"></seealso>   
 7246: /// <seealso cref="System.IO.Stream"></seealso>   
 7347: /// <seealso cref="System.Text.StringBuilder"></seealso>   
 7448: /// <seealso cref="System.ArgumentException"></seealso>   
 7549:   
 7650: public bool GetContent(out string strContent)   
 7751: {   
 7852: strContent = "";   
 7953: // ...   
 8054: return true;   
 8155: }   
 8256: }   
 83  
 849.2.2 添加备注和列表   
 85<remarks> 标签是规定大量文档的地方。与之相比, <summary>只仅仅规定了成员的简短描述。   
 86你不限于只提供段落文本(使用<para>标签)。例如,你可以在备注部分包含bulleted(和有限偶数)列表   
 87(list):   
 88  
 89/// <list type="bullet">   
 90/// <item>Constructor   
 91/// <see cref="RequestWebPage()"></see> or   
 92/// <see cref="RequestWebPage(string)"></see>   
 93/// </item>   
 94/// </list>   
 95  
 96这个list有一项(item),且该item引用了两个不同的构造函数描述。你可以根据需要,任意往list item中添加内   
 97容。   
 98另一个在备注部分很好用的标签是<paramref>。例如,你可以用<paramref>来引用和描述传递给构造函数的参数:   
 99  
100/// <remarks>Stores the URL from the parameter /// <paramref name="strURL"></paramref> in   
101/// the private variable <see cref="m_strURL"></see>.</remarks>   
102public RequestWebPage(string strURL)   
103  
104在清单9.6中,你可以看到所有的这些以及前面的标签正在起作用。   
105  
106清单9.6 为文档添加一个备注和bullet list   
107  
1081: using System;   
1092: using System.Net;   
1103: using System.IO;   
1114: using System.Text;   
1125:   
1136: /// <summary>Class to tear a Webpage from a Webserver</summary>   
1147: /// <remarks>The class RequestWebPage provides:   
1158: /// <para>Methods:   
1169: /// <list type="bullet">   
11710: /// <item>Constructor   
11811: /// <see cref="RequestWebPage()"></see> or   
11912: /// <see cref="RequestWebPage(string)"></see>   
12013: /// </item>   
12114: /// </list>   
12215: /// </para>   
12316: /// <para>Properties:   
12417: /// <list type="bullet">   
12518: /// <item>   
12619: /// <see cref="URL"></see>   
12720: /// </item>   
12821: /// </list>   
12922: /// </para>   
13023: /// </remarks>   
13124: public class RequestWebPage   
13225: {   
13326: private const int BUFFER_SIZE = 128;   
13427:   
13528: /// <summary>m_strURL stores the URL of the Webpage</summary>   
13629: private string m_strURL;   
13730:   
13831: /// <summary>RequestWebPage() is the constructor for the class   
13932: /// <see cref="RequestWebPage"></see> when called without arguments.</summary>   
14033: public RequestWebPage()   
14134: {   
14235: }   
14336:   
14437: /// <summary>RequestWebPage(string strURL) is the constructor for the class   
14538: /// <see cref="RequestWebPage"></see> when called with an URL as parameter.</summary>   
14639: /// <remarks>Stores the URL from the parameter <paramref name="strURL"></paramref> in   
14740: /// the private variable <see cref="m_strURL"></see>.</remarks>   
14841: public RequestWebPage(string strURL)   
14942: {   
15043: m_strURL = strURL;   
15144: }   
15245:   
15346: /// <remarks>Sets the value of <see cref="m_strURL"></see>.   
15447: /// Returns the value of <see cref="m_strURL"></see>.</remarks>   
15548: public string URL   
15649: {   
15750: get { return m_strURL; }   
15851: set { m_strURL = value; }   
15952: }   
16053:   
16154: /// <summary>The GetContent(out string strContent) method:   
16255: /// <para>Included in the <see cref="RequestWebPage"></see> class</para>   
16356: /// <para>Uses variable <see cref="m_strURL"></see></para>   
16457: /// <para>Used to retrieve the content of a Webpage. The URL   
16558: /// of the Webpage (including http://) must already be   
16659: /// stored in the private variable m_strURL.   
16760: /// To do so, call the constructor of the RequestWebPage   
16861: /// class, or set its property <see cref="URL"></see> to the URL string.</para>   
16962: /// </summary>   
17063: /// <remarks>Retrieves the content of the Webpage specified in   
17164: /// the property<see cref="URL"></see> and hands it over to the out   
17265: /// parameter <paramref name="strContent"></paramref>.   
17366: /// The method is implemented using:   
17467: /// <list>   
17568: /// <item>The <see cref="System.Net.WebRequestFactory.Create"></see>method.</item>   
17669: /// <item>The <see cref="System.Net.WebRequest.GetResponse"></see> method.</item>   
17770: /// <item>The <see cref="System.Net.WebResponse.GetResponseStream"></see>method</item>   
17871: /// <item>The <see cref="System.IO.Stream.Read"></see> method</item>   
17972: /// <item>The <see cref="System.Text.StringBuilder.Append"></see> method</item>   
18073: /// <item>The <see cref="System.Text.Encoding.ASCII"></see> property together with its   
18174: /// <see cref="System.Text.Encoding.ASCII.GetString"></see> method</item>   
18275: /// <item>The <see cref="System.Object.ToString"></see> method for the   
18376: /// <see cref="System.IO.Stream"></see> object.</item>   
18477: /// </list>   
18578: /// </remarks>   
18679: /// <seealso cref="System.Net"></seealso>   
18780: public bool GetContent(out string strContent)   
18881: {   
18982: strContent = "";   
19083: // ...   
19184: return true;   
19285: }   
19386: }   
194  
1959.2.3 提供例子   
196要想说明一个对象和方法的用法,最好的办法是提供优秀源代码的例子。因此,不要诧异文档注释也有用于声明例子   
197的标签: <example> and <code>。 <example>标签包含了包括描述和代码的整个例子,而 <code> 标签仅包含了例子的代   
198码(令人惊讶)。   
199清单9.7 说明如何实现代码例子。包括的例子用于两个构造函数。你必须给GetContent方法提供例子。   
200  
201清单.7 利用例子解释概念   
202  
2031: using System;   
2042: using System.Net;   
2053: using System.IO;   
2064: using System.Text;   
2075:   
2086: /// <summary>Class to tear a Webpage from a Webserver</summary>   
2097: /// <remarks> ... </remarks>   
2108: public class RequestWebPage   
2119: {   
21210: private const int BUFFER_SIZE = 12</code></example></code></example></paramref></paramref></para></summary></remarks></seealso></para></see></summary></seealso></see></summary>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus