C#中最好的替代"On Error Resume Next"是什么?

16
如果我在我的C#代码中使用空的catch块,是否相当于VB.NET中的“On Error Resume Next”语句?
try
{
    C# code;
}

catch(exception)
{
}

我提出这个问题的原因是因为我需要将VB.NET代码转换为C#,而旧代码中有大约200个"On Error Resume Next"语句,尽管我在新代码中使用了适当的try {} catch {},但是否有更好的替代方案呢?


17
"仅使用On Error Resume Next,没有其他替代方案,并非是无意的疏忽......为什么可能需要它?如果您解释了想法背后的动机,我确信这里有人可以给您提供更好的解决方案。" - Cody Gray
3
即使他/她将代码保留为VB.NET,用更结构化的异常处理(或不使用任何异常处理)替换"On Error Goto Next"仍然是个好主意。 - Cody Gray
5
@Cody,将错误处理进行改进是一种进步,但您需要评估需要多长时间(成本如何)以及代码是否需要重大修改。如果代码能正常工作且不需要更改,则可能有更好的事情可以利用这段时间做。 - MarkJ
2
@MarkJ:好的,我同意你的观点。我只是提供了我认为是另一方面有说服力的论点。这是你必须做出的设计决策之一。我应该坚持那些虽然效果不错但代码质量较差的代码,还是花时间改进它以获得长期利益。我假设进行转换的原因是因为现有代码并不能完全正常工作。其他情况可能会有所不同,你的建议值得考虑。 - Cody Gray
2
@CodyGray:我可以想到很多合理的理由来使用“继续下一个”功能。比如说你要删除一堆文件,但是你知道有些文件可能已经不存在了,如果你错过了其中一个文件也没关系(例如,如果它正在被使用)。在这种情况下,为什么需要为每个操作都使用“try...catch”呢? - Jonathan Wood
显示剩余5条评论
12个回答

20
我发现VB程序员常常出于(不好的)习惯在代码中滥用许多On Error Resume Next语句。我的建议是从没有抑制异常开始,看看到底会有哪些问题出现。你可能认为出现的问题没有那么多。相反,你进行的回归测试越多,就越好;可能有一些边缘情况只有在忽略错误时才能正常工作。
最终,你需要决定一个错误处理策略,无论是优雅地解除许多try/catch块内部的异常,还是让错误上浮到顶层处理程序(这两种策略都很有用)。
如果你最终不得不抑制一些异常以满足截止日期,至少记录下这些异常,以便下一个开发人员在处理你的代码时不会被空的try/catch语句弄伤。

8

虽然有时这是可以接受的,但通常它表明存在代码异味。如果您百分之百确定要吞掉已发生的异常,可以按照您的方式执行,但通常如果抛出异常,则应该采取某些行动。

通常情况下,您可以通过精心设计的代码实现相同的结果。如果您目前遇到了特定的错误,请将其添加到问题中,但如果您只是出于好奇而问,那么没有等价物,这是一件好事。


8
尽管On Error Resume Next被滥用的情况比正常使用的情况更多,但在VB.NET中有些地方使用它仍然很有帮助。考虑一个程序,它为大量的Excel属性分配值,例如默认打印机参数--Excel中有无数的打印机参数。较新版本的Excel可能具有较早版本不支持的属性,而确定每个版本支持哪些属性并不容易。如果属性存在,则程序应该赋值,但如果使用较早版本的Excel,则忽略该属性。在VB.NET中做到这一点的“正确”方法是确定每个Excel版本支持哪些打印机属性,读取正在使用的版本,仅分配在该版本中实现的属性。这需要大量的研究和一些代码,但好处很少。On Error Resume Next将是一个更实际的解决方案。不幸的是,我现在正面临这个问题。我要尝试的解决方法是编写一个子例程,只是将一个值分配给另一个值,忽略错误。我将调用这个子例程来代替每个赋值语句。这不太糟糕,但也不是那么好。

7
"On Error Resume Next" 允许进行“内联错误处理”,这是VB中专家级别的错误处理。其概念是逐行处理错误,根据错误执行操作或在有益时忽略错误 - 但按照编写顺序运行代码而不使用代码跳转。
不幸的是,许多新手使用"On Error Resume Next"来隐藏他们的能力不足或出于懒惰而忽略所有错误,try/catch 是块级错误处理,在前-.NET时代是通过设计和实现中级别的。
在VB.NET中使用"On Error Resume Next"的问题在于它会在每行执行代码时加载err对象,因此比try/catch慢。我有点担心这个论坛会检查并推广一个愚蠢的答案,声称使用On Error Resume Next是一种坏习惯和代码垃圾。这是一个C#论坛;它真的应该用于C#程序员攻击他们不熟悉的另一种语言吗?

https://msdn.microsoft.com/en-us/library/aa242093(v=vs.60).aspx

有人说,没有真正的VB经验的中级C#程序员不应该因为他们对另一种“Microsoft Net”语言的奇怪鄙视而试图让C#变得简化和功能有限。请看以下代码:

//-Pull xml from file and dynamically create a dataset.
 string strXML = File.ReadAllText(@"SomeFilePath.xml");
 StringReader sr = new StringReader(strXML);
 DataSet dsXML = new DataSet();
 dsXML.ReadXml(sr);

string str1 = dsXML.Tables["Table1"].Rows[0]["Field1"].ToString();
string str2 = dsXML.Tables["Table2"].Rows[0]["Field2"].ToString();
string str3 = dsXML.Tables["Table3"].Rows[0]["Field3"].ToString();
string str4 = dsXML.Tables["Table4"].Rows[0]["Field4"].ToString();
string str5 = dsXML.Tables["Table5"].Rows[0]["Field5"].ToString();

如果xml通常具有Field3的值,但有时没有,我将会收到一个烦人的错误,说表格中不包含该字段。但是我并不在乎它是否存在,因为这不是必需数据。在这种情况下,ON Error Resume Next将允许我忽略错误,我就不必编写每行代码来设置变量,并使用Contains方法检查表格、行和列组合的存在性。这只是一个小例子;我可能会从大型文件中提取数千个表格、列、行组合。此外,请假设字符串变量必须以这种方式填充。这是未处理的代码,会出现问题。
考虑VB.NET和ON Error Resume Next实现:
On Error Resume Next

        'Pull Xml from file And dynamically create a dataset.
        Dim strXML As String = File.ReadAllText("SomeFilePath.xml")
        Dim srXmL As StringReader = New StringReader(strXML)
        Dim dsXML As DataSet = New DataSet()
        dsXML.ReadXml(srXmL)

        'Any error above will kill processing. I can ignore the first two errors and only need to worry about dataset loading the XML.
        If Err.Number <> 0 Then
            MsgBox(Err.Number & Space(1) & Err.Description)
            Exit Sub 'Or Function
        End If

        Dim str1 As String = dsXML.Tables("Table1").Rows(1)("Field1").ToString()
        Dim str2 As String = dsXML.Tables("Table2").Rows(2)("Field2").ToString()
        Dim str3 As String = dsXML.Tables("Table3").Rows(3)("Field3").ToString()
        Dim str4 As String = dsXML.Tables("Table4").Rows(4)("Field4").ToString()

在上面的代码中,只需要处理一种可能的错误情况;即使在第三个错误被处理之前有两个错误。RAD开发需要使用On Error Resume Next。C#是我的语言选择,但由于许多原因它并不像VB那样是一种RAD语言。我希望所有程序员意识到,几种主要的语言(例如C)只是运行而不会在未处理的错误上停止执行;开发人员需要在他们认为必要的地方检查这些错误。在Microsoft世界中,On Error Resume Next是最接近该范例的东西。
幸运的是,.NET确实提供了许多高级选择来处理这些情况;我提到了Contains。因此,在C#中,您必须增强对语言的知识水平,并根据C#语言规范正确处理此类问题。考虑一个解决方案,用于处理可能包含令人讨厌的丢弃错误的大块重复代码:
try
            {
                if (!File.Exists(@"SomeFilePath.xml")) { throw new Exception("XML File Was Not Found!"); }
                string strXML = File.ReadAllText(@"SomeFilePath.xml");
                StringReader sr = new StringReader(strXML);
                DataSet dsXML = new DataSet();
                dsXML.ReadXml(sr);

                Func<string, string, int, string> GetFieldValue = (t, f, x) => (dsXML.Tables[t].Columns.Contains(f) && dsXML.Tables[t].Rows.Count >= x + 1) ? dsXML.Tables[t].Rows[x][f].ToString() : "";

                //-Load data from dynamically created dataset into strings.
                string str1 = GetFieldValue("Table1", "Field1", 0);
                string str2 = GetFieldValue("Table2", "Field2", 0);
                string str3 = GetFieldValue("Table3", "Field3", 0);
                //-And so on.

            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            } 

尽管在try/catch块中,lambda函数仍然检查从动态填充的xml数据集中提取的每个表、行、列组合的存在性。这可以逐行检查,但会需要大量冗余代码(虽然我们有相同数量的执行代码,但维护所需的编写代码要少得多)。不幸的是,这可能被认为是“单行函数”的另一种不良实践。我在lambda和匿名函数的情况下打破了这个规则。
自从.NET提供了许多检查对象状态的方法后,对于VB专家来说,On Error Resume Next并不像在.NET之前那样重要,但还是很好用;特别是当你编写的东西如果不快速粗略地编码会浪费时间时。对于Java转换为C#的你们来说,加入Microsoft的世界吧,不要再假装如果有10000个中级Java和C#程序员这么说,那就一定是真的,因为如果任何一个顶级Microsoft大师(例如那些创建VB语言和.NET的人之一)在它们开发.NET本身过程中明显与你相矛盾,那就是错误的,你看起来很愚蠢。我想在C#、VB、F#和任何其他需要使用的语言中获得所有的功能。C#很优雅,但VB由于拥有更长的历史而更加完善,但它们都做“同样的事情”并使用相同的对象。请好好学习它们两个,或者请不要在比较对话中评论它们中的任何一个;对于我们这些从上世纪九十年代初以来一直在高水平使用Microsoft技术的人来说,这是令人作呕的。

6
您需要逐个分析每个On Error Resume Next语句并看看它们的目的是什么。有些可能只是懒散的代码,但在Visual Basic 6.0代码中使用On Error Resume Next也有合法的理由。
以下是在Visual Basic 6.0代码中使用On Error Resume Next的一些示例:
  • 检查Visual Basic 6.0集合中是否存在给定键。唯一的方法是通过键访问元素,并处理在不存在键时引发的错误。转换为.NET时,您可以通过检查键的存在来替换此方法。

  • 将字符串解析为整数。在.NET中,您可以使用TryParse


仅在这些情况下使用 On Error Resume Next 是同样糟糕的。On Error Resume Next 存在的原因是有其它语句 - IFs 来捕获单个错误类型,而 On Error GoTo 0 则用于恢复错误引发而不是跳过所有错误。 - Mike
7
@Mike:我不太确定你在这里想要表达什么。Joe的回答明确澄清了,你需要“处理引发的错误,如果键不存在”。我几乎只在VB 6中使用“On Error Resume Next”来实现自己的错误处理程序。我认为我们都是在说同一件事情。如果你明智地使用它,这个语句在VB 6中有其存在的意义,但在VB.NET中则完全没有(因为引入了更优秀的“try”/“catch”语法)。 - Cody Gray
@Cody Gray - 我同意。大约10年前,我曾在一个大型产品团队工作过,标准政策是在我们的VB代码中包含On Error Resume Next。然而,如果您的代码在例程执行时没有不断测试错误条件,您将在代码审查中受到严厉批评。这导致了一些非常冗长的代码,但是可以编写处理异常情况的可靠代码。 - Tim M.
@Tim:最荒谬的情况是在展示“打开/保存”公共对话框时必须这样做。如果用户取消了操作,它会引发一个错误,您必须适当地捕获和处理该错误。我从来不确定为什么空的FileName值不足以作为指示器…… - Cody Gray
@Cody Gray - 我想说的基本上是一样的...只是想详细说明如何处理VB中的错误。 - Mike

6

不,它们不一样。

使用 On Error Resume Next 时,如果出现错误,VB 会跳到下一行。而使用 try/catch,在发生错误(异常)时,执行会跳转到 catch 块。


2
我想你可以将每个可能会出错的语句都包装在自己的try/empty catch中,以获得类似的效果,但那将是一些恶意代码。 - Tim M.
@Tim:哈哈,只要我不需要维护那段代码太长时间就好了! - Jonathan Wood
太好了。完全正确。 - as9876

2
使用"On Error Resume Next"不是处理错误的好方法(当然,这是我的个人意见,但似乎大多数开发人员都同意我的观点)。正如其他人在之前的帖子中建议您使用Try...Catch...Finally(无论是在VB.NET还是C#中)。
这是一个非常聪明的错误处理选项,但它也允许您对错误什么也不做(一个空的catch块) :) 我建议您将可能导致错误的每一行代码放在单独的Try...Catch Block中,这样如果出现错误时,您就有机会做任何您想做的事情。 程序员们,愉快的编码 :)

2

正如@Tim Medora所说,编码时应该努力避免使用这种方法。然而,在某些情况下,这是有用的,可以模拟这种行为。以下是一个函数及其使用示例。(请注意,一些代码元素是使用C#6编写的)

    /// <summary>
    /// Execute each of the specified action, and if the action is failed, go and executes the next action.
    /// </summary>
    /// <param name="actions">The actions.</param>
    public static void OnErrorResumeNext(params Action[] actions)
    {
        OnErrorResumeNext(actions: actions, returnExceptions: false);
    }

    /// <summary>
    /// Execute each of the specified action, and if the action is failed go and executes the next action.
    /// </summary>
    /// <param name="returnExceptions">if set to <c>true</c> return list of exceptions that were thrown by the actions that were executed.</param>
    /// <param name="putNullWhenNoExceptionIsThrown">if set to <c>true</c> and <paramref name="returnExceptions"/> is also <c>true</c>, put <c>null</c> value in the returned list of exceptions for each action that did not threw an exception.</param>
    /// <param name="actions">The actions.</param>
    /// <returns>List of exceptions that were thrown when executing the actions.</returns>
    /// <remarks>
    /// If you set <paramref name="returnExceptions"/> to <c>true</c>, it is possible to get exception thrown when trying to add exception to the list. 
    /// Note that this exception is not handled!
    /// </remarks>
    public static Exception[] OnErrorResumeNext(bool returnExceptions = false, bool putNullWhenNoExceptionIsThrown = false, params Action[] actions)
    {
        var exceptions = returnExceptions ? new ArrayList() : null;
        foreach (var action in actions)
        {
            Exception exp = null;
            try { action.Invoke(); }
            catch (Exception ex) { if(returnExceptions) { exp = ex; } }

            if (exp != null || putNullWhenNoExceptionIsThrown) { exceptions.Add(exp); }
        }
        return (Exception[])(exceptions?.ToArray(typeof(Exception)));
    } 

举个例子,不要写成:

        var a = 19;
        var b = 0;
        var d = 0;
        try { a = a / b; } catch { }
        try { d = a + 5 / b; } catch { }
        try { d = (a + 5) / (b + 1); } catch { }

您可以:

            var a = 19;
            var b = 0;
            var d = 0;
            OnErrorResumeNext(
                () =>{a = a / b;},
                () =>{d = a + 5 / b;},
                () =>{d = (a + 5) / (b + 1);}
            );

1
我认为那些发明“On Error Resume Next”的人在创建它时确实有些想法。 回答你的问题是,没有类似于这种构造的C#等价物。 在C#和.Net中,我们有很多函数非常渴望关心和注意,但是在不断地迎合每个人的“异常行为”之后,会变得很疲倦。当几乎所有东西都可能引发异常时,这个词本身就有点失去了意义。 如果数百万项中有几千个例外情况,你应该怎么办?Resume Next可能是其中一个方便的答案。

1

“On error resume next”在.NET中的正确替代方法是使用Try___方法。在Visual Basic 6.0中,要查找集合中是否存在某个键,必须手动搜索(非常缓慢),或者尝试索引它并捕获任何错误,如果不存在则会出现错误。在VB.NET中,字典对象(这是旧集合的改进版本)支持TryGetValue方法,它将指示获取值的尝试是否成功,如果没有成功,则不会导致错误。许多其他.NET对象支持类似的功能。有一些方法应该具有“try”等效方法,但实际上没有(例如Control.BeginInvoke),但它们很少,逐个包装它们在Try/Catch中也不太繁琐。


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