類型的實例,並指明是否區分大小寫
///
/// <
param name="caseSensitive">是否區分大小寫
protected SearchCondition(bool caseSensitive)
{
this.caseSensitive = caseSensitive;
}
// 這裡用空行隔開
protected
bool caseSensitive = false;
///
/// 獲取或設置一個 Boolean"/> 類型的值,以指示是否區分大小寫
///
public bool CaseSensitive
{
get { return caseSensitive; }
set { caseSensitive = value; }
}
// 這裡用空行隔開
///
/// 獲取表示此搜索條件的
SQL 篩選條件表達式
///
///
/// 返回一個字元串形式的條件表達式,可直接用於 SQL 語言中的 WHERE 子句
///
}
}
這樣排版無疑會使得每個成員的
代碼段更富獨立性,絕大多數的編譯器,在自動生成代碼時都會遵照此方式排版。您可能會發現,上例中的caseSensitive欄位與CaseSensitive屬性之間並未留有空行,這是為了強調欄位與其對應用於公開訪問的屬性之間的聯繫,關於類似情況,我們將在後面的章節詳細討論。
然而,一個空行意味著的不僅僅是功能模塊的界限,它更是對代碼邏輯塊的劃分。我們無法期望每個操作都只通過一行代碼一條語句來完成,大多數情況下,它們都需要許多行代碼來執行一個完整的操作。例如,你想查詢資料庫,那麼你需要先生成SQL代碼,建立命令,然後執行這個命令並填充至數據集。這中間大約需要使用四五行代碼,而這四五行代碼便組成了一個相對緊密的邏輯塊,它與其後面的其他邏輯塊即可以通過一個空行來進行分隔。請看下面的一個例子:
代碼示例1-10:用空行分隔邏輯塊
public static string[] GetPhotoIds(string filterExpression, string sort, bool caseSensitive)
{
// 第一個邏輯段代碼根據處理后的參數取得數據行
xml.Photos.CaseSensitive = caseSensitive;
DataRow[] rows =
xml.Photos.Select(filterExpression, sort ?? string.
Empty);
// 遍曆數據行,取出需要的元素
string[] ids = new string[rows.Length];
for (int i = 0; i < rows.Length; i++)
{
ids = (string)rows["Id"];
}
// 返回結果
return ids;
}
這個函數的目的是根據指定的篩選條件和排序規則返回照片的標識號(
Photo IDs),函數內部自然形成了三個邏輯段:先是根據要求取得原始數據,然後從原始數據中提取我們需要的部分,最後將結果返回。用空行將這三個邏輯區分隔開來將會更加有利於我們理解其思路。關於註釋的合理使用,我們會在後面的章節中再專門介紹。
既然空行可以起到分隔代碼,提高清晰度的作用,那麼有的朋友也許會為了強調這種分隔效果,多加幾個空行。可事實的效果是,連續的多個空行,在並未提高多少清晰度的同時,浪費了屏幕的空間,而且會讓人覺得前後兩個
代碼段並不相關——事實上它們應該是相繼執行的。空行的意義和文章的段落一樣,僅在於表示一個停頓,而並非結束。
近自然語序的習慣(如And、Not、Inherits、
Implements、Handles等等關鍵字);而
Delphi更是延續著Pascal語言那標誌性的BEGIN-END作風。C語言由於在
操作系統開發上取得了成功,使得它在
軟體開發歷史上佔據了絕對的優勢,相比而言,它的語法更加具有影響力,廣泛被C++、
Java、C#,乃至用於編寫網頁
言是使用符號最多的語言。當其他語法體系都採用AND、OR等關鍵字作為運算符時,C語言卻使用了“&&”、“||”這樣的符號,雖然在語法上並沒有增加任
何複雜性,但各種奇形怪狀難以記憶的符號還是會令初學者望而卻步。讓我們來比較一下下面的幾行代碼:
BASIC: If a>b And c<>d Or Not e>f Then ...
PASCAL: If (a>b) And (c<>d) Or (Not (e>f)) Then ...
C: if(a>b&&c!=d||!(e>f)) ...
這三行的意義是完全相同的,但明顯可以讓人感覺到清晰程度的差異,Basic和Pascal的代碼看上去很容易明白,而C語言的代碼卻像
螞蟻一般縮成一團。
重要的原因在於:C語言的運算符幾乎都只由“符號”構成,與變數名之間不需要用空格充當分隔符。這樣一來,由於缺少空格的稀釋,C語言的代碼就像被濃縮過似
的——現如今它除了影響我們閱讀以外,沒有什麼好處。因此我們有必要人為地添加一點空格,幫它降低代碼的“密度”。這裡,我總結了一些關於如何在運算
1. 單目運算符(Unary Operators)與它的操作數之間應緊密相接,不需要空格。例如:
代碼示例1-11:單目運算符的空格規則示例
y = ++x; // ++ 在這裡是前綴單目運算,它與x之間無空格
2. 在雙目、
三目運算符(Binary/Ternary Operators)的左右兩側分別添加空格。例如:
代碼示例1-12:雙目、三目運算符的空格規則示例
int a = 3 + 5; // 在雙目運算符左右添加空格
int b = a * 6 + 7;
int c = a & b;
int d = b++ * c--; // 雖然有單目運算符,但
雙目運算符兩側仍應添加空格
int e = a > 0 ? 1 : 0; // 在三目運算符左右添加空格
3. 括弧(包括小括弧、中括弧與大括弧)的內側應該緊靠操作數或其他運算符,不需要添加額外的空格。例如:
代碼示例1-13:括弧的空格規則示例
int f = (a + b) * c; // 括弧內側緊靠操作數,因其他運算符添加的空格留在外側
int g[MAX] = {1, 2, 3}; // 中括弧與表達式中的大括弧也同樣處理
4. 不要使用連續的兩個或多個空格。
其實,如果理解了這些規則,在實際書寫的時候很容易遵循。對於任何一個表達式,我們先把單目運算符和括弧去掉,然後在雙目、三目運算符的左右兩側分別
添加一個空格,再將單目運算符和括弧填回去,放在靠近自己操作數的一邊即可。
關於函數調用時,要不要在函數名和其後的括弧之間添加空格的問題已經討論了很久。其實這個是一個無傷大雅的事情,無論使用何種方式,都不會對代碼
的可讀性產生多少實質性的影響,純粹是各人喜好罷了。不過在這裡,我建議採用Visual Studio中的默認規則:在函數調用時不添加空格,而在一些類似的帶括弧的語法結構中添加空格。請看下面這段代碼:
代碼示例1-14:函數調用時的空格規則示例
string cmd = string.Empty;
// 函數形式的調用,括弧前沒有空格
cmd = Console.ReadLine();
// 語句結構,括弧前有空格
if (cmd.Length > 0)
{
Console.WriteLine(cmd.ToUpper());
}
else
{
Console.WriteLine("(Empty)");
}
在有關代碼風格的問題中,最為顯眼的可以說就是代碼的縮進(
Indent)了。所謂縮進,是通過在每一行的代碼左端空出一部分長度,更加清晰地從外觀上體現出程序的層次結構。為了更好地描述這一點,先請讀者欣賞下列這段代碼:
int kmp_match(char[] t, char[] p, int[]
flink, int n, int m)
{
int i = 0, j = 0;
while (i < n)
{
while (j != -1 && p[j] != t)
{
j = flink[j];
}
if (j == m - 1)
{
return i - m + 1;
}
i++;
j++;
}
return -1;
}
我想,就算讓你檢查一下它裡面有沒有
大括弧配對錯誤恐怕都很困難,更不用說這段代碼有什麼功能 了——你能一眼看清楚每個while循環的內容是什麼嗎?讓我們換個方式,看看另一段程序:
代碼示例1-15:正確縮進的例子
兩段程序,除了縮進的區別以外,一字不差。孰是孰非,相信大家都能看得出來,縮進的必要性不難理解。接下來的問題就是:應該如何縮進。
當遇到有關命名空間、類、結構、函數、以及枚舉等等複雜程序結構的定義的時候,我們通常需要將它的內容縮進一層。在C# 語言中,大括弧是一個非常明顯的標誌,凡是遇到大括弧,都應該直接聯想到縮進。請看下面的示例:
代碼示例1-16:包含關係下的縮進
namespace MyNamespace
{
// 命名空間內的內容應縮進
{
// 類的成員應縮進
public string MyMethod()
{
// 方法函數的函數體應縮進
return "Hello!";
}
private MyEnum myProperty = MyEnum.Alpha;
public MyEnum MyProperty
{
// 屬性的內容應縮進
get
{
// 屬性的get部分函數體應縮進
return myProperty;
}
set
{
// 屬性的set部分函數體應縮進
myProperty = value;
}
}
}
public enum MyEnum
{
// 枚舉類型內容應縮進
Alpha,
Beta,
}
}
分支結構(包括if…else結構、switch結構等)和循環結構(包括for結構、while/do…while結構等)都是存在嵌套關係的,因此從條理清晰的角度來說,它同樣應該進行縮進書寫:
代碼示例1-17:嵌套關係下的縮進
// if...else結構
if (a > b)
{
// if 子句的結構體內容應縮進
max = a;
min = b;
}
else
{
// else 子句的結構體內容應縮進
max = b;
min = a;
}
// switch結構
switch (n)
{
// switch結構的內容應縮進
case 0:
// case 子句內容也應縮進
// ...
break;
case 1:
// ...
break;
default:
// ...
break;
}
// for結構
for (int i = 0; i < 100; i++)
{
// for 的循環體應縮進
s += data;
t *= data;
}
// while結構
i = 0;
while (data != 0)
{
// while 的循環體應縮進
s += data;
t *= data;
i++;
}
縮進時,應將內部結構中的所有語句都統一向右退一格,大括弧則與原來的語句保持在同一個垂直位置上。每層縮進的長度應該一致,通常為一個
製表符寬或四個空格。
還有一些細節的地方也與換行相關,例如if、switch、for這類具有嵌套結構的語句,在書寫的時候都應避免將結構體與語句本身寫在同一行上,關於嵌套結構的書寫方法,我們會在後面的章節中詳細討論。
我們在前面提到過,當一條語句太長而超出一定的寬度時,應該折
行書寫。此時,從第二行起到該語句結束之間的各行應該縮進一層,至下一條語句時再恢復原來的縮進位置。
代碼示例1-18:因換行而產生的縮進
int myVar = 0;
// 這是一條很長的語句,因而出現了換行,從第二行起都縮進了一格:
myVar = myVar1 + myVar2 + myVar3 - myVar4 - myVar5 * myVar6 * myVar7 /
myVar8 / myVar9 + myVar10 + myVar11 - myVar12 - myVar13 * myVar14 *
myVar15 / myVar16;
// 後面的語句恢復正常的縮進位置
Console.Write(myVar);
如果該語句是進行函數調用,由於參數太多而造成的換行,那麼在縮進規則上有一些微小的差別:
代碼示例1-19:函數調用時分行書寫參數而引起的縮進
Rectangle imageBounds = new Rectangle(
itemBounds.X + padding,
itemBounds.Y + padding,
itemBounds.Width - padding * 2,
itemBounds.Width - padding * 2
);
注意最後一行的括弧與
分號並沒有縮進,因為這種結構其實是對類似if的大括弧嵌套結構的模擬。
如何縮進一向是一個有爭議的問題。使用Tab及Shift + Tab鍵縮進在操作上非常方便,而使用空格可以保證代碼在任何編輯器下都能正確顯示縮進格式。現在,我們依靠Visual Studio
開發環境則可以輕鬆解決這個矛盾:在“選項”對話框中對C# 編輯器的代碼縮進方式進行設置(如圖1-2),選擇“插入空格”模式。
圖1-2:Visual Studio中關於代碼縮進的設置
這樣一來,我們就可以在書寫代碼時使用Tab鍵和Shift + Tab鍵來調整縮進,而Visual Studio會將其轉換為空格保存至代碼文件中。
從外觀上來看,類C語言的最大標誌就是它無處不在的大括弧了。在C# 中,大括弧仍然扮演著幾種不同的角色:表示層次關係(如定義命名空間、類時使用的大括弧)、表示複合語句(如if、for中的大括弧)、表示數組元素。本節將討論有關大括弧書寫的幾個基本問題。
代碼風格中,如何擺放大括弧一直是人們熱衷的話題。其實,我更願意用開放的態度去看待它,具體使用何種方式並不重要,重要的是,要保持方式風格的統一,不能在同一個項目中出現不同的風格。
代碼示例1-20:K&R大括弧位置風格
public int Max(int x, int y)
{
if (x > y) {
return x;
} else {
return y;
}
}
據說,微軟公司內部使用的就是這種K&R風格,然而它在對外公開的文檔中,卻使用更為大家所熟知的一種風格,它將大括弧單獨寫成一行。本書所有示例代碼採用的都是這樣一種格式:
代碼示例1-21:C# 默認使用的大括弧位置風格
public int Max(int x, int y)
{
if (x > y)
{
return x;
}
else
{
return y;
}
}
雖然生硬地規定應該使用哪一種是不提倡的,而且也沒有必要,但是我們仍然建議讀者盡量選擇上述兩種風格中的一種,並在自己的程序中保持風格的統一。
所謂複合體,即指用於充當某個語法結構成份的,被大括弧括起來的多條語句。C# 的很多語法結構中都可以見到複合體的使用,如命名空間、類、結構、介面、枚舉、方法、屬性等的定義,以及if、switch、for、while、do…while、try…catch…finally結構等等。對於應該如何處理大括弧的擺放以及內容的縮進排版等問題,我們都已經詳細討論過了,現在要考慮的是複合體自身的一種特殊情況:空的複合體。
接下來的問題是:為什麼會出現空的複合體?有的時候來自於廢棄的空函數。開發過程中,很可能只寫了某個類或者函數的空聲明,打算待日後再細化。然而由於設計上的變動,這個函數不再使用,而躲在代碼某個角落的這個空函數又未能被及時刪除,結果一直保留到產品發布。在這種情況下,建議在空複合體中添加“TODO”的註釋:
代碼示例1-22:為未實現的空函數體中添加TODO註釋
public void UnusedMethod()
{
// TODO: 未實現的方法
}
因開發時的疏忽造成的空複合體還不僅僅是上面這一種情況,我曾見到過有人將if結構中的if段留空,卻在else里寫上一堆代碼,類似下面這樣:
if (table.Rows.Count == 0)
{
}
else
{
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["Name"]);
}
}
也許他本來在if中是有代碼的,後來程序不斷地修改,結果在if段中無事可做了。這種情況也很容易避免,只需很簡單地將if中的條件表達式反轉過來即可:
代碼示例1-23:反轉if條件表達式以避免空的if子句
if (table.Rows.Count > 0)
{
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["Name"]);
}
}
如果不是開發時的疏忽,那麼空複合體的出現就是有意而為之了。在
循環結構中,它出現的頻率相對較高。早期,人們會採用空循環的方法來達到“延時”的效果,那個時候常常會看到類似這樣的代碼:
for (i = 0; i < 10000; i++)
{
}
printf("Done.");
或者乾脆連大括弧結構都沒有,直接就是
printf("Waiting...\n");
for (i = 0; i < 10000; i++);
printf("Done.");
且不說現如今這種“延時”方式完全不可取,就從代碼的外觀來看,它也極易讓人將for下面的那一行printf輸出代碼當成是循環的內容。
由於C# 語言繼承了C語言語法靈活的特點,因此
循環語句本身就能承擔很多複雜的工作,以至於根本就不需要循環體。下面的代碼反映了一種典型的狀況:
int sum = 0;
for (int i = 0; i < table.Rows.Count; sum += (int)table[i++]["Amout"])
{
}
這個for語句本身就把累加工作給做完了,使得循環體內已經無事可做。雖說從語法角度來說沒有多少問題,但是它的可讀性並不好,我們仍然希望for僅僅是做一些“循環”本身的事情,將具體的數據和邏輯操作放在循環體內,就像下面這樣:
代碼示例1-24:讓for循環語句本身僅控制循環,不要涉及具體事務
int sum = 0;
for (int i = 0; i < table.Rows.Count; i++)
{
sum += (int)table["Amout"];
}
這樣總結下來,似乎空複合體完全不應該出現在代碼中,然而有一種情況下它的確可以而且應當存在,這就是
構造函數。構造函數與其他的函數不同,即使沒有任何一行代碼內容,它本身的存在與否也有著極其重要的意義——直接決定這個類型是否能夠被實例化。因此,當我們不希望某個類被外部實例化的時候,哪怕我們並不需要自定義構造函數,我們仍需要為該類型添加一個非公開的空構造函數。為了避免日後將這個空函數當成是沒有用的廢棄函數,我們有必要特別加以說明:
代碼示例1-25:通過註釋強調空構造函數
public class PhotoCollection : IEnumerable, IEnumerable
{
internal PhotoCollection()
{
// 空構造函數
}
// 其他類成員已省略
}
其他情況下,如果確實有必要使用空的複合體,也應當仿照上例書寫,添加相應的註釋說明,以防止產生誤解。絕不能省略大括弧結構,僅以一個分號代替。
我們再來討論另一種特殊的複合體。按照C# 的語法,如果if、while、for等等的結構體僅為一條語句,那麼大括弧是可以不寫的——其實複合體就是將多條語句合併起來充當單條語句來用的。甚至我們可以將結構體與if、while、for寫成一行,例如:
if (a > b) x++;
else y++;
或者是:
for (int i = 0; i < 10; i++)
dest = source;
雖然這在語法上沒有什麼問題,但當代碼數量增加,上下文的代碼變得複雜時,我們有可能會對if、while、for的結構體究竟包含哪些語句產生誤解。因此,我們建議,即使if、while、for結構的內容只有一條語句,也應該像處理多條語句那樣,寫在大括弧內。因此,剛才那兩段代碼就應該寫成下面