簡體   English   中英

try catch 和 finally block 的執行順序

[英]Order of execution of try catch and finally block

我對 try、catch 和 finally 塊執行的順序感到困惑。

我也想知道什么時候應該使用 try-catch 塊,我應該在 try-catch 塊中放什么? 我還想知道 try 塊中是否出現異常,然后是否采取了對應於 try 塊的操作,那么首先執行哪個 catch 或最后執行(始終要執行)? 執行完這兩個之后,控制是返回到 try 塊還是離開它?

如果您有(注意:這不是有效的 C#,請參閱下面的有效示例):

try {
   // ... some code: A
} catch(...) {
   // ... exception code: B
} finally {
   // finally code: C
}

代碼 A 將被執行。 如果一切順利(即在執行 A 時沒有拋出異常),它將轉到 finally,因此將執行代碼 C。 如果在執行 A 時拋出異常,那么它將轉到 B,然后最后到 C。

例如,這是來自http://msdn.microsoft.com/en-us/library/dszsf989.aspx的有效 C# 代碼塊:

public class EHClass
{
    void ReadFile(int index)
    {
        // To run this code, substitute a valid path from your local machine
        string path = @"c:\users\public\test.txt";
        System.IO.StreamReader file = new System.IO.StreamReader(path);
        char[] buffer = new char[10];
        try
        {
            file.ReadBlock(buffer, index, buffer.Length);
        }
        catch (System.IO.IOException e)
        {
            Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message);
        }
        finally
        {
            if (file != null)
            {
                file.Close();
            }
        }
        // Do something with buffer...
    }
}

使用 try/catch/finally 的原因是為了防止您的程序在某些代碼(上例中的 A)出現錯誤時失敗。 如果出現問題,您可以使用catch部分來捕獲問題並做一些有用的事情,例如通知用戶、將異常記錄到日志文件中、重試或嘗試其他您認為可能有效的方法,而不是您嘗試過的方法起初。

finally用於確保執行一些清理。 例如,在 A 中,您可能會嘗試打開一個文件並閱讀它。 如果打開成功,但讀取失敗,您將有一個打開的文件懸空。 在這種情況下,您希望將其關閉,您將在finally塊中執行此操作 - 該塊總是被執行,從而保證文件的關閉。

在這里查看更多信息:

try ... catch塊用於捕獲異常。 try塊中,您放置您期望可能引發異常的代碼。

如果沒有發生異常,則try塊中的代碼按預期完成。 如果有finally塊,那么接下來會執行。

如果確實發生了異常,則執行跳轉到第一個匹配的catch塊的開頭。 一旦該代碼完成,finally 塊(如果存在)就會被執行。 執行不會返回到try塊。

你幾乎不應該使用 try/catch。

您應該只catch您實際上可以糾正的異常,並且僅在您期望它們時捕獲它們。 否則,讓調用者處理異常 - 或不。

如果使用,任何catch子句都會首先執行 - 只有其中一個。

然后, finally被“最終”執行。


這在很多地方都有更好的說明,但我會嘗試。 以下代碼:

try
{
    // Do something here
}
catch (Exception ex)
{
    MessageBox.Show("Friendly error message");
}

不修復異常。 它隱藏了異常,因此問題永遠不會得到解決。 該代碼不知道拋出了哪個異常,因為它會捕獲所有異常,並且它不會糾正問題 - 它只是告訴用戶一個禮貌的虛構。

事實上,上面的代碼應該替換為以下代碼:

// Do something here

這樣,如果此方法的調用者知道如何修復特定問題,那么調用者就可以修復它們。 您不會從調用者那里刪除該選項。

如果調用者不知道如何解決問題,那么調用者也不應該捕獲異常。


這是一個以合理方式使用異常的示例(來自 MSDN)。 它是SmtpFailedRecipientsException Class文檔中示例的修改形式。

public static void RetryIfBusy(string server)
{
    MailAddress from = new MailAddress("ben@contoso.com");
    MailAddress to = new MailAddress("jane@contoso.com");
    using (
        MailMessage message = new MailMessage(from, to)
                                  {
                                      Subject = "Using the SmtpClient class.",
                                      Body =
                                          @"Using this feature, you can send an e-mail message from an application very easily."
                                  })
    {
        message.CC.Add(new MailAddress("Notifications@contoso.com"));
        using (SmtpClient client = new SmtpClient(server) {Credentials = CredentialCache.DefaultNetworkCredentials})
        {
            Console.WriteLine("Sending an e-mail message to {0} using the SMTP host {1}.", to.Address, client.Host);
            try
            {
                client.Send(message);
            }
            catch (SmtpFailedRecipientsException ex)
            {
                foreach (var t in ex.InnerExceptions)
                {
                    var status = t.StatusCode;
                    if (status == SmtpStatusCode.MailboxBusy || status == SmtpStatusCode.MailboxUnavailable)
                    {
                        Console.WriteLine("Delivery failed - retrying in 5 seconds.");
                        System.Threading.Thread.Sleep(5000); // Use better retry logic than this!
                        client.Send(message);
                    }
                    else
                    {
                        Console.WriteLine("Failed to deliver message to {0}", t.FailedRecipient);
                            // Do something better to log the exception
                    }
                }
            }
            catch (SmtpException ex)
            {
                // Here, if you know what to do about particular SMTP status codes,
                // you can look in ex.StatusCode to decide how to handle this exception
                // Otherwise, in here, you at least know there was an email problem
            }
            // Note that no other, less specific exceptions are caught here, since we don't know
            // what do do about them
        }
    }
}

請注意,此代碼使用 try/catch 包圍一小段代碼。 在該 try/catch 塊中,如果拋出 SmtpException 或 SmtpFailedRecipientsException,我們知道該怎么做。 例如,如果我們要捕獲IOException ,我們將不知道它意味着什么,或者如何處理它。 不應捕獲您實際上不知道如何糾正的任何異常,除非可能將信息添加到異常,記錄並重新拋出。

這是一個例子:

try
{
    someFunctionThatWorks();

    functionThatThrowsAnException(); // As soon as this function throws an exception we are taken to the catch block

    anotherFunction();  // <-- This line will never get executed
}
catch(Exception e)
{
    // Here you can handle the exception, if you don't know how to handle it you should not be catching it
    // After this you will not be taken back to the try block, you will go right to the finally block
}
finally
{
    // Code here is always executed at the very end, regardless of whether an exception was thrown or not
}

我想詳細說明一下,並擴展 @icyrock.com 的答案,當你在 catch 塊中重新拋出異常時,它會在執行堆棧的較低位置處理......

我嘗試使用以下代碼:

static void Main(string[] args)
{
    try
    {
        // pick one:
        // NormalExcecution();
        // TroubleExcecution();
    }
    catch
    {
        Console.WriteLine("block D");
    }
    
    Console.ReadKey();
}

private static void NormalExcecution()
{
    try
    {
        Console.WriteLine("block A");
    }
    catch (Exception)
    {
        Console.WriteLine("block B");
        throw;
    }
    finally
    {
        Console.WriteLine("block C");
    }
}

private static void TroubleExcecution()
{
    try
    {
        Console.WriteLine("block A");
        throw new Exception();
    }
    catch (Exception)
    {
        Console.WriteLine("block B");
        throw;
    }
    finally
    {
        Console.WriteLine("block C");
    }
}

所以當塊A中沒有異常時,那么順序如下(永遠不會命中異常處理塊):

Block A
Block C

當A塊出現問題時,順序如下:

block A
block B
block C
block D

換句話說,發生的異常首先由塊 B 處理,然后執行 finally 子句,只有在異常重新拋出並在執行堆棧(塊 D)的較低位置處理之后。

請注意,我可能對 .NET 框架下實際發生的事情有誤——我只是展示了我觀察到的結果:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM