你能在一个代码块中捕获多种类型的异常吗?

15

这个问题与我想做的接近,但还不太一样。

有没有一种方法可以简化以下代码?

private bool ValidDirectory(string directory)
{
    if (!Directory.Exists(directory))
    {
        if (MessageBox.Show(directory + " does not exist. Do you wish to create it?", this.Text) 
            == DialogResult.OK)
        {
            try
            {
                Directory.CreateDirectory(directory);
                return true;
            }
            catch (IOException ex)
            {
                lblBpsError.Text = ex.Message;
            }
            catch (UnauthorizedAccessException ex)
            {
                lblBpsError.Text = ex.Message;
            }
            catch (PathTooLongException ex)
            {
                lblBpsError.Text = ex.Message;
            }
            catch (DirectoryNotFoundException ex)
            {
                lblBpsError.Text = ex.Message;
            }
            catch (NotSupportedException ex)
            {
                lblBpsError.Text = ex.Message;
            }
        }
    }

    return false;
}

看起来这样做是浪费的,如果我以后想改变如何向用户报告错误,或者我想记录这些错误,或者其他什么,那么我就必须更改5个不同的catch块。我是错过了什么,还是这显然违反了代码重用原则?

我只是在试图变得(太)懒吗?

11个回答

24

你可以使用:

catch (SystemException ex)
{
  if(    (ex is IOException)
      || (ex is UnauthorizedAccessException )
// These are redundant
//    || (ex is PathTooLongException )
//    || (ex is DirectoryNotFoundException )
      || (ex is NotSupportedException )
     )
       lblBpsError.Text = ex.Message;
    else
        throw;
}

1
这可能会简化一些 - 删除DirectoryNotFoundException和PathTooLongException,因为(ex is IOException)将返回它们的真值。 - Aliaksei Kliuchnikau
不会的。is 运算符非常精确,它不会返回子类的 true 值,只会对准确的类返回 true。我之前也有这个想法,但在 LINQPad 中测试过了。 - Matthew Scharley
1
Matthew,以下的测试代码会返回true。我不确定你为什么得到了不同的结果... try {
throw new DirectoryNotFoundException(); } catch (Exception ex) { return (ex is IOException); }
- Aliaksei Kliuchnikau
嗯...你说得对,我可能在测试中搞砸了(在你的例子中检查IOException是否为DirectoryNotFoundException)。糟糕。不要理我。 - Matthew Scharley
1
我想说的一件事是,你应该只捕获SystemException异常。重新抛出异常并不等同于一开始就不捕获它。除此之外,FxCop和其他类似的程序会警告捕获Exception异常,而让它们安静下来的最好方法就是不要这样做。 - Matthew Scharley
C# 6的新特性:catch (Exception ex) when (ex.Message.Equals("400"))。查看:http://www.codeproject.com/Tips/1023426/Whats-New-in-Csharp - Kiquenet

8
如果异常共享一个公共超类,那么可以捕获超类。

1
他们没有。或者说,它们没有共同的超类,而我想要处理的其他可能异常也没有。 - Matthew Scharley

3

是的,你试图变得懒惰,但懒惰是程序员的优点之一,所以这很好。

至于你的问题:我不知道有没有方法,但是有一些可用的解决方法:

  • 给异常一个共同的祖先。我认为在你的情况下这可能不可能,因为它们似乎是内置的。
  • 捕获你能够捕获到的最普遍的异常。
  • 将处理代码移到自己的函数中,并从每个catch块中调用该函数。

记录一下,它们全部都是内置的,被调用的方法也是如此。 - Matthew Scharley

2

这很烦人,其他答案已经提出了好的解决方法(我会使用@Lotfi的)。

然而,这种行为是由C#的类型安全性要求的。

假设你可以这样做:

try
{
    Directory.CreateDirectory(directory);
    return true;
}
catch (IOException, 
    UnauthorizedAccessException,
    PathTooLongException,
    DirectoryNotFoundException,
    NotSupportedException ex)
{
    lblBpsError.Text = ex.Message;
}

现在,ex 是什么类型?它们都有 .Message,因为它们继承了 System.Exception,但尝试访问它们的其他属性,你会遇到问题。


你必须使用最低公共分母。你只能捕捉“异常”的导数,所以这种方式没有真正的问题。 - Matthew Scharley
所以在这种情况下,你会得到一个SystemException。编译器可以解决这个问题,因此类型安全也不会受到威胁。 - Matthew Scharley
编译器可以,IDE也可以,但这对开发者来说可能有些模糊——他们应该期望什么样的智能感知呢?不过我可以想到一种方法让它能够工作——就像C#4中的协变/逆变支持一样。 - Keith
我现在能看到了... catch (in SystemException)... 除了那已经是正在发生的事情。也许可以这么写catch(in<IOException, UnauthorizedAccessException, ...> SystemException),会很有趣。 - Matthew Scharley

2
重要的是要注意,当捕获多种类型的异常时,它们应该按最具体到最普遍的顺序排序。异常将在列表中找到第一个匹配的异常并抛出该错误,其他错误将不会被抛出。

我发帖后意识到IOException是其中一些类的基类,但它是上面列表中的第一个。无论如何,对于这段代码来说并不重要,但这是一个非常好的观点。 - Matthew Scharley

2

为了完整起见:

在VB中,您可以使用条件异常处理:

Try
    …
Catch ex As Exception When TypeOf ex Is MyException OrElse _
                           TypeOf ex Is AnotherExecption
    …
End Try

这样的Catch块只会针对指定的异常进入 - 不像C#。

也许将来的C#版本会提供类似的功能(毕竟,有一个特定的IL指令用于该代码)。

MSDN:如何在Visual Basic中过滤Catch块中的错误


1

请查看 异常处理应用程序块,它来自于EntLib。他们表达了一种非常好的基于策略和配置的异常处理方法,可以避免大型条件逻辑块。


0

1
那需要捕获“Exception”,但是是的,那是一个解决方案。 - Matthew Scharley
在类型上进行切换,而默认情况下可以再次抛出它吗? - Chris James
你可以捕获异常,但如果它不是你正在处理的类型,你总是可以再次抛出它。 - Keith
对我来说最好的解决方案是创建一个ExceptionCustom类,并在其中进行类型处理,这样就不会使代码变得混乱。 - Polo
在那个case语句中,我会添加默认操作:抛出异常。 - Keith
5
您不能在 ex.GetType() 上使用 switch 语句:该表达式必须具有隐式转换为以下类型之一的能力:sbyte、byte、short、ushort、int、uint、long、ulong、char、string。这对于 Type 类型不适用。您可以使用 switch (ex.GetType().Name),但是这样会失去类型安全性。 - Fredrik Mörk

0

您可以捕获基类异常(所有异常都派生自SystemException):

try
{
  Directory.CreateDirectory(directory);
  return true;
}
catch (SystemException ex)
{
  lblBpsError.Text = ex.Message;
}

但是这样你可能会捕获到你不想捕获的异常。


这里的问题是,ArgumentException 也是 SystemException,而我特别不想捕获那些异常,因为那是我的问题,而不是用户的问题。 - Matthew Scharley
然后你只需要添加一个捕获 ArgumentException 的 catch 分支,该分支在捕获 Exception 的分支之前执行(或者编译器会优化 try-catch 吗?)。 - RobV
它按顺序运行它们。我相当肯定,这至少没有被优化。但是,你回到了拥有三个仅纯 throw; 语句的地方。 - Matthew Scharley

0
你可以使用委托,这将实现你想要的功能:
编辑:稍微简化了一下。
static void Main(string[] args)
{
    TryCatch(() => { throw new NullReferenceException(); }, 
        new [] { typeof(AbandonedMutexException), typeof(ArgumentException), typeof(NullReferenceException) },
        ex => Console.WriteLine(ex.Message));

}

public static void TryCatch(Action action, Type[] exceptions, Action<Exception> catchBlock)
{
    try
    {
        action();
    }
    catch (Exception ex)
    {
         if(exceptions.Any(p => ex.GetType() == p))
         {
             catchBlock(ex);
         }
         else
         {
             throw;
         }
    }
}

你的特定 try/catch 将是:
bool ret;
TryCatch(
    () =>
        {
            Directory.CreateDirectory(directory);
            ret = true;
        },
    new[]
        {
            typeof (IOException), typeof (UnauthorizedAccessException), typeof (PathTooLongException),
            typeof (DirectoryNotFoundException), typeof (NotSupportedException)
        },
    ex => lblBpsError.Text = ex.Message
);

return ret;

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