关于c#:使用finally而不是catch

Using finally instead of catch

我已经见过这种模式几次了:

1
2
3
4
5
6
7
8
9
10
11
        bool success = false;
        try
        {
            DoSomething();
            success = true;
        }
        finally
        {
            if (!success)
                Rollback();
        }

我一直在想:为什么这比使用catch进行回滚更好?

1
2
3
4
5
6
7
8
9
        try
        {
            DoSomething();
        }
        catch
        {
            Rollback();
            throw;
        }

确保在失败时回滚更改的两种方法之间有什么区别?


我在这里发布了一些代码,即使它与问题并不相关(稍后将删除)。

使用此程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;

namespace testcs
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                try
                {
                    foo();
                    foo();
                    foo();
                }
                catch
                {
                    throw;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private static void foo()
        {
            throw new Exception("oops");
        }
    }
}

堆栈跟踪(查看行号!)保留,但在main函数中,您将看到"第19行",其中throw是调用foo()的真实行(第13行)。


如果您不关心这个特定的代码,那么应该使用哪种类型的异常:

1
2
3
4
5
6
7
8
9
try
{
   DoSomething();
   ok = true;
}
finally
{
    if(!ok)Rollback();
}

这将使调用堆栈保持其原始形式的100%。同样,如果您使用这样的异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try
{
   DoSomething();
   ok = true;
}
catch(FirstExcetionType e1)
{
    //do something
}
catch(SecondExcetionType e2)
{
    //do something
}
catch(Exception e3)
{
    //do something
}
finally
{
    if(!ok)Rollback();
}

在末尾使用finally可以使代码比从每个catch语句调用rollback更具可读性。


我不确定这是否只是轶事证据,但我个人使用这种模式的原因非常实际:当DoSomething抛出异常时,Visual Studio调试器将在DoSomething中中断,在第一个版本中发生异常,而在第二个版本中,它将在throw;中中断。这允许在Rollback清除所有内容之前检查应用程序状态。

Screenshot


finally语句通常用于清理资源。如果异常不是回滚事务的唯一原因,那么可以在那里使用Rollback()方法。Close()Dispose()方法是最终进入最终阶段的主要候选方法。

但是,您不希望在那里执行任何可以引发异常的操作。


finally总是被执行,而不仅仅是捕获异常。

当然,在这种特定的情况下,只有在出现错误时才需要回滚,但是作为一种通用模式,try-finally可能对资源管理更有用(通常需要确保始终正确地使用Close()Dispose())。特别是如果代码的作者来自Java背景,这个习语更广泛。


这里明确的目标是在出现任何错误时调用Rollback。这两个代码片段都实现了这一目标。第一个使用一个始终运行的finally来验证是否成功到达了try块的最后一行。第二个捕获任何错误,回滚事务,然后重新抛出捕获的异常。这两个代码段的结果都是,引发的任何异常都将导致回滚,同时仍然冒泡到下一个级别。

您提到该项目是从Java移植的。在爪哇中,可以使用eDCOX1(13)来重新抛出类似于C语言中的异常。您还可以抛出一个仍将维护调用堆栈的新异常(等)。第二个是C语言中的更简单/更简单(虽然不是很多),第一个有实际的Java工作的优势。