官术网_书友最值得收藏!

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

主站蜘蛛池模板: 满洲里市| 大丰市| 永康市| 宜州市| 闸北区| 晋宁县| 颍上县| 潮州市| 江口县| 葫芦岛市| 项城市| 丁青县| 磐石市| 海安县| 东明县| 昌宁县| 武邑县| 呼伦贝尔市| 长顺县| 斗六市| 梅河口市| 绥滨县| 石楼县| 建始县| 新干县| 鹤岗市| 察雅县| 溧水县| 黄陵县| 新丰县| 孝昌县| 昌黎县| 井研县| 南雄市| 汤原县| 工布江达县| 封丘县| 崇礼县| 凤阳县| 水城县| 阜宁县|