关于C#:在foreach循环中使用try-catch块的最佳实践是什么?


Which is the best practice to use try - catch blocks with foreach loop?

关于性能,使用try catch块的最佳实践是什么?

1
2
3
4
5
6
7
8
foreach (var one in all)
{
    try
    {
        //do something
    }
    catch { }
}

1
2
3
4
5
6
7
8
try
{
    foreach (var one in all)
    {
        // do something
    }
}
catch { }


公平不是硬性和快速的规则,而是情境性的。

这取决于您是希望在其中一个项引起问题时停止整个循环,还是只捕获该单个问题并继续。

例如,如果您正在向人们发送电子邮件,那么在发送其中一个邮件时,您不会希望停止处理,但是如果您正在管理一组数据库事务,并且在其中任何一个事务失败时需要回滚,那么在出现异常/问题时,可能更希望停止处理?


在这两种情况下,性能可能都相同(但如果您想确定的话,可以运行一些测试)。每次通过循环时,异常检查仍在进行,只是在被捕获时跳到其他地方。

不过,这种行为是不同的。

在第一个示例中,一个项目上的错误将被捕获,其余项目的循环将继续。

在第二种情况下,一旦遇到错误,循环的其余部分将永远不会执行。


根据要求,这是我最酷的答案。有趣的部分将在最后,所以如果你已经知道什么是尝试捕捉,请随时滚动。(抱歉,部分偏离主题)好的。

让我们从回答一般的Try-Catch概念开始。好的。

为什么?因为这个问题表明缺乏充分的知识如何以及何时使用这个特性。好的。什么是Try-Catch?或者更确切地说,什么是"尝试捕捉"最终。

(这一章也被称为:你为什么还没有用谷歌来了解它?)好的。

  • Try-潜在的不稳定代码,这意味着您应该移动所有它的稳定部分。总是执行,但没有保证完成的。好的。

  • catch-这里您放置的代码旨在纠正在Try部分发生。只有在发生异常时才执行试块。好的。

  • 最后——它的第三部分和最后一部分,在某些语言中可能没有。存在。它总是被执行。通常用于释放内存并关闭I/O流。好的。

  • 通常,Try-Catch是一种将可能不稳定的代码与程序其余部分分离的方法。在机器语言方面,它可以缩短为将所有处理器寄存器的值放在堆栈上,以避免它们损坏,然后通知环境忽略执行错误,因为它们将由代码手动处理。好的。使用Try-Catch块的最佳实践是什么?

    根本不用。用try-catch覆盖代码意味着您期望它失败。为什么代码失败?因为它写得不好。对于性能和质量而言,编写不需要尝试捕获就可以安全工作的代码要好得多。好的。

    有时,特别是在使用第三方代码时,Try-Catch是最简单和最可靠的选项,但在您自己的代码上使用Try-Catch的大多数时间表明存在设计问题。好的。

    实例:好的。

  • 数据分析-在数据分析中使用try catch非常非常糟糕。有很多方法可以安全地分析最奇怪的数据。其中最丑的是正则表达式方法(有问题吗?使用regexp,问题喜欢复数)。字符串到int转换失败?首先检查您的数据,.NET甚至提供了类似于typarse的方法。好的。

  • 除以零,精度问题,数值溢出-不要用try-catch覆盖它,而不是升级代码。算术编码应该从好的数学方程开始。当然,你可以大量修改数学方程式运行得更快(例如0x5f375a86),但您仍然需要良好的数学开始。好的。

  • 列表索引越界、堆栈溢出、分段错误,令人心碎的——在代码设计中你有更大的错误。那些错误不应该发生在运行在健康的环境。所有的代码都有一个简单的错误确保索引(内存地址)在预期边界内。好的。

  • I/O错误-在尝试使用流(内存、文件、网络),第一步是检查流是否存在(非空,文件存在,连接打开)。然后检查流是否正确-is你的索引的大小?流准备好使用了吗?它的队列/缓冲区容量足够容纳您的数据吗?所有这些都可以在没有单次尝试捕获。尤其是在框架(.net)下工作时,Java等)。好的。

    当然,仍然存在意外访问问题-RAT咀嚼你的网线,硬盘融化。这里的用法尝试捕获不仅可以被原谅,而且应该发生。尽管如此,它仍然需要以适当的方式完成,例如这个文件的例子。您不应该将整个流操作代码放在try catch中,而是使用内置方法检查其状态。好的。

  • 坏的外部代码-当你开始使用糟糕的代码库时,没有任何纠正方法(欢迎来到企业界)Try-Catch通常是保护其余代码的唯一方法。但是还同样,只有直接危险的代码(呼叫恐怖写得不好的库中的函数)应该放在try catch中。好的。

  • 所以你应该在什么时候使用"试抓",什么时候不应该?

    它可以用非常简单的问题来回答。好的。

    我可以更正不需要try catch的代码吗?好的。

    对?然后放弃那个尝试捕获并修复代码。好的。

    不?然后将不稳定的部分打包到try catch中,并提供良好的错误处理。好的。如何处理catch中的异常?

    第一步是知道会发生什么类型的异常。现代环境提供了在类中隔离异常的简单方法。尽可能捕获最具体的异常。做I/O?捕捉I/O。做数学?抓住算术题。好的。

    用户应该知道什么?好的。

    只有用户可以控制的内容:好的。

    • 网络错误-检查电缆。
    • 文件I/O错误-格式C:。
    • 内存不足-升级。

    其他异常只会告诉用户代码写得有多糟糕,所以请坚持使用神秘的内部错误。好的。尝试在循环中捕获还是在循环外捕获?

    正如许多人所说,这个问题没有明确的答案。这完全取决于您提交的代码。好的。

    一般规则可以是:原子任务,每个迭代都是独立的-尝试在循环内部捕获。链计算,每个迭代都依赖于前面的迭代-在循环中尝试catch。好的。

    for和foreach有什么不同?好的。

    foreach循环不保证顺序执行。听起来很奇怪,几乎从未发生过,但仍有可能。如果将foreach用于创建它的任务(数据集操作),那么您可能希望将try catch放在它周围。但正如所解释的,你应该尽量不要用"尝试捕捉"来捕捉自己。好的。有趣的部分!

    这篇文章的真正原因只是你几行,亲爱的读者们!好的。

    根据弗朗辛·德格罗德·泰勒的要求,我会写更多有趣的部分。请记住,正如约阿希姆·伊萨克森所注意到的,乍一看这很奇怪。好的。

    尽管这一部分将重点放在.NET上,但它可以应用于其他JIT编译器,甚至部分应用于程序集。好的。

    所以…试一试绕环是如何加速的?这完全没有道理!错误处理意味着额外的计算!好的。

    检查关于它的stackoverflow问题:试着加速我的代码?你可以在那里阅读.NET的特定内容,这里我将重点介绍如何滥用它。记住这个问题是从2012年开始的,所以它也可以被"纠正"(它不是一个bug,它是一个特性!)在当前的.NET版本中。好的。

    如上所述,Try-Catch将代码段与其余部分分离。分离过程的工作方式与方法类似,因此,您也可以在分离方法中放置包含大量计算的循环,而不是Try-Catch。好的。

    分离代码如何加快速度?寄存器。网络比HDD慢,HDD比RAM慢,RAM比超快的CPU缓存慢。还有CPU寄存器,它们嘲笑缓存的速度有多慢。好的。

    分离代码通常意味着释放所有通用寄存器——这正是Try-Catch所做的。或者更确切地说,JIT正在做的是由于尝试捕获。好的。

    JIT最突出的缺陷是缺乏预感。它看到循环,编译循环。当它最终注意到循环将执行数千次并吹嘘计算,这使得CPU发出吱吱声时,释放寄存器为时已晚。所以循环中的代码必须编译为使用寄存器的剩余部分。好的。

    即使是一个额外的寄存器也能极大地提高性能。每个内存访问都非常长,这意味着CPU可以在相当长的时间内不使用。尽管现在我们已经失去了正常的执行、可爱的管道和预取功能,但仍然有一些阻塞操作迫使代码停止。好的。

    现在让我们来谈谈为什么与X64相比,X86很烂,而且是垃圾。链接SE问题中的Try-Catch速度增益在为x64编译时没有发生,为什么?好的。

    因为一开始没有速度提升。所有这些都是由蹩脚的JIT输出造成的速度损失(经典编译器没有这个问题)。尝试捕获纠正的JIT行为主要是意外的。好的。

    为某些任务创建了x86寄存器。X64体系结构使它们的大小增加了一倍,但它仍然不能改变这样一个事实:在执行循环时,必须牺牲CX,其他寄存器(可怜的孤立BX除外)也有类似的情况。好的。

    那为什么X64这么棒?它拥有8个额外的64位宽的寄存器,没有任何特定的用途。你可以用它们做任何事。不仅理论上和X88寄存器一样,实际上是为了任何东西。8个64位寄存器意味着8个64位变量直接存储在CPU寄存器中,而不是RAM中,不存在任何数学问题(这通常需要AX和DX来获得结果)。64位意味着什么?x86可以将int放入寄存器,x64可以长入寄存器。如果数学块将有空的寄存器,那么它可以在不接触内存的情况下完成大部分工作。这就是真正的速度提升。好的。

    但这不是结局!您还可以滥用缓存。缓存越接近CPU,速度越快,但也会越小(成本和物理大小都是限制)。如果您将优化数据集以一次填充缓存,例如日期块大小为一半的l1,则将另一半保留为代码和任何CPU在缓存中发现的必要内容(除非您使用汇编,否则无法真正优化它,在高级语言中必须使用"guestimate")。通常每个(物理)核心都有自己的一级内存,这意味着您可以一次处理几个缓存的块(但创建线程的开销并不总是值得的)。好的。

    值得一提的是,旧Pascal/Delphi在32位处理器中使用了"16位恐龙",它们在几个重要的功能中(比C/C++中的32位慢了两倍)。所以爱你的CPU寄存器,甚至可怜的老BX。他们非常感激。好的。

    再加一点,因为这已经变得相当疯狂了,为什么C/Y/Java可以比原生代码慢和快?JIT是答案,框架代码(IL)被翻译成机器语言,这意味着长计算块将作为C/C++的本机代码来执行。但是请记住,你可以很容易地使用.NET中的本地组件(在爪哇你可以通过尝试来疯狂)。对于足够复杂的计算,您可以通过本地代码的速度增益来覆盖切换托管本地模式的开销(并且本地代码可以通过asm注入进行增强)。好的。好啊。


    对于大多数应用程序,性能差异可以忽略不计。

    然而,问题是,如果循环中的其余项失败,您是否希望继续处理其中的其余项。如果是,使用前臂内侧,否则使用单个外侧环。


    你应该看你想要什么样的行为,而不是看你的表现。考虑是否希望在发生异常时能够继续循环,以及在何处进行任何清理。在某些情况下,您可能希望捕获循环内外的异常。

    try...catch对性能的影响很小。您所做的大多数事情实际上都会导致异常,这比设置捕获异常所需的时间要长得多,因此在大多数情况下,性能差异可以忽略不计。

    在任何情况下,如果有一个可测量的性能差异,那么在循环中您将做很少的工作。在这种情况下,您通常希望try...catch在循环之外,因为循环内没有任何需要清理的内容。


    在第一个示例中,循环在捕获发生后继续(除非您告诉它中断)。也许你会希望在需要将错误数据收集到一个列表(在catch中要做的事情)中的情况下使用它,以便在结尾处向某人发送电子邮件,并且不希望在出现问题时停止整个过程。

    在第二个示例中,如果希望它在发生错误时立即命中中断点,以便进行分析,它将阻止循环的其余部分发生。


    这取决于您想要实现什么:)第一个函数将尝试循环中的每个函数,即使其中一个失败,其余的函数也将运行…第二个错误即使出现一个错误也会中止整个循环…