为什么“继续”声明不能在“最终”块内?

时间:2013-08-01 10:07:36

标签: c# .net

我没有问题;我只是好奇。想象一下以下场景:

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

这不会编译,因为它会引发compiler error CS0157

  

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

为什么?

11 个答案:

答案 0 :(得分:149)

无论是否抛出异常,

finally块都会运行。如果抛出异常,continue会做什么?您无法继续执行循环,因为未捕获的异常会将控制转移到另一个函数。

即使没有抛出异常,finally也会在try / catch块中的其他控制转移语句运行时运行,例如return,这会带来同样的问题。

简而言之,对于finally的语义,允许将控制从finally块内部转移到其外部是没有意义的。

使用一些替代语义来支持这一点会更有困难而不是有用,因为有一些简单的解决方法可以使预期的行为更加清晰。所以你得到一个错误,并被迫正确思考你的问题。这是C#中持续存在的“让你陷入成功之中”的想法。

C#, you, and the out if success

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

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

如果您希望continue仅在未引发未捕获的异常时,请将continue放在try-block之外。

答案 1 :(得分:32)

这是一个可靠的来源:

  

continue语句不能退出finally块(第8.10节)。什么时候   一个continue语句出现在finally块中,即target的目标   continue语句必须在同一个finally块中;否则,a   发生编译时错误。

摘自MSDN, 8.9.2 The continue statement

文件说:

  

当控制离开时,总是执行finally块的语句   试试声明。无论控制转移是否发生,都是如此   正常执行的结果,由于执行休息,   继续,转到或返回语句,或者作为传播的结果   try语句中的异常。如果在此期间抛出异常   执行finally块,异常传播到下一个   附上试试声明。如果另一个例外正在进行中   正在传播,这个例外就会丢失。传播过程   在throw语句的描述中进一步讨论了一个例外(第8.9.5节)。

这是 8.10 The try statement

答案 2 :(得分:31)

你可能认为这是有道理的,但实际上没有意义

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

如果抛出异常,您打算做什么中断继续? C#编译器团队不希望通过假设breakcontinue来自行决策。相反,他们决定抱怨开发人员的情况不明确,从finally block转移控制权。

因此,开发人员的工作是明确说明他打算做什么,而不是编译器假设其他事情。

我希望你理解为什么这不能编译!

答案 3 :(得分: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?
    }
}

Dreaded Goto

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仅用于

答案 4 :(得分:11)

通常continuefinally块中使用时没有意义。看看这个:

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

答案 5 :(得分:5)

  

“这不会编译,我认为这是完全合理的”

嗯,我认为不是。

如果你真的有catch(Exception),那么你不需要最终(甚至可能不是continue)。

当你有更现实的catch(SomeException)时,如果未捕获到异常会发生什么?你的continue想要单行,异常处理另一个。

答案 6 :(得分:3)

你不能离开最后一个街区的尸体。这包括中断,返回和在您的情况下继续关键字。

答案 7 :(得分:3)

finally块可以在等待重新抛出的异常时执行。能够在不重新抛出异常的情况下退出块(通过continue或其他任何东西)是没有意义的。

如果你想继续你的循环,你不需要finally语句:只需捕获异常而不要重新抛出。

答案 8 :(得分:1)

无论是否抛出未捕获的异常,

finally都会运行。其他人已经解释了为什么这会使continue不合逻辑,但这里有一个替代方案,它遵循这个代码似乎要求的精神。基本上,finally { continue; }说:

  1. 当遇到异常时,请继续
  2. 当有未捕获的异常时,允许它们被抛出,但仍然继续
  3. (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也会在第三种情况下执行,其中没有任何异常抛出,被捕获或未被捕获。如果需要,您当然可以在try-catch块之后放置一个continue,而不是在每个catch内放置。{/ p>

答案 9 :(得分:1)

从技术上讲,它是底层CIL的限制。来自language spec

  

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

  

只允许通过异常指令(leaveend.filterend.catchend.finally)来控制从受保护区域转出

br instruction的文档页面上:

  

此指令无法执行控制传入和传出try,catch,filter和finally块。

这最后适用于所有分支指令,包括beqbrfalse等。

答案 10 :(得分:-1)

该语言的设计者根本不想(或不能)推断最终块的语义被控制传输终止。

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

如果我们从finally清理块中进行控制转移,则原始控制转移被“劫持”。它被取消,控制权转移到其他地方。

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

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

但是,即使你这样做,它也无法解决如果从finally启动动态传输会发生什么的问题:如果finally块调用一个函数,该函数抛出怎么办?然后,原始异常处理被“劫持”。

如果你计算出第二种劫持形式的语义,就没有理由放弃第一种类型的劫持。它们实际上是一回事:控制转移是一种控制转移,无论它是否是相同的词汇范围。