第九章 配置和調(diào)度
在上一章,你學(xué)到如何創(chuàng)建一個(gè)通用語言運(yùn)行時(shí)(CLR)組件,且如何在一個(gè)簡單的測(cè)試應(yīng)用程序中使用它。雖然CLR 組件就要準(zhǔn)備裝載了,但你還是應(yīng)該思考以下技術(shù)之一: 。條件編譯 。文檔注釋 。代碼版本化
9.1 條件編譯 沒有代碼的條件編譯功能,我就不能繼續(xù)工作。條件編譯允許執(zhí)行或包括基于某些條件的代碼;例如,生成應(yīng)用程序 的一個(gè)查錯(cuò)(DEBUG)版本、演示(DEMO)版本或零售(RELEASE)版本。可能被包括或被執(zhí)行的代碼的例子為許可證代 碼、 屏幕保護(hù)或你出示的任何程序。 在C#中,有兩種進(jìn)行條件編譯的方法: 。預(yù)處理用法 。條件屬性 9.1.1 預(yù)處理用法 在C++中,在編譯器開始編譯代碼之前,預(yù)處理步驟是分開的。在C#中,預(yù)處理被編譯器自己模擬—— 沒有分離的預(yù) 處理。它只不過是條件編譯。 盡管C#編譯器不支持宏,但它具有必需的功能,依據(jù)符號(hào)定義的條件,排除和包括代碼。以下小節(jié)介紹了在C#中受支 持的各種標(biāo)志,它們與在C++中看到的相似。 。定義符號(hào) 。依據(jù)符號(hào)排除代碼 。引起錯(cuò)誤和警告 9.1.1.1 定義符號(hào) 你不能使用隨C#編譯器一起的預(yù)處理創(chuàng)建“define 標(biāo)志:符號(hào):定義 ”宏,但是,你仍可以定義符號(hào)。根據(jù)某些符號(hào) 是否被定義,可以排除或包括代碼。 第一種定義符號(hào)的辦法是在C#源文件中使用 #define標(biāo)志: #define DEBUG 這樣定義了符號(hào)DEBUG,且范圍在它所定義的文件內(nèi)。請(qǐng)注意,必須要先定義符號(hào)才能使用其它語句。例如,以下代碼 段是不正確的:
using System; #define DEBUG
編譯器將標(biāo)記上述代碼為錯(cuò)誤。你也可以使用編譯器定義符號(hào)(用于所有的文件): csc /define:DEBUG mysymbols.cs 如果你想用編譯器定義多種符號(hào),只需用分號(hào)隔開它們: csc /define:RELEASE;DEMOVERSION mysymbols.cs 在C#源文件中,對(duì)這兩種符號(hào)的定義分為兩行 #define 標(biāo)志。 有時(shí),你可能想要取消源文件中(例如,較大項(xiàng)目的源文件)的某種符號(hào)。可以用 #undef 標(biāo)志取消定義: #undef DEBUG #define的“定義標(biāo)志:符號(hào): 定義”規(guī)則同樣適用于#undef: 它的范圍在自己定義的文件之內(nèi),要放在任何語句如 using語句之前。 這就是全部有關(guān)用C#預(yù)處理定義符號(hào)和取消定義符號(hào)所要了解的知識(shí)。以下小節(jié)說明如何使用符號(hào)有條件地編譯代 碼。
9.1.1.2 依據(jù)符號(hào)包括和排除代碼 最重要的“if標(biāo)志:符號(hào):包括代碼”方式的目的為,依據(jù)符號(hào)是否被定義,有條件地包括和排除代碼。清單9.1 包含 了已出現(xiàn)過的源碼,但這次它依據(jù)符號(hào)被有條件地編譯。
清單 9.1 利用 #if 標(biāo)志有條件地包括代碼
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: }
注意,在這個(gè)源文件中沒有定義符號(hào)。當(dāng)編譯應(yīng)用程序時(shí),定義(或取消定義)符號(hào): csc /define:CALC_W_OUT_PARAM square.cs 根據(jù)“ if標(biāo)志:符號(hào):包括代碼”的符號(hào)定義,不同的 CalcSquare 被調(diào)用了。用來對(duì)符號(hào)求值的模擬預(yù)處理標(biāo)志為 #if、 #else和 #endif。它們產(chǎn)生的效果就象C#相應(yīng)的if 語句那樣。你也可以使用邏輯“與”(&&)、邏輯“或” (¦¦)以及“否”(!)。它們的例子顯示在清單9.2 中。
清單 9.2 使用#elif 在#if標(biāo)志中創(chuàng)建多個(gè)分支
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: }
在這個(gè)“if標(biāo)志:符號(hào):包含代碼”例子中,所有的符號(hào)都在C#源文件中被定義。注意第6行#undef語句增加的那部分。 由于不編譯DEBUG代碼的DEMO版本(任意選擇),我確信它不會(huì)被某些人無意中定義了,而且總當(dāng)DEBUG被定義時(shí),就取消 DEMO版本的定義。 接著在第15~21行,預(yù)處理符號(hào)被用來包括各種代碼。注意#elif標(biāo)志的用法,它允許你把多個(gè)分支加到#if 標(biāo)志。該 代碼運(yùn)用邏輯操作符“&&”和非操作符“!”。也可能用到邏輯操作符“¦¦”,以及等于和不等于操作 符。
9.1.1.3 引起錯(cuò)誤并警告 另一種可能的“警告 標(biāo)志錯(cuò)誤 標(biāo)志”預(yù)處理標(biāo)志的使用,是依據(jù)某些符號(hào)(或根本不依據(jù),如果你這樣決定)引 起錯(cuò)誤或警告。各自的標(biāo)志分別為 #warning和#error,而清單9.3 演示了如何在你的代碼中使用它們。 清單 9.3 使用預(yù)處理標(biāo)志創(chuàng)建編譯警告和錯(cuò)誤
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: }
在這個(gè)例子中,當(dāng)你生成一個(gè)不是DEBUG版本的DEMO版本時(shí),就發(fā)出了一個(gè)編譯警告(第5行~第7行)。當(dāng)你企圖生成 一個(gè)DEBUG DEMO版本時(shí),就引起了一個(gè)錯(cuò)誤,它阻止了可執(zhí)行文件的生成。對(duì)比起前面只是取消定義令人討厭的符號(hào)的例 子,這些代碼告訴你,“警告 標(biāo)志錯(cuò)誤 標(biāo)志”企圖要做的工作被認(rèn)為是錯(cuò)誤的。這肯定是更好的處理辦法。 9.1.1.4 條件屬性 C++的預(yù)處理也許最經(jīng)常被用來定義宏,宏可以解決一種程序生成時(shí)的函數(shù)調(diào)用,而卻不能解決另一種程序生成時(shí)的任 何問題。這些例子包括 ASSERT和TRACE 宏,當(dāng)定義了DEBUG符號(hào)時(shí),它們對(duì)函數(shù)調(diào)用求值,當(dāng)生成一個(gè)RELEASE版本時(shí),求 值沒有任何結(jié)果。
當(dāng)了解到宏不被支持時(shí),你也許會(huì)猜測(cè),條件功能已經(jīng)消亡了。幸虧我可以報(bào)道,不存在這種情況。你可以利用條件 屬性,依據(jù)某些已定義符號(hào)來包括方法。:
[conditional("DEBUG")] public void SomeMethod() { }
僅當(dāng)符號(hào)DEBUG被定義時(shí),這個(gè)方法被加到可執(zhí)行文件。并且調(diào)用它,就象 SomeMethod();
當(dāng)該方法不被包括時(shí),它也被編譯器聲明。功能基本上和使用C++條件宏相同。 在例子開始之前,我想指出,條件方法必須具有void的返回類型,不允許其它返回類型。然而,你可以傳遞你想使用 的任何參數(shù)。 在清單9.4 中的例子演示了如何使用條件屬性重新生成具有C++的TRACE宏一樣的功能。為簡單起見,結(jié)果直接輸出到 屏幕。你也可以根據(jù)需要把它定向到任何地方,包括一個(gè)文件。
清單 9.4 使用條件屬性實(shí)現(xiàn)方法
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類中,有兩個(gè)靜態(tài)方法,它們根據(jù)DEBUG符號(hào)被有條件地編譯:Trace,接收一個(gè)參數(shù),而TraceX則接收n個(gè)參 數(shù)。Trace的實(shí)現(xiàn)直接了當(dāng)。然而,TraceX實(shí)現(xiàn)了一個(gè)你從沒有見過的關(guān)鍵字:params。 params 關(guān)鍵字允許你指定一個(gè)方法參數(shù),它實(shí)際上接收了任意數(shù)目的參數(shù)。其類似C/C++的省略參數(shù)。注意,它必須 是方法調(diào)用的最后一個(gè)參數(shù),而且在參數(shù)列表中,你只能使用它一次。畢竟,它們的局限性極其明顯。 使用params 關(guān)鍵字的意圖就是要擁有一個(gè)Trace方法,該方法接收一個(gè)格式字符串以及無數(shù)個(gè)置換對(duì)象。幸好,還有 一個(gè)支持格式字符串和對(duì)象數(shù)組的 WriteLine方法(第16行)。 這個(gè)小程序產(chǎn)生的哪一個(gè)輸出完全取決于DEBUG是否被定義。當(dāng)DEBUG符號(hào)被定義時(shí),方法都被編譯和執(zhí)行。如果DEBUG 不被定義,對(duì)Trace和TraceX的調(diào)用也隨之消失。 條件方法是給應(yīng)用程序和組件增加條件功能的一個(gè)真正強(qiáng)大的手段。用一些技巧,你就可以根據(jù)由邏輯“或” (¦¦)以及邏輯“與”(&&)連接起來的多個(gè)符號(hào),生成條件方法。然而,對(duì)于這些方案,我想給你推薦C# 文檔。
9.2 在XML中的文檔注釋 很多程序員根本不喜歡的一項(xiàng)任務(wù)就是寫作,包括寫注釋和寫文檔。然而,有了C#,你就找到改變老習(xí)慣的好理由: 你可以用代碼的注釋自動(dòng)生成文檔。 由編譯器生成的輸出結(jié)果是完美的XML。它可以作為組件文檔的輸入被使用,以及作為顯示幫助并揭示組件內(nèi)部細(xì)節(jié)的 工具。例如, Visual Studio 7 就是這樣一種工具。 這一節(jié)專門為你說明如何最好地運(yùn)用C#的文檔功能。該例子涉及的范圍很廣,所以你不能有這樣的借口,說它過于復(fù) 雜,以至很難領(lǐng)會(huì)如何加入文檔注釋。文檔是軟件極其重要的一部分,特別是要被其他開發(fā)者使用的組件的文檔。 在以下小節(jié)中,文檔注解用來說明RequestWebPage 類。我已分別在以下幾小節(jié)中做出解釋: 。描述一個(gè)成員 。添加備注和列表 。提供例子 。描述參數(shù) 。描述屬性 。編譯文檔
9.2.1 描述一個(gè)成員 第一步,為一個(gè)成員添加一個(gè)簡單的描述。你可以用 <summary> 標(biāo)簽這樣做: /// <summary>This is .... </summary>
每一個(gè)文檔注釋起始于由三個(gè)反斜杠組成的符號(hào)“///”。你可以把文檔注釋放在想要描述的成員之前:
/// <summary>Class to tear a Webpage from a Webserver</summary>
public class RequestWebPage
使用<para>和 </para>標(biāo)簽,為描述添加段落。用<see>標(biāo)簽引用其它已有了注釋的成員。 /// <para>Included in the <see cref="RequestWebPage"/> class</para>
增加一個(gè)鏈接到RequestWebPage類的描述。注意,用于標(biāo)簽的語法是XML語法,這意味著標(biāo)簽大寫化的問題,而且標(biāo)簽 必須正確地嵌套。 當(dāng)為一個(gè)成員添加文檔時(shí),另一個(gè)有趣的標(biāo)簽是<seealso> 。它允許你描述可能使讀者非常感興趣的其它話題。
/// <seealso cref="System.Net"/>
前面的例子告訴讀者,他可能也想查閱System.Net 名字空間的文檔。你一定要給超出當(dāng)前范圍的項(xiàng)目規(guī)定一個(gè)完全資 格名。 作為許諾,清單9.5 包含 RequestWebPage類中正在工作的文檔的所有例子。看一下如何使用標(biāo)簽以及嵌套如何為組件 產(chǎn)生文檔。
清單 9.5 利用 <summary>, <see>, <para>, and <seealso> 標(biāo)簽描述一個(gè)成員
1: using System; 2: using System.Net; 3: using System.IO; 4: using System.Text; 5: 6: /// <summary>Class to tear a Webpage from a Webserver</summary> 7: public class RequestWebPage 8: { 9: private const int BUFFER_SIZE = 128; 10: 11: /// <summary>m_strURL stores the URL of the Webpage</summary> 12: private string m_strURL; 13: 14: /// <summary>RequestWebPage() is the constructor for the class 15: /// <see cref="RequestWebPage"/> when called without arguments.</summary> 16: public RequestWebPage() 17: { 18: } 19: 20: /// <summary>RequestWebPage(string strURL) is the constructor for the class 21: /// <see cref="RequestWebPage"/> when called with an URL as parameter.</summary> 22: public RequestWebPage(string strURL) 23: { 24: m_strURL = strURL; 25: } 26: 27: public string URL 28: { 29: get { return m_strURL; } 30: set { m_strURL = value; } 31: } 32: 33: /// <summary>The GetContent(out string strContent) method: 34: /// <para>Included in the <see cref="RequestWebPage"/> class</para> 35: /// <para>Uses variable <see cref="m_strURL"/></para> 36: /// <para>Used to retrieve the content of a Webpage. The URL 37: /// of the Webpage (including http://) must already be 38: /// stored in the private variable m_strURL. 39: /// To do so, call the constructor of the RequestWebPage 40: /// class, or set its property <see cref="URL"/> to the URL string.</para> 41: /// </summary> 42: /// <seealso cref="System.Net"/> 43: /// <seealso cref="System.Net.WebResponse"/> 44: /// <seealso cref="System.Net.WebRequest"/> 45: /// <seealso cref="System.Net.WebRequestFactory"/> 46: /// <seealso cref="System.IO.Stream"/> 47: /// <seealso cref="System.Text.StringBuilder"/> 48: /// <seealso cref="System.ArgumentException"/> 49: 50: public bool GetContent(out string strContent) 51: { 52: strContent = ""; 53: // ... 54: return true; 55: } 56: }
9.2.2 添加備注和列表 <remarks> 標(biāo)簽是規(guī)定大量文檔的地方。與之相比, <summary>只僅僅規(guī)定了成員的簡短描述。 你不限于只提供段落文本(使用<para>標(biāo)簽)。例如,你可以在備注部分包含bulleted(和有限偶數(shù))列表 (list):
/// <list type="bullet"> /// <item>Constructor /// <see cref="RequestWebPage()"/> or /// <see cref="RequestWebPage(string)"/> /// </item> /// </list>
這個(gè)list有一項(xiàng)(item),且該item引用了兩個(gè)不同的構(gòu)造函數(shù)描述。你可以根據(jù)需要,任意往list item中添加內(nèi) 容。 另一個(gè)在備注部分很好用的標(biāo)簽是<paramref>。例如,你可以用<paramref>來引用和描述傳遞給構(gòu)造函數(shù)的參數(shù):
/// <remarks>Stores the URL from the parameter /// <paramref name="strURL"/> in /// the private variable <see cref="m_strURL"/>.</remarks> public RequestWebPage(string strURL)
在清單9.6中,你可以看到所有的這些以及前面的標(biāo)簽正在起作用。
清單9.6 為文檔添加一個(gè)備注和bullet list
1: using System; 2: using System.Net; 3: using System.IO; 4: using System.Text; 5: 6: /// <summary>Class to tear a Webpage from a Webserver</summary> 7: /// <remarks>The class RequestWebPage provides: 8: /// <para>Methods: 9: /// <list type="bullet"> 10: /// <item>Constructor 11: /// <see cref="RequestWebPage()"/> or 12: /// <see cref="RequestWebPage(string)"/> 13: /// </item> 14: /// </list> 15: /// </para> 16: /// <para>Properties: 17: /// <list type="bullet"> 18: /// <item> 19: /// <see cref="URL"/> 20: /// </item> 21: /// </list> 22: /// </para> 23: /// </remarks> 24: public class RequestWebPage 25: { 26: private const int BUFFER_SIZE = 128; 27: 28: /// <summary>m_strURL stores the URL of the Webpage</summary> 29: private string m_strURL; 30: 31: /// <summary>RequestWebPage() is the constructor for the class 32: /// <see cref="RequestWebPage"/> when called without arguments.</summary> 33: public RequestWebPage() 34: { 35: } 36: 37: /// <summary>RequestWebPage(string strURL) is the constructor for the class 38: /// <see cref="RequestWebPage"/> when called with an URL as parameter.</summary> 39: /// <remarks>Stores the URL from the parameter <paramref name="strURL"/> in 40: /// the private variable <see cref="m_strURL"/>.</remarks> 41: public RequestWebPage(string strURL) 42: { 43: m_strURL = strURL; 44: } 45: 46: /// <remarks>Sets the value of <see cref="m_strURL"/>. 47: /// Returns the value of <see cref="m_strURL"/>.</remarks> 48: public string URL 49: { 50: get { return m_strURL; } 51: set { m_strURL = value; } 52: } 53: 54: /// <summary>The GetContent(out string strContent) method: 55: /// <para>Included in the <see cref="RequestWebPage"/> class</para> 56: /// <para>Uses variable <see cref="m_strURL"/></para> 57: /// <para>Used to retrieve the content of a Webpage. The URL 58: /// of the Webpage (including http://) must already be 59: /// stored in the private variable m_strURL. 60: /// To do so, call the constructor of the RequestWebPage 61: /// class, or set its property <see cref="URL"/> to the URL string.</para> 62: /// </summary> 63: /// <remarks>Retrieves the content of the Webpage specified in 64: /// the property<see cref="URL"/> and hands it over to the out 65: /// parameter <paramref name="strContent"/>. 66: /// The method is implemented using: 67: /// <list> 68: /// <item>The <see cref="System.Net.WebRequestFactory.Create"/>method.</item> 69: /// <item>The <see cref="System.Net.WebRequest.GetResponse"/> method.</item> 70: /// <item>The <see cref="System.Net.WebResponse.GetResponseStream"/>method</item> 71: /// <item>The <see cref="System.IO.Stream.Read"/> method</item> 72: /// <item>The <see cref="System.Text.StringBuilder.Append"/> method</item> 73: /// <item>The <see cref="System.Text.Encoding.ASCII"/> property together with its 74: /// <see cref="System.Text.Encoding.ASCII.GetString"/> method</item> 75: /// <item>The <see cref="System.Object.ToString"/> method for the 76: /// <see cref="System.IO.Stream"/> object.</item> 77: /// </list> 78: /// </remarks> 79: /// <seealso cref="System.Net"/> 80: public bool GetContent(out string strContent) 81: { 82: strContent = ""; 83: // ... 84: return true; 85: } 86: }
9.2.3 提供例子 要想說明一個(gè)對(duì)象和方法的用法,最好的辦法是提供優(yōu)秀源代碼的例子。因此,不要詫異文檔注釋也有用于聲明例子 的標(biāo)簽: <example> and <code>。 <example>標(biāo)簽包含了包括描述和代碼的整個(gè)例子,而 <code> 標(biāo)簽僅包含了例子的代 碼(令人驚訝)。 清單9.7 說明如何實(shí)現(xiàn)代碼例子。包括的例子用于兩個(gè)構(gòu)造函數(shù)。你必須給GetContent方法提供例子。
清單.7 利用例子解釋概念
1: using System; 2: using System.Net; 3: using System.IO; 4: using System.Text; 5: 6: /// <summary>Class to tear a Webpage from a Webserver</summary> 7: /// <remarks> ... </remarks> 8: public class RequestWebPage 9: { 10: private const int BUFFER_SIZE = 12
|
溫馨提示:喜歡本站的話,請(qǐng)收藏一下本站!