C# try {} catch {}

10

嗨,感谢阅读。我是一个编程、C# 和套接字编程的新手。在我的代码中,我使用 try 和 catch 来提供应用程序的容错能力。以下是:

        catch (ArgumentNullException e)
        {
            OnNetworkEvents eventArgs = new OnNetworkEvents("Network Unavailable", e.Message);
            OnUpdateNetworkStatusMessage(this, eventArgs);
        }
        catch (EncoderFallbackException e)
        {
            OnNetworkEvents eventArgs = new OnNetworkEvents("Network Unavailable", e.Message);
            OnUpdateNetworkStatusMessage(this, eventArgs);
        }
        catch (SocketException e)
        {
            OnNetworkEvents eventArgs = new OnNetworkEvents("Network Unavailable", e.Message);
            OnUpdateNetworkStatusMessage(this, eventArgs);
        }
        catch (ArgumentOutOfRangeException e)
        {
            OnNetworkEvents eventArgs = new OnNetworkEvents("Network Unavailable", e.Message);
            OnUpdateNetworkStatusMessage(this, eventArgs);
        }
        catch (ObjectDisposedException e)
        {
            OnNetworkEvents eventArgs = new OnNetworkEvents("Network Unavailable", e.Message);
            OnUpdateNetworkStatusMessage(this, eventArgs);
        }

我想知道能否用一个代码段替换这个重复的代码:

catch (Exception e) { handle here}

这个方法可行吗?

再次感谢。


只要你不介意以这种方式处理所有异常,它就能正常工作。这是你想要的吗? - Paddy
我相信您可以首先单独选择特定类型的异常,然后如果您像您所建议的那样在一般情况下捕获异常,它将捕获其余部分。 - Jean-Bernard Pellerin
7
你确定所有这些异常都意味着网络不可用吗? - Austin Salonen
这里有一个更好的:https://dev59.com/dXM_5IYBdhLWcg3wn0rT - Jon Seigel
网络不可用是当网络错误发生且无法修复时的简单解决方案。此后的步骤是重新建立网络作为新的连接/会话。这是一个简单的2个对等体解决方案,适用于简单的游戏。 - iTEgg
显示剩余2条评论
11个回答

23

不要捕获 System.Exception,这点我今天已经说了很多遍了,但我真的强调不够。你不能处理 OutOfMemoryException 或者 AccessViolationException,因此不要捕获它们!

至于示例代码,我非常怀疑 ArgumentNullException 或者 ArgumentOutOfRangeException 异常会对应网络错误。如果是这样,那么很可能意味着真正应该捕获此异常的组件并没有做到。

“容错性”并不意味着“忽略所有异常以防止应用程序崩溃”,而是实际上了解可能出现的问题并正确地进行处理


4
根据出现错误的原因,有一些解决方法。例如,如果由于尝试声明一个大小为4GB的byte[]而导致OutOfMemoryException,那么这很糟糕。进行笼统的陈述是不好的。通常会有一种边缘情况证明你异常,而非规则。 - Matthew Whited
13
虽然我在精神上同意,但我不能说“永远不会捕获System.Exception”。我有Windows服务处理大量数据,例如分析超过100k+行,如果一些非结构化数据引发未处理的异常,这是我无法预料的情况,我就会捕获这个System.Exception,并继续处理剩余的记录并通过我们的日志机制发出警报。很抱歉,在无人值守的进程中,特别是在批处理中,这个catch all是有用的。这是我的个人意见。 - Ta01
2
@Aaronaught: "你无法处理OutOfMemoryException"。您能否解释一下为什么?我刚刚尝试执行以下代码行:“ArrayList array = new ArrayList(500000000);”,并成功捕获了OutOfMemoryException,这只是告诉我我没有足够的内存。程序状态完全没有被破坏。 - Igor Korkhov
2
@Aaronaught:文档还说:“以下 Microsoft 中间语言(MSIL)指令会抛出 OutOfMemoryException:box、newarr、newobj”。这意味着 CLR 无法找到给定大小的空闲内存块。它不会扩展或缩小其他对象的内存以支持您的请求。这就是为什么我告诉你,在我的示例中状态没有被破坏的原因。 - Igor Korkhov
1
首先,你永远不知道程序的状态在任何时刻是否已经损坏,至少在使用C#编写的程序中是如此(或者任何不支持自动数学演示程序语义与该程序规范一致性的语言)。但这与异常无关。OutOfMemoryException只是表示无法分配N字节的内存,就像NullReferenceException表示尝试取消引用空引用。而且由程序员决定在每种情况下该做什么。 - Igor Korkhov
显示剩余14条评论

16

一般来说,不要捕获 System.Exception 异常。 这是个坏主意,并且表明了对异常目的的误解。我认为这是由开发人员相信异常本身就是错误而不是触发异常的代码所导致的。

我经常在生产代码中看到这种问题,它通常会掩盖真正的错误,因为有价值的异常被捕获和吞噬了,导致应用程序处于糟糕的状态。这使得跟踪故障的根本原因变得更加困难。

使用你在这里使用的方法来捕获异常就是比较正确的方法。不过,有几点需要考虑改进:

  1. 通常不要将 ArgumentNullExceptionArgumentOutOfRangeException 视为运行时网络故障(除非确实是)。相反,应该将其视为逻辑错误,尽可能快地使程序失败(以便您可以尽可能接近故障点使用调试器进行检查)。这通常意味着根本不捕获任何 ArgumentException

  2. 考虑检查异常层次结构以找到一个涵盖您要报告的异常的适当基本异常。例如,(我没有查过),假设你的三个异常都派生自SocketException。你可以通过捕获一个SocketException来节省一些输入时间,而不是分别捕获这三个异常。但是只有在报告所有套接字异常时才能这样做。(这基本上是您最初尝试仅捕获 Exception 的更加纪律版)

  • 因为每个异常处理程序中的两行代码都是相同的,所以您可以创建一个函数,在单个处理程序中执行这两行工作。这是典型的不要重复自己的重构。如果您想要更改如何报告异常,请考虑更改单个函数比所有个别处理程序更容易。

  • 几乎任何重要的I/O操作(包括网络和文件)都可能涉及到一系列相当大的异常处理程序,因为有太多东西可能出错。在I/O周围看到许多错误报告并不是反模式,但这可能是一个很好的代码气味。就像松树清香或新鲜出炉的面包一样。 :)


  • 1
    我为一个比我的回答更详细、更少对抗性的回答点了个赞。有时候我觉得人们需要一些激励;我们已经远离“ON ERROR RESUME NEXT”的日子了。 ;) - Aaronaught

    3
    你可以这样做,但是会失去精细度和逐个处理每个问题的能力,这并不是最佳实践。通常,你会首先处理不同类型的异常,就像你已经做的一样,然后如果需要对任何未处理的异常进行操作,可以在末尾添加Exception。完成后重新抛出以保留堆栈跟踪并让其向上冒泡。
    保留你所拥有的内容,在末尾添加一个基本情况:
    catch (ArgumentNullException e) { ... }
    catch (EncoderFallbackException e)  { ... }
    catch (SocketException e) { ... }
    catch (ArgumentOutOfRangeException e)  { ... }
    catch (ObjectDisposedException e) { ... }
    catch (Exception e)
    {
        // not handled by above exceptions, do something if needed
        throw; // after doing what you need, re-throw it
    }
    

    3

    这样做确实可以捕获所有异常,但这可能是不好的实践。有一些异常会被隐藏或歪曲,这将使测试和调试您的应用程序变得更加困难。


    2
    正如之前的评论所建议的那样,您应该使用特定于异常的catch块捕获所有“已知”异常,并使用catch (Exception ex) { ... }catch { ... }捕获其他未知或更合适的“不匹配”异常。
    然而,根据您的代码指定,您正在以相同的方式处理所有类型的异常。因此,在这种特殊情况下,输出(您称之为“容错能力”)将是相同的,但性能将会提高(因为运行时无需将异常类型与给定类型列表进行比较)。
    故事的寓意:在这种特殊情况下,请使用单个通用try-catch。但一般情况下,避免这样的习惯。

    1

    今天早上我刚看了一篇关于这个话题的博客文章

    你可以像在文章评论中提到的那样做:

    catch(Exception e){
        if(    e is ArgumentNullException 
            || e is EncoderFallbackException 
            || e is ...whatever 
        ){
          OnNetworkEvents eventArgs = new OnNetworkEvents("Network Unavailable", e.Message);
          OnUpdateNetworkStatusMessage(this, eventArgs);
        } else { throw; } 
    }
    

    除了将代码复制到相同的异常处理程序中,确切地在多个显式异常类型上执行完全相同的操作,没有其他避免方法。 - Mark
    1
    通常情况下,这是不好的。除非我有很多重复的事件处理程序,否则我永远不会这样做。如果处理程序不完全相同,这可能会让某人走上错误的道路。 - Mark
    1
    @新手:通常情况下,我会尽量避免运行时类型检查/向下转换,除非有充分的理由不这样做。语言本身支持等效行为的事实表明,没有令人信服的理由。特别是考虑到我怀疑大多数OP的异常应该发布不同的消息,而不仅仅是“网络不可用”。 - Greg D
    在这些错误上,用户无法做任何事情,因此我显示相同的错误消息,但我还传递了e.Message,以便如果其他人正在使用代码,则可以显示我的错误消息或e.Message。 - iTEgg
    @Greg D:他可能只是想捕获所有异常,将它们包装在内部异常中,然后作为自己选择的新异常抛出,或者可能将它们传递给某个错误处理程序日志以供用户干预。我们并不都编写相同的程序。不同的程序有不同的要求。(向最终用户报告网络错误是引起大量支持电话和“错误”投诉的好方法。特别是如果他们只是试图将文件保存到网络驱动器。) - Matthew Whited
    显示剩余2条评论

    1

    这样做是可行的,但也会捕获“空引用”异常和任何其他随机抛出的异常。


    1

    是的,它会“工作”,但不会做相同的事情,因为它会捕获所有异常而不仅仅是特定的。

    通常这不是你想要的,因为一般的规则是仅捕获你知道如何处理的异常。有一些情况下需要这样的 catch-all 来重定向异常(比如跨线程边界或在异步调用中)或者在出现任何错误时执行一些清理工作(但然后你通常会在处理完后重新抛出异常)。


    1

    它将捕获所有从Exception类继承的异常。如果它们都以相同方式处理,你可以这样做,但是你不应该这样做,因为你无法以相同的方式处理所有从Exception继承的异常。


    0

    虽然你可以这样做,但我认为这是不好的实践方式,也不是你想要构建代码的方式。

    根据异常的类型,你可能需要采取不同的处理方式。无效的IP地址与硬件错误是不同的问题,还有一些错误可能需要通过委托通知UI或使用log4net记录在某个地方。

    但这只是我的看法,我远非专家 - 所以你可以权衡一下。


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