【问题标题】:C# try-catch-elseC# try-catch-else
【发布时间】:2009-07-24 12:47:22
【问题描述】:

从 Python 到 C# 的异常处理困扰着我的一件事是,在 C# 中似乎没有任何方法可以指定 else 子句。例如,在 Python 中我可以写这样的东西(注意,这只是一个例子。我不是在问什么是读取文件的最佳方式):

try
{
    reader = new StreamReader(path);
}
catch (Exception)
{
    // Uh oh something went wrong with opening the file for reading
}
else
{
    string line = reader.ReadLine();
    char character = line[30];
}

根据我在大多数 C# 代码中看到的情况,人们只会写以下内容:

try
{
    reader = new StreamReader(path);
    string line = reader.ReadLine();
    char character = line[30];
}
catch (Exception)
{
    // Uh oh something went wrong, but where?
}

这样做的问题是,我不想捕获超出范围的异常,因为文件中的第一行可能不包含超过 30 个字符。我只想捕获与读取文件流有关的异常。我可以在 C# 中使用任何类似的构造来实现相同的目的吗?

【问题讨论】:

  • 我很惊讶没有人想出带有 goto 的答案!
  • 捕获一般异常绝不是一个好主意,但您可以指定捕获的异常类型。
  • 我想我在最初的问题中没有说清楚,但我的意思是我不想通过捕获它们并让程序继续来掩盖 try {} 部分中的错误在路上。我希望它崩溃,以便我可以立即修复错误。同时也能够处理我无法控制的情况(即文件不存在或因为它位于网络驱动器上而无法访问它等等......)。大多数解决方案都建议捕获 IOException,但如果 StreamReader 抛出我可能没有预料到的其他类型的异常怎么办?
  • 马丁,如果你没有预料到,一般来说,你应该让它过去。只处理你理解的和你能处理的。 C# 为您提供了所有工具,让您可以随心所欲地处理您想要的事情,您必须制定策略。
  • 这个线程相当令人失望。没有一个答案提出了将 Python 中的 try/except/else 编写为 C# 中的 try/catch/else 的正确方法。

标签: c# exception-handling


【解决方案1】:

捕获特定类别的异常

try
{
    reader = new StreamReader(path);
    string line = reader.ReadLine();
    char character = line[30];
}
catch (IOException ex)
{
    // Uh oh something went wrong with I/O
}
catch (Exception ex)
{
    // Uh oh something else went wrong
    throw; // unless you're very sure what you're doing here.
}

当然,第二个捕获是可选的。而且由于您不知道发生了什么,因此吞下这个最普遍的例外是非常危险的。

【讨论】:

  • 我宁愿在阅读时单独尝试{...}catch(Exception){...}。对于未经检查的异常,您永远不知道 Reader 除了 IOException 之外还会抛出什么——现在或将来。
  • 在这种情况下,异常将通过调用者冒泡并在其他地方被捕获。当您准备好处理异常时,请始终处理它。
  • Tiberiu,“你永远不知道还有什么”与 char[30] 等中的 IndexOutOfRange 一起使用。除非有充分的理由,否则无需区别对待 ReadLine。但捕捉“剩余”可能会发生在更高的位置。
  • 问题具体是如何捕获(all)与读取文件流相关的异常(不一定限于IOException)。在这种情况下,您可以在 read 周围添加一个 try{},捕获所有异常,然后将它们打包到 ReadingStreamException 中并再次抛出它们。
  • 您真的非常想帮自己一个忙,并且从不在一般错误记录代码之外捕获+吞下一般异常。一旦出现你没有考虑到现有代码处理不正确的新问题,它必然会导致难以调试错误。
【解决方案2】:

你可以这样写:

bool success = false;
try {
    reader = new StreamReader(path);
    success = true;
}
catch(Exception) {
    // Uh oh something went wrong with opening the file for reading
}
finally {
    if(success) {
        string line = reader.ReadLine();    
        char character = line[30];
    }
}   

【讨论】:

  • 为什么要把它放在 finally 块中?
  • @ChieltenBrinke,我认为 Johan Kullbom 希望代码易于理解。通过在finally中添加行,我可以看到if子句下的代码属于try and catch。
  • 终于专为资源清理而设计。它不应该像那样在标签外使用。
  • 我认为这个问题的最佳答案是这个。
【解决方案3】:

你也可以嵌套你的 try 语句

【讨论】:

    【解决方案4】:

    捕获更具体的异常。

    try {
       reader = new StreamReader(path);
       string line = reader.ReadLine();
       char character = line[30];
    }
    catch(FileNotFoundException e) {
       // thrown by StreamReader constructor
    }
    catch(DirectoryNotFoundException e) {
       // thrown by StreamReader constructor
    }
    catch(IOException e) {
       // some other fatal IO error occured
    }
    

    此外,一般来说,尽可能处理最具体的异常并避免处理基本的System.Exception

    【讨论】:

      【解决方案5】:

      您可以有多个 catch 子句,每个子句都特定于您希望捕获的异常类型。因此,如果您只想捕获 IOExceptions,则可以将 catch 子句更改为:

      try
      {
          reader = new StreamReader(path);
          string line = reader.ReadLine();
          char character = line[30];
      }
      catch (IOException)
      {    
      }
      

      除了 IOException 之外的任何东西都会向上传播到调用堆栈。如果您还想处理其他异常,则可以添加多个异常子句,但您必须确保它们以最具体到最通用的顺序添加。例如:

      try
      {
          reader = new StreamReader(path);
          string line = reader.ReadLine();
          char character = line[30];
      }
      catch (IOException)
      {    
      }
      catch (Exception)
      {
      }
      

      【讨论】:

        【解决方案6】:

        更惯用的说法是,您可以使用using 语句将文件打开操作与其包含的数据完成的工作分开(并包括退出时的自动清理)

        try {
          using (reader = new StreamReader(path))
          {
            DoSomethingWith(reader);
          }
        } 
        catch(IOException ex)
        {
          // Log ex here
        }
        

        最好避免捕获所有可能的异常——比如那些告诉你运行时即将到期的异常。

        【讨论】:

        • Martin Sherburn 询问如何在 C# 中编写 try / catch / else,而不是如何正确编写文件打开器。
        【解决方案7】:

        在看到其他建议的解决方案后,这是我的方法:

        try {
            reader = new StreamReader(path);
        }
        catch(Exception ex) {
            // Uh oh something went wrong with opening the file stream
            MyOpeningFileStreamException newEx = new MyOpeningFileStreamException();
            newEx.InnerException = ex;
            throw(newEx);
        }
            string line = reader.ReadLine();
            char character = line[30];
        

        当然,只有当您对打开文件流所引发的任何异常感兴趣(此处作为示例)除了中的所有其他异常时,这样做才有意义应用程序。在应用程序的某个更高级别,您可以按照您认为合适的方式处理您的MyOpeningFileStreamException

        由于未经检查的异常,您永远无法 100% 确定在整个代码块中仅捕获 IOException 就足够了 - StreamReader 也可以决定抛出一些其他类型的异常,现在或在未来。

        【讨论】:

          【解决方案8】:

          如果你碰巧在一个循环中,那么你可以在 catch 块中放置一个 continue 语句。这将导致该块的剩余代码被跳过。

          如果您不在循环中,则无需在此级别捕获异常。让它将调用堆栈向上传播到一个知道如何处理它的 catch 块。您可以通过在当前级别消除整个 try/catch 框架来做到这一点。

          我也喜欢 Python 中的 try/except/else,也许有一天它们会被添加到 C# 中(就像多个返回值一样)。但是,如果您对异常的看法稍有不同,那么 else 块并不是绝对必要的。

          【讨论】:

            【解决方案9】:

            异常在 .NET 中的使用方式不同;它们仅用于特殊情况。

            事实上,你不应该捕获异常,除非你知道它的含义,并且实际上可以一些事情。

            【讨论】:

            • 该建议同样适用于 Python 和 .NET。
            • 这不能回答问题。
            • @CraigMcQueen:这就是你投反对票的原因吗?
            • 是的,就是这个原因。
            • @CraigMcQueen:谢谢 - 很高兴知道为什么会发生这些事情。
            【解决方案10】:

            你可以这样做:

            try
            {
                reader = new StreamReader(path);
            }
            catch (Exception)
            {
                // Uh oh something went wrong with opening the file for reading
            }
            
            string line = reader.ReadLine();
            char character = line[30];
            

            当然,您必须将reader 设置为正确的状态或将return 设置为方法之外。

            【讨论】:

            • 这只会捕获打开阅读器的错误,而不是实际阅读中的错误。
            • 阿德里安,你是对的,但文字说的不一样。
            • 评论说“打开文件进行阅读”,而不是实际阅读。
            • 这可能是最接近预期的答案。
            【解决方案11】:

            是否有任何类似的构造我可以在 C# 中使用 实现同样的目标?

            没有。

            使用“if”语句包装您的索引访问器,这是在性能和​​可读性方面的最佳解决方案。

            if (line.length > 30) {
               char character = line [30];
            } 
            

            【讨论】:

              【解决方案12】:

              我冒昧地对您的代码进行了一些改动,以展示一些要点。

              using 构造用于打开文件。如果抛出异常,即使您没有捕获异常,您也必须记住关闭文件。这可以使用try { } catch () { } finally { } 构造来完成,但using 指令对此要好得多。它保证当using 块的范围结束时,内部创建的变量将被释放。对于文件,这意味着它将被关闭。

              通过研究StreamReader 构造函数和ReadLine 方法的文档,您可以了解可能会抛出哪些异常。然后,您可以抓住那些您认为合适的人。请注意,记录在案的异常列表并不总是完整的。

              // May throw FileNotFoundException, DirectoryNotFoundException,
              // IOException and more.
              try {
                using (StreamReader streamReader = new StreamReader(path)) {
                  try {
                    String line;
                    // May throw IOException.
                    while ((line = streamReader.ReadLine()) != null) {
                      // May throw IndexOutOfRangeException.
                      Char c = line[30];
                      Console.WriteLine(c);
                    }
                  }
                  catch (IOException ex) {
                    Console.WriteLine("Error reading file: " + ex.Message);
                  }
                }
              }
              catch (FileNotFoundException ex) {
                Console.WriteLine("File does not exists: " + ex.Message);
              }
              catch (DirectoryNotFoundException ex) {
                Console.WriteLine("Invalid path: " + ex.Message);
              }
              catch (IOException ex) {
                Console.WriteLine("Error reading file: " + ex.Message);
              }
              

              【讨论】:

              • "(注意,这只是一个例子。我不是在问什么是读取文件的最佳方式)"
              • @NoctisSkytower:我不确定如何解释您的评论。我想它可以被解读为对我的回答的批评,但我花了一段时间将您引用的文字与问题的文字联系起来。有趣的是,您引用的文字是在我九年前写下答案后添加到问题中的。
              【解决方案13】:

              听起来你只想在第一件事成功的情况下做第二件事。并且可能捕获不同类别的异常是不合适的,例如,如果两个语句都可以抛出相同类别的异常。

              try
              {
                  reader1 = new StreamReader(path1);
                  // if we got this far, path 1 succeded, so try path2
                  try
                  {
                      reader2 = new StreamReader(path2);
              
                  }
                  catch (OIException ex)
                  {
                      // Uh oh something went wrong with opening the file2 for reading
                      // Nevertheless, have a look at file1. Its fine!
                  }
              }
              catch (OIException ex)
              {
                  // Uh oh something went wrong with opening the file1 for reading.
                  // So I didn't even try to open file2
              }
              

              【讨论】:

                【解决方案14】:

                在 C# 中可能没有对 try { ... } catch { ... } else { ... } 的任何原生支持,但如果您愿意承担使用变通方法的开销,那么下面显示的示例可能很有吸引力:

                using System;
                
                public class Test
                {
                    public static void Main()
                    {
                        Example("ksEE5A.exe");
                    }
                
                    public static char Example(string path) {
                        var reader = default(System.IO.StreamReader);
                        var line = default(string);
                        var character = default(char);
                        TryElse(
                            delegate {
                                Console.WriteLine("Trying to open StreamReader ...");
                                reader = new System.IO.StreamReader(path);
                            },
                            delegate {
                                Console.WriteLine("Success!");
                                line = reader.ReadLine();
                                character = line[30];
                            },
                            null,
                            new Case(typeof(NullReferenceException), error => {
                                Console.WriteLine("Something was null and should not have been.");
                                Console.WriteLine("The line variable could not cause this error.");
                            }),
                            new Case(typeof(System.IO.FileNotFoundException), error => {
                                Console.WriteLine("File could not be found:");
                                Console.WriteLine(path);
                            }),
                            new Case(typeof(Exception), error => {
                                Console.WriteLine("There was an error:");
                                Console.WriteLine(error);
                            }));
                        return character;
                    }
                
                    public static void TryElse(Action pyTry, Action pyElse, Action pyFinally, params Case[] pyExcept) {
                        if (pyElse != null && pyExcept.Length < 1) {
                            throw new ArgumentException(@"there must be exception handlers if else is specified", nameof(pyExcept));
                        }
                        var doElse = false;
                        var savedError = default(Exception);
                        try {
                            try {
                                pyTry();
                                doElse = true;
                            } catch (Exception error) {
                                savedError = error;
                                foreach (var handler in pyExcept) {
                                    if (handler.IsMatch(error)) {
                                        handler.Process(error);
                                        savedError = null;
                                        break;
                                    }
                                }
                            }
                            if (doElse) {
                                pyElse();
                            }
                        } catch (Exception error) {
                            savedError = error;
                        }
                        pyFinally?.Invoke();
                        if (savedError != null) {
                            throw savedError;
                        }
                    }
                }
                
                public class Case {
                    private Type ExceptionType { get; }
                    public Action<Exception> Process { get; }
                    private Func<Exception, bool> When { get; }
                
                    public Case(Type exceptionType, Action<Exception> handler, Func<Exception, bool> when = null) {
                        if (!typeof(Exception).IsAssignableFrom(exceptionType)) {
                            throw new ArgumentException(@"exceptionType must be a type of exception", nameof(exceptionType));
                        }
                        this.ExceptionType = exceptionType;
                        this.Process = handler;
                        this.When = when;
                    }
                
                    public bool IsMatch(Exception error) {
                        return this.ExceptionType.IsInstanceOfType(error) && (this.When?.Invoke(error) ?? true);
                    }
                }
                

                【讨论】:

                  【解决方案15】:

                  你可以做类似这样的事情:

                  bool passed = true;
                  try
                  {
                      reader = new StreamReader(path);
                  }
                  catch (Exception)
                  {
                      passed = false;
                  }
                  if (passed)
                  {
                      // code that executes if the try catch block didnt catch any exception
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 2011-01-23
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-04-07
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-06-08
                    • 2011-06-19
                    相关资源
                    最近更新 更多