- C#入門經典(第7版):C# 6.0 & Visual Studio 2015(.NET開發經典名著)
- (美)Beijamin Perkins Jacob Vibe Hammer Jon D. Reid
- 3920字
- 2021-04-02 21:18:41
7.2 錯誤處理
本章的第一部分討論如何在應用程序的開發過程中查找和改正錯誤,使這些錯誤不會在發布的代碼中出現。但有時,我們知道可能會有錯誤發生,但不能100%地肯定它們不會發生。此時,最好能預料到錯誤的發生,編寫足夠健壯的代碼以處理這些錯誤,而不必中斷程序的執行。
錯誤處理就是用于這個目的。本節將介紹異常和處理它們的方式。異常是在運行期間代碼中產生的錯誤,或者由代碼調用的函數產生的錯誤。這里的“錯誤”定義要比以前更含糊,因為異??赡苁窃诤瘮档冉Y構中手工產生的。例如,如果函數的一個字符串參數不是以a開頭,就產生一個異常。嚴格來講,從該函數的外部看這并不是一個錯誤,但調用該函數的代碼會把它看成錯誤。
在本書前面已經遇到幾次異常了。最簡單的示例是試圖定位一個超出范圍的數組元素,例如:
int[] myArray = { 1, 2, 3, 4 }; int myElem = myArray[4];
這會產生如下異常信息,并中斷應用程序的執行:
Index was outside the bounds of the array.
異常在名稱空間中定義,大多數異常的名稱清晰地說明了它們的用途。在這個示例中,產生的異常稱為System.IndexOutOfRangeException,說明我們提供的myArray數組索引不在允許使用的索引范圍內。只有在異常未處理時,這個信息才會顯示出來,應用程序也才會中斷執行。下一節將討論如何處理異常。
7.2.1 try...catch...finally
C#語言包含結構化異常處理(Structured Exception Handling, SEH)的語法。用3個關鍵字可以標記出能處理異常的代碼和指令,如果發生異常,就使用這些指令處理異常。用于這個目的的3個關鍵字是try、catch和finally。它們都有一個關聯的代碼塊,必須在連續的代碼行中使用。其基本結構如下:
try
{
...
}
catch (<exceptionType> e) when (filterIsTrue)
{
<await methodName(e); >
...
}
finally
{
<await method name>
...
}
也可以在catch或finally塊內使用C# 6引入的await。await關鍵字用于支持先進的異步編程技術,避免瓶頸,且可以提高應用程序的總體性能和響應能力。利用async和await關鍵字的異步編程在本書中不討論;然而,這些關鍵字簡化了這個編程技術的實現,所以強烈建議學習它們。
也可以只有try塊和finally塊,而沒有catch塊,或者有一個try塊和好幾個catch塊。如果有一個或多個catch塊,finally塊就是可選的,否則就是必需的。這些代碼塊的用法如下:
● try—— 包含拋出異常的代碼(在談到異常時,C#語言用“拋出”這個術語表示“生成”或“導致”)。
● catch——包含拋出異常時要執行的代碼。catch塊可以使用<exceptionType>,設置為只響應特定的異常類型(如System.IndexOutOfRangeException),以便提供多個catch塊。還可以完全省略這個參數,讓通用的catch塊響應所有異常。C# 6引入了一個概念“異常過濾”,通過在異常類型表達式后添加when關鍵字來實現。如果發生了該異常類型,且過濾表達式是true,就執行catch塊中的代碼。
● finally——包含始終會執行的代碼,如果沒有產生異常,則在try塊之后執行,如果處理了異常,就在catch塊后執行,或者在未處理的異常“上移到調用堆?!敝皥绦小!吧弦频秸{用堆棧”表示,SEH允許嵌套try…catch…finally塊,可以直接嵌套,也可以在try塊包含的函數調用中嵌套。例如,如果在被調用的函數中沒有catch塊能處理某個異常,就由調用代碼中的catch塊處理。如果始終沒有匹配的catch塊,就終止應用程序。finally塊在此之前處理正是其存在的意義,否則也可以在try…catch…finally結構的外部放置代碼。
在try塊的代碼中出現異常后,依次發生的事件如下,如圖7-18所示:
● try塊在發生異常的地方中斷程序的執行。
● 如果有catch塊,就檢查該塊是否匹配已拋出的異常類型。如果沒有catch塊,就執行finally塊(如果沒有catch塊,就一定要有finally塊)。
● 如果有catch塊,但它與已發生的異常類型不匹配,就檢查是否有其他catch塊。
● 如果有catch塊匹配已發生的異常類型,且有一個異常過濾器是true,就執行它包含的代碼,再執行finally塊(如果有的話)。
● 如果有catch塊匹配已發生的異常類型,但沒有異常過濾器,就執行它包含的代碼,再執行finally塊(如果有的話)。
● 如果catch塊都不匹配已發生的異常類型,就執行finally塊(如果有的話)。

圖7-18
注意:如果存在兩個處理相同異常類型的catch塊,就只執行異常過濾器為true的catch塊中的代碼。如果還存在一個處理相同異常類型的catch塊,但沒有異常過濾器或異常過濾器是false,就忽略它。只執行一個catch塊的代碼,catch塊的順序不影響執行流。
下面用一個示例來說明異常處理。這個示例以幾種方式拋出和處理異常,以便讀者了解其機制。
試一試:異常處理:Ch07Ex02\Program.cs
(1)在C:\BegVCSharp\Chapter07目錄中創建一個新的控制臺應用程序Ch07Ex02。
(2)修改代碼,如下所示(這里顯示的行號注釋有助于將代碼與后面討論的內容聯系起來,在本章的可下載代碼中也包含這些行號,以方便參考):
class Program { static string[] eTypes = { "none", "simple", "index", "nested index", "filter" }; static void Main(string[] args) { foreach (string eType in eTypes) { try { WriteLine("Main() try block reached."); // Line 21 WriteLine($"ThrowException(\"{eType}\") called."); ThrowException(eType); WriteLine("Main() try block continues."); // Line 23 } catch (System.IndexOutOfRangeException e) when (eType == "filter") { WriteLine("Main() FILTERED System.IndexOutOfRangeException" + $"catch block reached. Message:\n\"{e.Message}\"); } catch (System.IndexOutOfRangeException e) // Line 32 { WriteLine("Main() System.IndexOutOfRangeException catch " + $"block reached. Message:\n\"{e.Message}\"); } catch // Line 36 { WriteLine("Main() general catch block reached."); } finally { WriteLine("Main() finally block reached."); } WriteLine(); } ReadKey(); } static void ThrowException(string exceptionType) { WriteLine($"ThrowException(\"{exceptionType}\") reached."); switch (exceptionType) { case "none": WriteLine("Not throwing an exception."); break; // Line 57 case "simple": WriteLine("Throwing System.Exception."); throw new System.Exception(); // Line 60 case "index": WriteLine("Throwing System.IndexOutOfRangeException."); eTypes[5] = "error"; // Line 63 break; case "nested index": try // Line 66 { WriteLine("ThrowException(\"nested index\") " + "try block reached."); WriteLine("ThrowException(\"index\") called."); ThrowException("index"); // Line 71 } catch // Line 73 { WriteLine("ThrowException(\"nested index\") general" + " catch block reached."); } finally { WriteLine("ThrowException(\"nested index\") finally" + " block reached."); } break; case "filter": try // Line 86 { WriteLine("ThrowException(\"filter\") " + "try block reached."); WriteLine("ThrowException(\"index\") called."); ThrowException("index"); // Line 91 } catch // Line 93 { WriteLine("ThrowException(\"filter\") general" + " catch block reached."); throw; } break; } } }
(3)運行應用程序,結果如圖7-19所示。

圖7-19
示例說明
這個應用程序在Main()中有一個try塊,它調用函數ThrowException()。這個函數會根據調用時使用的參數拋出異常:
● ThrowException("none")—— 不拋出異常。
● ThrowException("simple")—— 生成一般異常。
● ThrowException("index")—— 生成System.IndexOutOfRangeException異常。
● ThrowException("nested index")——包含它自己的try塊,其中的代碼調用ThrowException("index"),生成System.IndexOutOfRangeException異常。
● ThrowException("filter")—— 包含自己的try塊,try塊包含的代碼調用ThrowException("index")來生成System. IndexOutOfRangeException異常,在其中異常過濾器是true。
其中的每個string參數都存儲在全局數組eTypes中,在Main()函數中迭代,用每個可能的參數調用ThrowException()。在迭代過程中,會把各種信息寫到控制臺,說明發生了什么情況。這段代碼可以使用本章前面介紹的代碼單步執行技巧。在執行代碼的過程中,一次執行一行代碼可以確切地了解代碼的執行進度。
注意:上面代碼清單的步驟(2)未列出一條throw語句。這兩條語句在ThrowException(string exceptionType)方法中拋出異常,以激活Main()方法中的catch塊。這更好地演示了C# 6中新引入的異常過濾功能。
在代碼的第21行添加一個新斷點(用默認的屬性),該行代碼如下:
WriteLine("Main() try block reached.");
注意:這里使用了本章可下載代碼中的行號來表示代碼。如果關閉了行號,可以選擇Tools|Options,在Text Editor | C# | General選項區域打開它們。上面的代碼在注釋中包含行號,這樣讀者在閱讀這里的說明時就不需要打開文件。
在調試模式下運行應用程序。程序立即進入中斷模式,此時光標停在第20行上。如果選擇變量監視窗口中的Locals選項卡,就會看到eType當前是none。使用Step Into按鈕處理第21和第22行,看看第一行文本是否已經寫到控制臺。接著使用Step Into按鈕單步執行第23行的ThrowException()函數。
執行到ThrowException()函數后,Locals窗口會發生變化。eType和args超出了作用域(因為它們是Main()的局部變量),我們看到的是exceptionType局部參數,它當然是none。繼續單擊Step Into,到達switch語句,檢查exceptionType的值,執行代碼,把字符串Not throwing an exception寫到屏幕上。在執行第57行上的break語句時,將退出函數,繼續處理Main()中的第24行代碼。因為沒有拋出異常,所以繼續執行try塊。
接著處理finally塊。再單擊Step Into幾次,執行完finally塊和foreach的第一次循環。下次執行到第23行時,使用另一個參數simple調用ThrowException()。
繼續使用Step Into單步執行ThrowException(),最終會執行到第60行:
throw new System.Exception();
這里使用C#的throw關鍵字生成一個異常,需要為這個關鍵字提供新初始化的異常作為其參數,拋出一個異常,這里使用System名稱空間中的另一個異常System.Exception。
注意:在case塊中使用throw時,不需要break語句,使用throw就可以結束該塊的執行。
在使用Step Into執行這條語句時,將從第36行開始執行一般的catch塊。因為與第26行開始的catch塊都不匹配,所以執行這個一般的catch塊。單步執行這段代碼,然后執行finally塊,最后返回到另一個循環周期,該循環在第23行用一個新參數調用ThrowException(),這次的參數是index。
這次ThrowException()在第63行生成一個異常:
eTypes[5] = "error";
eTypes是一個全局數組,所以可以在這里訪問它。但是這里試圖訪問數組中的第6個元素(其索引從0開始計數),這會生成一個System.IndexOutOfRangeException異常。
這次Main()中有多個匹配的catch塊,其中第26行的一個catch塊有異常過濾器(eType =="filter"),第26行的另一個catch塊沒有異常過濾器。存儲在eType中的值當前是"index",因此異常過濾器是false,跳過這個catch塊。
單步執行到下一個catch塊,從第32行開始。這個塊中調用的WriteLine()使用e.Message,輸出存儲在異常中的消息(可以通過catch塊的參數訪問異常)。之后再次單步執行finally塊(而不是第二個catch塊,因為異常已經處理完畢)。返回循環,再次調用第23行的ThrowException()。
在執行到ThrowException()中的switch結構時,進入一個新的try塊,從第66行開始。在執行到第71行時,將遇到ThrowException()的一個嵌套調用,這次使用index參數。可以使用Step Over按鈕跳過其中的代碼行,因為前面已經單步執行過了。與前面一樣,這個調用生成一個System.IndexOutOfRangeException異常。但這個異常在ThrowException()中的嵌套try...catch...finally結構中處理。這個結構沒有明確匹配這種異常的catch塊,所以執行一般的catch塊(從第73行開始)。
繼續單步執行代碼,這次到達ThrowException()中的switch結構時,進入一個新的try塊,從第86行開始。到達第91行時,和以前一樣,執行一個嵌套調用ThrowException()。但是,這次處理Main()中System. IndexOutOfRangeException異常的catch塊會檢查過濾表達式(eType = = "filter"),其結果是true,所以執行該catch塊,不是處理System. IndexOutOfRangeException的、沒有異常過濾器的catch塊。
與前面的異常處理一樣,現在單步執行這個catch塊,以及關聯的finally塊,最后返回到函數調用的末尾處。但是它們有一個重要區別:拋出的異常是由ThrowException()中的代碼處理的。這就是說,異常并沒有留給Main()處理,所以直接進入finally塊,之后應用程序中斷執行。
7.2.2 列出和配置異常
.NET Framework包含許多異常類型,可以在代碼中自由拋出和處理這些類型的異常。IDE提供了一個Exceptions對話框,可以檢查和編輯可用的異常。使用Debug | Exceptions菜單項(或按下Ctrl+D, E)可打開該對話框,如圖7-20所示。
該對話框按照類別和.NET庫名稱空間列出異常。展開Common Language Runtime Exceptions的加號,就可以看到System名稱空間中的異常,這個列表包括上面使用的System.IndexOutOf-RangeException異常。
每個異常都可以使用右邊的復選框來配置。使用(break when)Thrown時,即使是對于已處理的異常,也會進入調試器。

圖7-20
- JavaScript前端開發模塊化教程
- Vue.js前端開發基礎與項目實戰
- JMeter 性能測試實戰(第2版)
- Unity 2020 Mobile Game Development
- iOS開發實戰:從零基礎到App Store上架
- Learning Network Forensics
- Getting Started with Greenplum for Big Data Analytics
- SQL Server 2016 從入門到實戰(視頻教學版)
- 現代CPU性能分析與優化
- Visual Basic程序設計實驗指導及考試指南
- Three.js權威指南:在網頁上創建3D圖形和動畫的方法與實踐(原書第4版)
- SQL Server實例教程(2008版)
- Visual Basic語言程序設計上機指導與練習(第3版)
- 高質量程序設計指南:C++/C語言
- Python GUI設計:tkinter菜鳥編程