在談談VS中的模板中, 我介紹了如何建立項目/項目範本,這種方式可以在建立項目時省卻不少重複性的工作,從而提高開發效率。在建立好了項目和檔案後,就得開始具體的編碼了,這時 又有了新的重複性工作,就是需要經常編寫一些類似或者說雷同的代碼,我們需要一種方法將這些代碼管理起來,減少重複輸入。
一個常見的例子,在使用for語句結構時,可能會有這樣的代碼:Code
int[] array = { 1, 2, 3, 4, 5 };
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
}
或者
Code
List<string> names = new List<string> { "Anders", "Bill", "Clark", "David"};
for (int i = 0; i < names.Count; i++)
{
if (names[i].StartsWith("A"))
{
Console.WriteLine(names[i]);
}
}
顯然,這兩個for迴圈的代碼很相似:輸入for,選擇一個變數用作索引,該變數有個上限值,還有幾個括弧和分號。而且絕大多數的for迴圈都是 如此,那麼該如何減少重複輸入呢? 可以想到的一種方法是把一段for迴圈的代碼儲存在某個地方,比如一個檔案內,在需要for的地方,拷貝進來,把變數名、初始值、上限修改一下就可以用 了。
VS的開發人員想的很周全,提供了Code Snippet功能,從而實現了上面的想法。它儲存了for迴圈代碼的模板,然後給它一個快速鍵for。現在在編輯器中(需要是C#檔案),輸入for,連續按兩下Tab鍵,就會出現下面的代碼:
不 僅有了for的基本代碼,還定位到了變數的名字處,如果需要可以修改變數名,假設改為index,後面的兩個i會自動改為index,然後按Tab,游標 會跳至下一個深色顯示的地方,即length,這裡可以修改index的上限,然後斷行符號,游標會跳至for迴圈的代碼體:
是不是很方便呢?還有很多其它Snippet,比如輸入cw,按兩下Tab就出來Console.WriteLine()。
很多時候,同樣的功能在不同語言內的表現是不同的,所以Code Snippet(以下簡稱Snippet)是特定於語言的,也就是說C#的Snippet不能用於VB.NET。VS2008中的Snippet支援C#、VB.NET、XML。
Snippet的管理
首 先VS2008提供了很多內建的Snippet,另外我們也可以將自己編寫的或者他人編寫的匯入VS中。通過菜單Tools -> Code Snippets Manager(或按Ctrl+K, Ctrl+B),開啟Code Snippets Manager視窗:
可以看到上面的Language列表,現在選中的是C#。可以通過Import方式來匯入新的Snippet。在使用NUnit時,由於測試代碼的特點,會有很多重複輸入,所以Scott Bellware提供了NUnit的Snippet,我把它放在自己的部落格來了:BellwareNUnitSnippet。現在把包裡的.snippet檔案匯入。
嗯,可以使用了。比如,輸入tc,按兩下Tab,出來的代碼是這樣的:
輸入TestCase的名稱,斷行符號,這樣就可以輸入測試代碼了。觀察一下這個Snippet,它的變化之處只有一個,就是TestCase處。
接下來我們來分析一下Snippet檔案的結構,這樣才能編寫自己的Snippet。
Snippet定義檔案解析
下 面來看看Snippet是如何?的。根據上面tc的例子,我們可以猜想要存放Snippet,至少需要模板代碼、預留位置、語言類型、快速鍵這幾個關鍵信 息,每個Snippet都是如此。事實上,VS把這些資訊儲存在XML檔案中,這些資訊都對應著某些節點,這個與上一篇裡的模板資訊清單檔類似。
存放Snippet的檔案是XML檔案,不過它的副檔名是.snippet。一個Snippet檔案可以包含多個Snippet,就像上面的BellwareNUnit.snippet那樣。它的基本結構如下:
XML Code
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Code Snippet for Debug.WriteLine method.</Title>
<Shortcut>dw</Shortcut>
<Author>Anders Cui</Author>
</Header>
<Snippet>
<Code Language="CSharp">
<![CDATA[
Debug.WriteLine(“Text”);
]]>
</Code>
</Snippet>
</CodeSnippet>
<!-- other snippets -->
</CodeSnippets>
現在建立一個XML檔案,輸入上面的代碼,這裡我們的Snippet是輸入Debug.WriteLine代碼。該檔案的根節點為 CodeSnippets,可以包含多個<CodeSnippet>節點。注意它的命名空間,有了這個,在VS內編輯時就方便多了。
重點關注<CodeSnippet>節點,它即表示一條Snippet。它必須包含一個Format Attribute(老是看到Attribute和Property的討論,故在此保留),用以表示Snippet版本。另外它必須包含兩個子節點:<Header>和<Snippet>。
對 於<Header>節點,最重要的是Title和Shortcut,即Snippet的名稱和快速鍵;另外還有SnippetTypes,它 可以包含若干個SnippetType節點,可有三種取值,Expansion、SurroundsWith、Refactoring。 Expansion允許代碼插入在游標處;SurroundsWith允許代碼圍繞在選中代碼兩邊(就像#region那樣);Refactoring指 定了在C#重構過程中所使用的Snippet,在自訂Snippet中不能使用。如果該值不做設定,則Snippet可以放在任何地方。
要瞭解<Header>的更多資訊,請參看這裡。
對於<Snippet>節點,它是實現代碼模板的地方。它包含四個子節點。
1、 <Code>節點
- Delimiter:分隔字元,預設值為$,後面你會看到它的用法。
- Kind:Snippet的類型,比如方法體、方法聲明、型別宣告等。
- Language:所適用的語言類型,如C#、VB.NET、XML。
在我們上面的例子中,已經有了Code節點了,注意這裡把程式碼封裝含在<![CDATA[]]>中,因為代碼很可能會包含一些特殊字元。
在上面的tc Snippet中,按下Tab後,VS會選中TestCase,這樣修改起來更為方便,對於上面的dw Snippet,我們自然希望VS選中”Text”部分,這需要下面的<Declarations>節點。
2、<Declarations>節點
該節點包含若干個<Literal>和<Object>節點。它們可以看作是預留位置。<Literal>用於指定一些文本值,<Object>則用於聲明模板中的對象。
詳細資料請參看<Literal>和<Object>。
這裡需要把”Text”看作預留位置,所以添加一個<Literal>節點:
XML Code
<Snippet>
<Code Language="CSharp">
<![CDATA[
Debug.WriteLine($text$);$end$
]]>
</Code>
<Declarations>
<Literal>
<ID>text</ID>
<ToolTip>Text to write</ToolTip>
<Default>"Text"</Default>
</Literal>
</Declarations>
</Snippet>
這裡添加了一個預留位置$text$,預設值為”Text”,行末的$end$是一個特殊的預留位置,它表示當你按下斷行符號後游標的位置。
3. <Imports>節點
用於指定使用Snippet時應當向檔案內添加的命名空間引用,不過只支援VB.NET。
4. <References>節點
用於指定使用Snippet時應當向添加的程式集引用,同樣只支援VB.NET:(
好了,現在可以測試一下我們的Snippet了,將檔案儲存為.snippet檔案,然後匯入。
還不錯吧?
Code Snippet 函數
前面說到,<Imports>和<References>節點只能用於VB.NET,而這裡的Code Snippet函數則只能用於C#。
在<Literal>和<Object>節點中,都包含了子節點<Function>,這些函數是VS的一部分,有時會比較有用。共有三個函數:
1. GenerateSwitchCases(EnumerationLiteral),根據提供的枚舉類型產生一個switch語句和一系列case語句,事實上,C#中已有這樣的一個例子:
斷行符號確認:
2. ClassName(),返回Snippet所在類的名稱。
3. SimpleTypeName(TypeName),在Snippet所在的上下文中推斷出TypeName參數的最簡單形式。
下面以SimpleTypeName為例來看一下這些函數的用法:
XML Code
<Snippet>
<Code Language="CSharp">
<![CDATA[
$NameOfDebug$.WriteLine($text$);$end$
]]>
</Code>
<Declarations>
<Literal>
<ID>text</ID>
<ToolTip>Text to write</ToolTip>
<Default>"Text"</Default>
</Literal>
<Literal Editable="false">
<ID>NameOfDebug</ID>
<Function>SimpleTypeName(global::System.Diagnostics.Debug)</Function>
</Literal>
</Declarations>
</Snippet>
這裡比前面的Snippet添加了一個Literal,為什麼需要這麼做呢?我們知道System.Diagnostics命名空間預設情 況下是沒有引用的,如果使用Debug類,還需要引用System.Diagnostics。這裡的妙處在於VS會推斷NameOfDebug的最簡單形 式,如果沒有引用System.Diagnostics,它會在Debug前面加上,否則就不會加上。
幾條建議
首先,Snippet的定義都在XML中,因此也算得上是代碼,所以在命名上與其它代碼無異,都要選擇更有意義或者相關性的名字。命名快速鍵的一個做法是使用首字母的縮寫,比如Assert.AreEqual(expected, actual);的快速鍵為ae。
另外,記得填寫ToolTip節點的內容,這些內容在使用Snippet時會看到。
其它工具
雖然Snippet可以簡化代碼輸入,可是它本身的編寫卻並非很方便,使用一些視覺化檢視會更好,比如Snippet Editor,有興趣可以試一下。
另外,這個世界還有很多人在編寫Snippet,比如gotcodesnippets.com,所以在動手編寫之前可以先搜尋一下:)
小結
本文介紹了Code Snippet的使用和編寫,它可以看作是程式碼片段的模板,在粒度上比項目/項目範本更小,從而進一步提高了工作效率。
參考
《Professional Visual Studio 2008 Extensibility》