簡體   English   中英

為什么“ continue”語句不能在“ finally”塊內?

[英]Why can't a 'continue' statement be inside a 'finally' block?

我沒問題 我只是好奇。 想象以下情況:

foreach (var foo in list)
{
    try
    {
         //Some code
    }
    catch (Exception)
    {
        //Some more code
    }
    finally
    {
        continue;
    }
}

這不會編譯,因為它會引起編譯器錯誤CS0157

控制不能離開finally子句的主體

為什么?

不管是否引發異常, finally塊都會運行。 如果引發異常,該怎么辦將continue 您無法繼續執行循環,因為未捕獲的異常會將控制權轉移到另一個函數。

即使沒有拋出異常,try / catch塊中的其他控件傳輸語句運行(例如return時, finally也會finally運行,例如,這會帶來相同的問題。

簡而言之,使用finally的語義,不允許將控制權從finally塊內部轉移到其外部。

用一些替代的語義來支持此方法比有用的方法更令人困惑,因為有一些簡單的變通方法可以使預期的行為更清晰。 因此,您會得到一個錯誤,並被迫正確考慮您的問題。 這是C#中普遍存在的“使您陷入成功的陷阱”的想法。

C#,你,如果成功的話

如果您想忽略異常(通常不是一個壞主意)並繼續執行循環,請使用catch all塊:

foreach ( var in list )
{
    try{
        //some code
    }catch{
        continue;
    }
}

如果你想continue ,只有當沒有捕獲的異常拋出,只是把continue在try塊外。

這是可靠的來源:

Continue語句不能退出finally塊(第8.10節)。 當在finally塊內出現continue語句時,continue語句的目標必須在相同的finally塊內。 否則,將發生編譯時錯誤。

它取自MSDN, 8.9.2繼續聲明

該文檔說:

當控制離開try語句時,總是執行finally塊的語句。 無論是由於正常執行,由於執行break,continue,goto或return語句,還是由於在try語句中傳播異常而發生了控制轉移,都是如此。 如果在執行finally塊期間引發了異常,則該異常將傳播到下一個封閉的try語句。 如果另一個異常正在傳播中,則該異常將丟失。 在throw語句的描述中將進一步討論傳播異常的過程(第8.9.5節)。

從這里8.10 try語句

您可能認為這很有意義,但實際上卻沒有任何意義

foreach (var v in List)
{
    try
    {
        //Some code
    }
    catch (Exception)
    {
        //Some more code
        break; or return;
    }
    finally
    {
        continue;
    }
}

當拋出異常時,您打算做什么休息繼續 C#編譯器團隊不希望通過假設作出自己的決定breakcontinue 取而代之的是,他們決定抱怨開發者的情況對於從finally block轉移控制權將是模棱兩可的。

因此,開發人員的工作是清楚地陳述自己打算做什么,而不是讓編譯器假設其他事情。

希望您能理解為什么它無法編譯!

正如其他人所述,但專注於異常,它實際上是關於轉移控制權的模棱兩可的處理。

在您的腦海中,您可能會想到這樣的情況:

public static object SafeMethod()
{
    foreach(var item in list)
    {
        try
        {
            try
            {
                //do something that won't transfer control outside
            }
            catch
            {
                //catch everything to not throw exceptions
            }
        }
        finally
        {
            if (someCondition)
                //no exception will be thrown, 
                //so theoretically this could work
                continue;
        }
    }

    return someValue;
}

從理論上講,您可以跟蹤控制流並說“是的”。 沒有引發異常,沒有控制權被轉移。 但是C#語言設計人員還要考慮其他問題。

被拋出的異常

public static void Exception()
{
    try
    {
        foreach(var item in list)
        {
            try
            {
                throw new Exception("What now?");
            }
            finally
            {
                continue;
            }
        }
    }
    catch
    {
        //do I get hit?
    }
}

可怕的五島

public static void Goto()
{
    foreach(var item in list)
    {
        try
        {
            goto pigsfly;
        }
        finally
        {
            continue;
        }
    }

    pigsfly:
}

回報

public static object ReturnSomething()
{
    foreach(var item in list)
    {
        try
        {
            return item;
        }
        finally
        {
            continue;
        }
    }
}

分手

public static void Break()
{
    foreach(var item in list)
    {
        try
        {
            break;
        }
        finally
        {
            continue;
        }
    }
}

所以在最后,是的,雖然使用輕微的可能性continue在控制不被轉移的情況,但情況下一個很好的協議(多數?)涉及異常或return塊。 語言設計師認為這太含糊了,並且(很可能)無法確保在編譯時在未傳輸控制流的情況下使用您的continue

通常,在finally塊中使用continue並沒有意義。 看看這個:

foreach (var item in list)
{
    try
    {
        throw new Exception();
    }
    finally{
        //doesn't make sense as we are after exception
        continue;
    }
}

“這不會編譯,我認為這完全有道理”

好吧,我認為不是。

當您字面上有catch(Exception)則不需要finally(甚至可能不需要continue )。

當您擁有更現實的catch(SomeException) ,如果未捕獲異常,應該怎么辦? continue想走一種方法,處理另一種異常。

您不能離開finally塊的主體。 這包括break,return和您可能的continue關鍵字。

可以執行finally塊,但有一個等待重新拋出的異常。 能夠(通過continue或其他任何方式)退出該塊而不拋出該異常並沒有任何意義。

如果您想繼續循環,無論發生什么情況,都不需要finally語句:只需捕獲異常就不會重新拋出。

finally運行,無論是否引發未捕獲的異常。 其他人已經解釋了為什么這會continue不合邏輯,但是這里有一個替代方案,它遵循了此代碼似乎要求的精神。 基本上, finally { continue; } finally { continue; }說:

  1. 當發現異常時,繼續
  2. 當有未捕獲的異常時,允許將它們拋出,但仍繼續

(1)可以通過在每個catch的末尾放置continue來滿足,並且(2)可以通過存儲未捕獲的異常以待稍后拋出來滿足。 您可以這樣寫:

var exceptions = new List<Exception>();
foreach (var foo in list) {
    try {
        // some code
    } catch (InvalidOperationException ex) {
        // handle specific exception
        continue;
    } catch (Exception ex) {
        exceptions.Add(ex);
        continue;
    }
    // some more code
}
if (exceptions.Any()) {
    throw new AggregateException(exceptions);
}

實際上, finally在第三種情況下也將執行,在這種情況下,根本不會引發任何異常,無論是捕獲還是未捕獲。 如果需要的話,您當然可以在try-catch塊之后放置一個continue ,而不是在每個catch

從技術上講,這是對基礎CIL的限制。 根據語言規范

除非通過異常處理機制,否則絕對不允許控制傳遞進入catch處理程序或finally子句。

只能通過異常指令( leaveend.filterend.catchend.finally )才允許將控制轉移出受保護區域。

br指令的文檔頁面上:

該指令無法執行控制進出try,catch,filter和finally塊的操作。

這對於所有分支指令(包括beqbrfalse等等)都適用。

由於Python 3.8finally塊中continue ,因此允許。

由於實現存在問題,finally子句中的continue語句是非法的。 在Python 3.8中,取消了此限制。 (由Serhiy Storchaka在bpo- 32489中貢獻。)

https://docs.python.org/3.8/whatsnew/3.8.html

該語言的設計者根本不想(或無法)推斷由控制傳遞終止的finally塊的語義。

一個問題或可能的關鍵問題是, finally塊作為某些非本地控制傳輸(異常處理)的一部分而執行。 控制傳遞的目標不是封閉循環; 異常處理將中止循環並繼續展開。

如果我們從finally清除塊中移出了控制權,則原始控制權被“劫持”。 它被取消,控制權轉移到其他地方。

語義可以解決。 其他語言也有。

C#的設計人員決定簡單地禁止靜態的,類似於“ goto”的控件傳輸,從而在某種程度上簡化了事情。

但是,即使您這樣做,也不能解決如果從finally啟動動態傳輸會發生什么問題:如果finally塊調用一個函數並且該函數拋出該怎么辦? 然后,原始異常處理被“劫持”。

如果弄清了第二種劫持形式的語義,則沒有理由取消第一種劫持。 它們實際上是同一回事:控件轉移就是控件轉移,無論它是否在相同的詞法范圍內。

暫無
暫無

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

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