为什么“ continue”语句不能在“ finally”块内?


107

我没问题 我只是好奇。想象以下情况:

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

这不会编译,因为它会引起编译器错误CS0157

控制不能离开finally子句的主体

为什么?


7
所以。只是好奇。如果您完全理解为什么不编译,为什么要有人解释已经有意义的原因?=)
J. Steen

14
为什么你会需要continue;finally块?是不是一样continue;的后try -- catch块?
bansi 2013年

6
@ J.Steen不,我确实知道这finally/continue是C#编译器的限制:-)我也对此限制的原因感到好奇。
xanatos

5
@xanatos-从技术上讲,这是对基础CIL的限制。根据语言规范:“除通过异常处理机制外,绝不允许控制传递进入捕获处理程序或finally子句。” 和“仅允许通过异常指令(离开,end.filter,end.catch或end.finally)将控制权移出保护区。” 该br分支指令的家庭不能做到这一点。
2013年

1
@Unsigned:这也是一个很好的限制,允许它是奇怪的:)
2013年

Answers:


150

finally无论是否引发异常,块都会运行。如果抛出异常,该怎么continue办?您无法继续执行循环,因为未捕获的异常会将控制权转移给另一个函数。

即使没有抛出异常,finally当try / catch块中的其他控件传输语句return(例如)运行时也会运行,这会带来相同的问题。

简而言之,使用finally它的语义不允许将控制从finally块内部转移到块外部。

用一些替代的语义来支持它会比帮助更令人困惑,因为有一些简单的变通方法可以使预期的行为更清晰。因此,您会得到一个错误,并被迫正确考虑您的问题。这是C#中普遍存在的“使您陷入成功的陷阱”的想法。

C#,您,如果成功,请出门

如果要忽略异常(通常不是一个坏主意)并继续执行循环,请使用catch all块:

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

如果只想continue在未引发任何未捕获的异常的情况下,只需将其放在continuetry块之外。


12
我会接受这个作为答案,因为您了解了Microsoft可能最终决定不接受继续的原因。可能图像也使我信服了:)
lpaloub

20
您能否详细阐述“让您进入成功之路”的想法?我没听懂-D
Ant


13
图像是由顺便说一句Jon Skeet制作的。我从这里得到:msmvps.com/blogs/jon_skeet/archive/2010/09/02/...
R.费尔南德斯Martinho

1
当然,如果块continue中的a finally仅继续在finally块内定义的局部循环,则可以。但是,在问题中,它试图继续“外部”循环。在finally块中不能包含的类似语句是returnbreak(从块中突破时)和goto(到finally块外的标签时)。有关Java的相关讨论,请参见从Java中的finally块返回
Jeppe Stig Nielsen

32

这是可靠的来源:

继续语句不能退出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语句


31

您可能认为这很有意义,但实际上却没有任何意义

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

当抛出异常时,您打算做什么休息继续?C#编译器团队不想通过假设break或自己做出决定continue。取而代之的是,他们决定抱怨开发人员在将控制权从转让时会产生歧义finally block

因此,开发人员的工作是清楚地陈述自己打算做什么,而不是让编译器假设其他事情。

希望您能理解为什么它无法编译!


值得一提的是,即使没有“ catch”,finally语句之后的执行路径也会受到语句是否catch已通过异常退出的影响,并且没有机制说明语句应如何与a交互continue。不过,我喜欢您的示例,因为它显示出更大的问题。
2013年

@supercat同意您的意见,我的回答显示了一个编译器模棱两可的情况的示例,但这种方法仍然存在很多问题。
Sriram Sakthivel

16

正如其他人所述,但专注于异常,这实际上是关于转移控制权的模棱两可的处理。

在您看来,您可能正在考虑这样的情况:

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使用您的语言。


当proguard在混淆时崩溃时,在Java中也遇到了同样的情况。您的解释很好。C#给出了编译时错误-完全有道理。
Dev_Vikram'3

11

通常continue,在finally块中使用时没有任何意义。看看这个:

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

“一般”很多事情都没有道理。这并不意味着它不应“特别”起作用。IE:continue在循环外声明没有任何意义,但这并不意味着它不受支持。
zerkms

我认为这是有道理的。item可以是文件,读取失败-> finally关闭文件。continue阻止其余的处理。
jnovacho

5

“这不会编译,我认为这完全有道理”

好吧,我认为不是。

当您真正拥有时,catch(Exception)则不需要final(甚至可能不需要continue)。

如果您比较现实catch(SomeException),那么在未捕获异常的情况下应该怎么办?您continue想采用一种方式,而异常处理则采用另一种方式。


2
我想你有空的finally时候可能会需要catch。通常用于以可靠的方式关闭资源。
jnovacho

但是,这不仅仅是例外。您可以轻松地returntrycatch块内添加一条语句。我们现在干什么?返回还是继续循环?(如果我们继续,那么如果它是要迭代的最后一个项目呢?那么我们就继续在之外,foreach并且返回永远不会发生?)编辑:或者甚至goto到循环外的标签,几乎所有将控制转移到foreach循环外的动作。
克里斯·辛克莱尔

2
我不同意“当您确实有catch(Exception)时,您就不需要finally(甚至可能不需要continue)。” 如果无论是否引发异常,我都需要进行操作怎么办?
lpaloub

好的,最后return在块中满足了其他要求。但是通常情况下,执行过程会在包罗万象之后继续进行。
Henk Holterman

“最终”不是“捕获”。它应该用于清除代码。无论是否引发异常,都会运行 finally块。诸如关闭文件或释放内存(如果您使用的是非托管代码)之类的东西,这些就是您放置在finally块中的内容
Robotnik 2013年


3

finally可以执行该块,但有一个等待重新抛出的异常。能够(不通过a continue或其他方式)退出该块而不抛出异常实际上并没有任何意义。

如果您想继续循环,无论发生什么情况,都不需要finally语句:只需捕获异常就不会重新抛出。


1

finally运行是否引发未捕获的异常。其他人已经解释了为什么这会导致continue不合逻辑,但是这里有一个替代方案,它遵循了此代码似乎要求的精神。基本上finally { continue; }是说:

  1. 当发现异常时,继续
  2. 当有未捕获的异常时,允许将它们抛出,但仍继续

(1)可以通过放置continue在每个元素的末尾来满足catch,而(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在第三种情况下也将执行,在这种情况下,根本不会引发任何异常,无论是捕获还是未捕获。如果需要的话,您当然可以continue在try-catch块之后放置一个,而不是在每个内部放置一个catch


1

从技术上讲,这是对基础CIL的限制。根据语言规范

除非通过异常处理机制,否则不允许控制传递进入catch处理程序或finally子句。

控制转移出一个受保护的区域的仅通过一个异常指令允许(leaveend.filterend.catch,或end.finally

在针对该文档页面br指令

该指令无法执行控制进出try,catch,filter和finally块的操作。

这最后看到真正的所有分支指令,包括beqbrfalse


-1

该语言的设计者根本不想(或无法)推断由控制传递终止的finally块的语义。

一个问题,或者可能是关键问题,是该finally块作为某些非本地控制传输(异常处理)的一部分执行。控制传递的目标不是封闭循环;异常处理将中止循环并继续展开。

如果我们有finally清除块之外的控制转移,则原始的控制转移被“劫持”。它被取消,控制权移到其他地方。

语义可以解决。其他语言也有。

C#的设计人员决定简单地禁止静态的,类似于“ goto”的控件传输,从而在某种程度上简化了事情。

但是,即使您这样做,也不能解决以下问题finally:如果从a启动动态传输,将会发生什么:如果finally块调用了一个函数,并且该函数引发了该怎么办?然后将原始异常处理“劫持”。

如果弄清了第二种劫持形式的语义,则没有理由取消第一种劫持。它们实际上是同一回事:控件转移就是控件转移,无论其词法作用域是否相同。


区别在于,finally根据定义,必须立即继续触发它的传输(未捕获的异常return等)。允许在内部进行“类似goto的”传输finally(顺便说一下,这是哪种语言进行的?)很容易,但是这样做将意味着try { return } finally { ... } 可能不会返回,这完全是意外的。如果finally调用一个引发函数,那并不是完全一样,因为我们已经期望异常可能随时发生并中断正常流程。
nmclean 2013年

@nmclean:实际上,转义的异常与之类似finally,因为它会导致程序异常和不合逻辑的流程。唯一的不同是,语言设计者无法拒绝所有程序,而最终程序块可能抛出未处理的异常,而是允许此类程序进行编译,并希望该程序可以接受放弃早期更正异常后可能产生的任何后果。序列。
2013年

@supercat我同意这会破坏预期的流程,但是我的观点是,无论当前流程是否为a,未处理的异常总是如此finally。根据卡兹(Kaz)上一段的逻辑,函数也应该可以通过continue诸如此类的方式自由地影响其调用者作用域中的流程,因为允许它们通过例外方式这样做。但这当然是不允许的。例外是该规则的唯一例外,因此名称为“例外”(或“中断”)。
nmclean 2013年

@nmclean:非嵌套异常表示与正常执行不同的控制流,但仍然是结构化的。仅当在该块内发生的所有异常都在其中捕获到异常时,才应执行该块后的语句。这似乎是一个合理的期望,但是finally块中发生的异常可能会违反它。真是一个令人讨厌的情况,IMHO应该至少通过让finally块知道任何未决异常来解决它,以便它们可以构建复合异常对象。
2013年

@mmclean对不起,我不同意“例外”这个名称来自C#,LOL特有的“规则例外”(关于控制)。异常的控制流更改方面只是一个非本地动态控制传递。在相同词法范围内进行常规控制转移(不会发生平仓)可以视为这种情况的特例。
卡兹(Kaz)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.