VB.NET中的Try Catch语句与多个Catch块

29

这是一个非常奇怪的问题。我们有一个带有多个Catch块的Try Catch语句。第一个Catch块没有代码,只有一条注释。

Try
  'Some Code
Catch ex As ThreadAbortException
  'Do Nothing
Catch ex As Exception
  HandleException(ex)
End Try

如果抛出的异常不是ThreadAbortException,那么它将被第二个Catch捕获,这是预期的。然而,在VS2010中调试代码时,在这种情况下,ex对象为空。到目前为止,我们已经发现了两种“修复”此问题的方法。

解决方法1: 重命名第一个异常变量。

Try
  'Some Code
Catch tex As ThreadAbortException
  'Do Nothing
Catch ex As Exception
  HandleException(ex)
End Try

修复方法2:在第一个 Catch 块中添加任意一行代码。

Try
  'Some Code
Catch ex As ThreadAbortException
  Dim i As Integer = 1
Catch ex As Exception
  HandleException(ex)
End Try

如果在任何情况下运行HandleException中的代码,它似乎仍然能够正常工作。这是Visual Studio或调试器的错误吗?还是我们漏掉了什么,上面的第一个代码块无效?

所有这些都是在.NET 4.0中完成的。


你是在发布模式下逐步执行代码吗? - pstrjds
2
+1。好问题,在StackOverflow上很少见。 - Victor Zakharov
1
这个调试器错误在VS2012中已经被修复了。请确保您安装了SP1,它可以修复许多调试器错误。 - Hans Passant
2
我们已经安装了SP1。我们也有VS2012,但大多数开发仍在使用VS2010。如果这在VS2012中可以工作,那么这将真正确认这是一个VS2010的错误。 - Dan
哇!你通过一个简单的问题获得了90点声望 :) - Teejay
3个回答

18

然而,如果你的Catch块是空的,那么处理这个异常就没有任何意义。你只想防止最后一个块捕获它。你可以使用你的方法 - 但要考虑到,一个空的Catch块通常是不可接受的:异常应该要么不被捕获,要么被适当地处理;默默地吞噬它们必须被视为一个错误。你的情况是这个规则的一个例外,但作为这样的例外,它需要在代码中进行记录,否则会让仔细维护者感到困惑。

好的,VB有一个特殊的习惯用语来处理这种情况:

Try
    ' …
Catch ex As Exception When Not TypeOf ex Is ThreadAbortException
    ' Only executed if `ex` isn’t a ThreadAbortException
End Try

这段代码完全不会捕获ThreadAbortException,如果你不想处理它,那么这样做是正确的:ThreadAbortException无法被吞噬,即使你捕获它,在Catch块的最后仍然会重新抛出。
注意,这与SysDragon的答案基本不同,他使用传统的If语句,而这里的代码使用Catch语句中的特殊子句作为过滤器。

有很多VB是“特殊的”。 - DeanOC
@Teejay 这正是关键所在:在 Dan 的代码中,即使看起来像是被吞掉了,ThreadAbortEx没有被吞掉。但是在 Catch 块结束后,它会被隐式地重新抛出 - 你可以自己试试!只要 Dan 的 Catch 块为空,我的代码和 Dan 的代码就完全一样。 - Konrad Rudolph
哦!真的吗?我明天会试一试! - Teejay
很遗憾,ideone.com似乎不支持Catch When(他们正在使用Mono,请参见此处http://ideone.com/GPLcd8)。无论如何,丹的代码运行顺畅,并像我预期的那样**吞噬**了异常:http://ideone.com/86KNPB(我为方便起见使用了ArgumentEx)。 - Teejay
@KonradRudolph 噢!谢谢你的解释,我没注意到!实际上,在这种特殊情况下你是对的。但是一般来说,你的代码行为与丹的不同。我们误解了彼此。 - Teejay
显示剩余9条评论

6

看起来是VS的调试器bug。

证据

如果你写:

Try
    Throw New InvalidOperationException("MESSAGE")
Catch ex As ArgumentException
    'Do Nothing
Catch ex As Exception
    Debug.WriteLine(ex)
End Try

当你查看ex时,在快速监视模式下它的值为Nothing

但是

在控制台中,程序正确地打印出System.InvalidOperationException: MESSAGE


-2

好的,让我详细说明一下...

看起来每个catch中都必须有一个"result"。如果你只是想让某个特定的catch什么也不做,那就不要包含它,或者将其移动到代码的其他位置。

Try
   'Some Code
Catch ex As ThreadAbortException
   'Do something(ex: HandleExceptionSub())
Catch ex As Exception
   HandleException(ex)
End Try

如果你“捕获”了一个异常,你必须对它采取一些措施。
编辑:
我还发现了这个信息,可以帮助你进一步了解try-catch的工作原理:
多个catch块
try块可以抛出多个异常,这些异常可以使用多个catch块来处理。请记住,在一般化的catch块之前应该有更专业的catch块。否则,编译器将显示编译错误。 Multiple Catch Blocks 这并不是调试器的“缺陷”。调试器旨在帮助您找到并处理所有异常。
编辑:
从我在另一篇文章中读到的内容来看,似乎可以完全避免这种异常。而且,避免异常比不处理它更好。 Handling ThreadAbortException

编辑: 刚刚在MSDN上找到了更多关于try中多个catch块的信息。它指出,在一个空白的catch块之后的catch块将永远不会被执行...Try Catch Finally Statement 这进一步证明了这不是一个bug,而是期望的功能,以强制处理代码中的所有异常。

编辑: 为了让评论区的某些人明白,我创建了一个非常简单的测试程序来查看这是否确实是一个bug。我的发现是catch块完美地工作。似乎遵循MSDN文档中创建具有多个Catch块的Try Catch的方式正如他们所说的那样运行良好。

    private void button1_Click(object sender, EventArgs e)
    {
        try
        {
            if (textBox1.Text == "")
            {
                throw new ArgumentNullException("textBox1", "TextBox can not be empty");
            }
            else
            {
                MyString(textBox1.Text);
            }
        }
        catch (ArgumentNullException ex)
        {
            //nothing
        }
        catch (Exception ex)
        {
            MessageBox.Show("Test: " + ex.Message);
        }
    }

    private int MyString(string text)
    {
        return int.Parse(text);
    }

我创建了一个简单的表单,里面有一个按钮和一个文本框。如果文本框为空,我会抛出一个ArgumentNullException异常,在“MyString”中将一个字符串解析为整数,这会引发一个“FormatException”异常。有一个空的try catch块,这不是处理"caught"异常的正确方法,但确实有效。据我所见,这不是一个bug。显然,我唯一能同意Teejay和Konrad的观点是,你不能使用try catch方法捕获和处理ThreadAbortException异常。Konrad的解决方案是编写try catch的最佳方式。


我在另一篇帖子中找到了如何处理此异常的方法。仅仅让线程中止并不是处理停止它的理想方式。http://stackoverflow.com/questions/602494/threadabortexception-vs-graceful-event-handle-exit-in-c-sharp - jforward5
1
这篇文章中几乎所有的信息都是错误的(最后一段除外)。这确实是调试器中的一个bug,你不必在处理程序块中放置代码。最后,你关于在此处使用“GoTo”的评论非常糟糕。GoTo的有效用途非常有限(如果有的话),这并不是其中之一。 - Konrad Rudolph
1
@jforward5 Teejay已经发布了证明。此外,查看VB语言规范(§10.10.1.2)将向您展示Catch块允许为空。实际上,无需查看:如果不同,编译器将标记此为错误。 - Konrad Rudolph
1
@jforward5 请再次阅读Teejay的回答:这是调试器中的一个错误,因为调试器显示ex对象的值为“Nothing”,而实际上它有一个不同的值。调试器显示了错误的值。这是一个错误。并不需要有关于这个问题的KB文章才能说明这是一个错误:微软并非全知全能,他们不知道所有的错误,也有可能之前没有人注意到这个错误。 - Konrad Rudolph
2
@jforward5,你的观点是错误的,Teejay的代码、MSDN和语言标准都明确告诉你这一点。如果你坚信自己是正确的,那么唯一正确的做法就是为你的假设设计一个测试:编写一个满足我们评论中所有标准的代码,其中包含一个空的Catch块,后跟一个不太具体但非空的Catch块,并查看第二个块是否被执行。这被称为可证伪性,到目前为止,你拒绝使用它。 - Konrad Rudolph
显示剩余21条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接