我在C#中怀念Visual Basic的"On Error Resume Next"语句。现在该如何处理错误?

13

在Visual Basic中,我只需在程序开头写上On Error Resume Next,整个项目中的错误都会被抑制。

在C#中,我非常想念这个功能。每个单独的过程通常需要使用try-catch来处理错误,这不仅非常耗时,而且会带来不良影响。如果遇到错误,即使已经处理了,代码也不能从发生错误的地方继续。有了On Error Resume Next,代码将从错误的地方继续执行,只是跳过导致错误的函数调用。

我还不太熟悉C#,但是也许C#中有比原始的try-catch更好的错误处理方法。

我还希望我的错误消息中包含出错模块或函数的名称以及行号。据我所知,Exception类不提供这些功能。有没有任何想法(当然要管理好,不涉及自己应用程序中的任何进程类)?

如何处理大型项目中的错误?我希望不必在每个方法中添加try-catch。在某种程度上,C#会抛出很多错误——这似乎是这种语言的典型特征。

我找到的解决方案来重新解决我的几个问题:

public partial class Form1 : Form
{

    public Form1()
    {
        InitializeComponent();
    }

    [STAThread]
    static void Main()
    {
      Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); //setup global error handler
      Application.Run(new Form1());
    }

    private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
    {   
            MessageBox.Show("Unhandled exception: " + e.Exception.ToString()); //get all error information with line and procedure call
            Environment.Exit(e.Exception.GetHashCode()); //return the error number to the system and exit the application
    }

    private void button1_Click(object sender, EventArgs e)
    {
        string s = ""; s.Substring(1, 5); //Produce an error
    }

   }

3
异常情况是有意义的,简单地忽略它们会导致混乱。 - Cole Tobin
2
这可能是一个不好的迹象,说明你需要一个全局的try/catch。大部分的代码不应该产生错误,只有在极少数情况下才会抛出异常。 - Prescott
10
不,你应该找出那些错误并修复它们。你所经历的情况不是典型的。你可能需要开始编写单元测试... - Jon Skeet
13
这其实是一个很好的问题。它表明提问者目前可能在做各种错误的事情,但它阐述了情况非常清楚,回答应该能够为任何处于同样境地的人提供启示。在我看来,一个表现不佳的程序员提出的好问题比一个优秀程序员提出的糟糕问题更有用 :) - Jon Skeet
2
如果你所说的“垃圾”被跳过了,那么你为什么要调用它呢?Feedwall,我直截了当地告诉你 - 像你描述的这样的项目,你的应用程序没有很好地工作。数据被破坏了。你很幸运没有遭受任何毁灭性的失败(至少你知道的)。相信我们 - 没有那个指令,你将会产生更高质量的工作。 - Michael Petrotta
显示剩余6条评论
6个回答

32

如果继续忽略错误而像什么都没发生一样继续编程,那是一种非常糟糕的方式。

算不���账户的新余额?没事,我们只需将其存储为0。没人会知道的,对吧?

try/catch块实际上应该相对较少,因为真正可以从中恢复的错误相对较少。通常情况下,您应该在某些逻辑操作的顶部有一个try/catch块,这样如果它失败了,您就可以通知用户并继续其他完全独立的操作 - 或者根据您正在编写的应用程序类型完全终止应用程序。 (Web应用程序在此处是一个很好的例子:您可以失败请求,希望确保您没有恶意的持久性副作用,并继续处理其他请求。)

在您可以合法地期望能够恢复的位置,捕获那些特定的异常并适当地处理它们(例如,如果写入数据库失败,则回退到写入文件)。同样,这些情况也比较少见。如果您发现自己在每个方法(甚至每个类)中都编写一个try/catch块,那么您可能正在不适当地处理异常。

我还希望在我的错误消息中包含模块或函数名称以及出现错误的行号。据我所知,Exception类并没有提供这些功能。

是有的。堆栈跟踪显示了堆栈中每个框架的类型、方法和行号(如果可用)......当然还有一个��希望有用的)消息。哦,如果一个故障是由另一个故障引起的,则可能会有一个嵌套异常。

不知何故,C#总是在执行时抛出很多错误,这是语言典型的特点。

不,那只是表明您做错了。


1
查看抛出异常的 StackTraceMessage 属性,@feedwall(如果从调试器运行,则 Visual Studio 会显示)。四处搜索 - 子类化异常的其他属性中通常有有用的信息,而 InnerException 有时会包含嵌套异常。 - Michael Petrotta
1
@feedwall:你不应该考虑异常的“数字”。使用异常的“类型”,结合消息和堆栈跟踪,当然也要考虑。 - Jon Skeet
3
“@feedwall: “也许我可以用SendKeys自动关闭抛出的错误,并为用户按下“继续”按钮,但我不确定如何做到这一点。” 不不不!请务必阅读此处的所有答案-您绝对不应该想要抑制错误并像这样继续进行。由于您决定忽略糟糕的代码,使您的应用程序覆盖了好的数据而变成了坏的,因此用户将不会欣赏您的应用程序。” - Jon Skeet
2
不是有意冒犯,@feedwall,我只想说:你提出的建议非常、非常错误。不要这样做。你感到沮丧,有点失控。请放松一下,周末休息一下。 - Michael Petrotta
1
哦,他并没有对我们发脾气,Jon——很抱歉暗示了这一点。但是当我读到像“也许我可以通过SendKeys自动关闭抛出的错误,并按下‘继续’”这样的解决方案时,我想象一个人已经快要失去耐心了,正在长溃疡,准备把笔记本电脑扔出窗外。我曾经也有过这种经历,那不健康。最好还是离开一下。 - Michael Petrotta
显示剩余6条评论

18

不。

作为一名曾经的VB程序员,我可以向您保证:这是任何语言中添加的最糟糕和滥用的功能。下面是更好的想法:

  1. 编写不会出错的代码...除非真的发生了问题。这可能需要在做事情之前检查你的假设;很好,那就这样做。
  2. 只捕获您预期的问题;吞噬所有错误只会带来大量问题。

正如Jon已经指出的那样,通常不需要随处使用异常处理。通常,您只需让异常上升到更高的调用者,因为发生了某些不好的事情。当我确实有一个try时,更常见的是try/finally(而不是try/catch) - 使用usinglock等则是方便的特例。


5
不行,C#是不可能的(在任何其他语言中也不应该)。VB 中“On Error Resume Next”的真正用途是在代码的某些部分中进行错误处理,就像try/catch一样。您启用它,在检查Err.Number <> 0之后进行工作,并使用“On Error GoTo 0”还原错误流程,或重定向到跟随不同路径来处理错误或继续执行的标签“On Error GoTo someErrorCase:”。您必须独自学习编程或与不正确的人一起学习。忽略错误是一个坏习惯,而且更糟糕的是,只是按照编码进行。毕竟,错误是可能发生的。相信我,我曾经是VB程序员,当我停下来阅读最佳实践时,这是很有启发性的。还有,尝试使用“Option Explicit”也会更好,虽然声明所有变量可能听起来更麻烦,但它将使您对代码更加自信,因为类型检查将限制一些常见错误。此外,C#异常非常有用,包含了所有您可能需要的信息。如果您没有问题的异常本身,只需打开它并查看其内部异常(当为Web开发时,因为所有代码都处于更高级别,我总是会查看内部异常)。

1
@Michael Petrotta 感谢您纠正我的错别字和拼写。 - Ricardo Souza
+1 是为了实际解释该功能是如何发展的。我从未想过实际上有一种合理的方法来使用它。 - sleske

4

实际上,异常通常都不是经常发生的,只有偶尔会出现。当它们确实发生时,您需要知道它们发生了,并且要么处理它们,要么关闭您的进程。在大多数情况下,不处理异常会导致非常意外的结果。


2
你一直在错误地使用VB功能,而你很幸运不能像这样在C#中使用它。
使用VB功能时,您应该在每个可能导致错误的操作之后检查错误状态。正确使用时,代码量不比在每个可能导致错误的操作周围放置try...catch块少。
因此,如果您认为在C#中必须进行更多的错误处理,则以前进行的错误处理太少了。

0
"On Error Resume Next" 允许在 VB 中进行 "内联错误处理",这是专家级别的错误处理。其概念是逐行处理错误,根据错误执行操作或在有益时忽略错误 - 但按照编写代码的顺序运行代码,而不使用代码跳转。
不幸的是,许多新手使用 "On Error Resume Next" 来隐藏他们的能力不足或因懒惰而忽略所有错误,从而欺骗使用他们应用程序的人。Try/catch 是块级错误处理,在 .NET 之前的世界中是中间层次的设计和实现。
在 VB.NET 中使用 "On Error Resume Next" 的问题在于它会在每行执行代码时加载 err 对象,因此比 try/catch 更慢。

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"].ToStrin();
string str3 = dsXML.Tables["Table3"].Rows[0]["Field3"].ToStrin();
string str4 = dsXML.Tables["Table4"].Rows[0]["Field4"].ToString();
string str5 = dsXML.Tables["Table5"].Rows[0]["Field5"].ToString();

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

        Dim strXML As String = File.ReadAllText("SomeNonExistentFileCausingAnErrorCondition.xml")
        If String.IsNullOrEmpty(strXML) Then
            strXML = strSomeOtherValidXmlThatIUseWhenTheFileIsEmpty
        End If
        Dim srXmL As StringReader = New StringReader(strXML)
        Dim dsXML As DataSet = New DataSet()
        dsXML.ReadXml(srXmL)
        If Err.Number <> 0 Then
            MsgBox(Err.Number & Space(1) & Err.Description)
            Exit Sub
        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()

在上面的代码中,只需要处理一个可能的错误条件;即使加载文件时出现错误。On Error Resume Next实际上允许我按照预期恢复,这使我能够检查字符串条件并使用我的备用字符串(我非常清楚也可以检查文件的存在并避免文件错误,但如果它是一个没有任何内容的好文件,则strXML将是一个空字符串)。关键的错误已经处理,并且该方法已退出,因为加载的数据集对其后的处理至关重要(如果需要,可以忽略任何错误运行处理)。文件错误可以被忽略,就像我忽略它一样,或者我可以检查错误条件并记录它。
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之前那样重要,但仍然很好用;特别是当你编写的东西如果不快速而肮脏地编码就会浪费时间时。任何曾经在专家级别上使用过VB的人都不会声称On Error Resume Next(内联错误处理)是添加到语言中最糟糕的功能。然而,它已经被新手广泛滥用。


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