使用例外狀況

在 C# 中,若程式在執行階段發生錯誤,會利用一種稱為例外狀況的機制,讓整個程式都得知此狀況。 例外狀況會由發生錯誤的程式碼擲回,然後被可更正該錯誤的程式碼攔截。 例外狀況會由 .NET 執行階段或程式中的程式碼擲回。 一旦擲回了例外狀況,便會在呼叫堆疊中將此訊息往上傳,直至找到例外狀況的 catch 陳述式為止。 未被攔截的例外狀況會由系統提供的泛型例外處理常式負責處理,這時會顯示對話方塊。

例外狀況會以衍生自 Exception 的類別表示。 此類別會識別例外狀況的類型,並包含具有例外狀況詳細資料的屬性。 擲回例外狀況的作業包括建立例外狀況衍生類別的執行個體、選擇性地設定例外狀況的屬性,然後使用 throw 關鍵字擲回物件。 例如:

class CustomException : Exception
{
    public CustomException(string message)
    {
    }
}
private static void TestThrow()
{
    throw new CustomException("Custom exception in TestThrow()");
}

擲回例外狀況之後,執行階段會檢查目前的陳述式是否在 try 區塊內。 如果是的話,便會檢查與 try 區塊關聯的任何 catch 區塊,以查看其是否能攔截該例外狀況。 Catch 區塊通常會指定例外狀況類型;如果 catch 區塊的類型與例外狀況或其基底類別的類型相同,catch 區塊便可處理此方法。 例如:

try
{
    TestThrow();
}
catch (CustomException ex)
{
    System.Console.WriteLine(ex.ToString());
}

如果擲回例外狀況的陳述式不在 try 區塊內,或是例外狀況所在的 try 區塊內沒有相符的 catch 區塊,執行階段便會檢查 try 陳述式和 catch 區塊的呼叫方法。 執行階段會繼續在呼叫堆疊中往上搜尋,直到找到相容的 catch 區塊。 找到並執行 catch 區塊之後,控制項就會傳遞至該 catch 區塊後面的下一個陳述式。

try 陳述式可以包含多個 catch 區塊。 第一個可以處理例外狀況的 catch 陳述式會先執行,接在後面的任何 catch 陳述式不論相容與否,都會予以略過。 將 catch 區塊從最特殊 (最具衍生性) 的區塊排列到最不特殊的區塊。 例如:

using System;
using System.IO;

namespace Exceptions
{
    public class CatchOrder
    {
        public static void Main()
        {
            try
            {
                using (var sw = new StreamWriter("./test.txt"))
                {
                    sw.WriteLine("Hello");
                }
            }
            // Put the more specific exceptions first.
            catch (DirectoryNotFoundException ex)
            {
                Console.WriteLine(ex);
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine(ex);
            }
            // Put the least specific exception last.
            catch (IOException ex)
            {
                Console.WriteLine(ex);
            }
            Console.WriteLine("Done");
        }
    }
}

執行 catch 之前,執行階段會檢查 finally 區塊。 Finally 區塊可讓程式設計人員清除任何中止之 try 區塊中所殘留的任何模稜兩可狀態,或是不用等候執行階段內的記憶體回收行程完成物件,便可釋放任何外部資源 (例如圖形控制代碼、資料庫連接,或檔案資料流)。 例如:

static void TestFinally()
{
    FileStream? file = null;
    //Change the path to something that works on your machine.
    FileInfo fileInfo = new System.IO.FileInfo("./file.txt");

    try
    {
        file = fileInfo.OpenWrite();
        file.WriteByte(0xF);
    }
    finally
    {
        // Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
        file?.Close();
    }

    try
    {
        file = fileInfo.OpenWrite();
        Console.WriteLine("OpenWrite() succeeded");
    }
    catch (IOException)
    {
        Console.WriteLine("OpenWrite() failed");
    }
}

WriteByte() 擲回例外狀況時,如果未呼叫 file.Close(),則嘗試重新開啟檔案之第二個 try 區塊中的程式碼會失敗,而且該檔案會保持鎖定狀態。 由於即使擲回例外狀況也會執行 finally 區塊,因此上述範例中的 finally 區塊可讓檔案正常關閉,並協助避免發生錯誤。

擲回例外狀況之後,如果在呼叫堆疊中找不到相容的 catch 區塊,則會發生下列三種情況的其中一種:

  • 如果例外狀況在完成項內,就會中止完成項並呼叫基底完成項 (如果有)。
  • 如果呼叫堆疊包含靜態建構函式或靜態欄位初始設定式,則會擲回 TypeInitializationException,同時將原始例外狀況指派給新例外狀況的 InnerException 屬性。
  • 如果到達執行緒的開頭,則會終止執行緒。