【问题标题】:How to properly handle exceptions when working with files in C#在 C# 中处理文件时如何正确处理异常
【发布时间】:2013-11-19 06:07:22
【问题描述】:

我已经阅读了许多关于正确异常处理的博客/文章/书籍章节,但我仍然不清楚这个主题。我将尝试用以下示例来说明我的问题。

考虑具有以下要求的类方法:

  1. 接收文件路径列表作为参数
  2. 读取每个文件的文件内容,如果尝试这样做有任何问题,请跳过
  3. 返回代表文件内容的对象列表

所以规格很简单,下面是我可以开始编码的方法:

    public class FileContent
    {
        public string FilePath { get; set; }
        public byte[] Content { get; set; }

        public FileContent(string filePath, byte[] content)
        {
            this.FilePath = filePath;
            this.Content = content;
        }
    }

    static List<FileContent> GetFileContents(List<string> paths)
    {
        var resultList = new List<FileContent>();

        foreach (var path in paths)
        {
            // open file pointed by "path"
            // read file to FileContent object
            // add FileContent to resultList
            // close file
        }

        return resultList;
    }

现在请注意,规范中的 2. 表示该方法应该“跳过任何由于某种原因无法读取内容的文件”。所以发生这种情况可能有很多不同的原因(例如,文件不存在,文件访问由于缺乏安全权限而被拒绝,文件被锁定并被其他应用程序使用等......)但重点是我应该不在乎原因是什么,我只想尽可能读取文件的内容,否则就跳过文件。我不在乎错误是什么......

那么如何正确实现这个方法呢?

好的,正确处理异常的第一条规则是永远不要捕获一般异常。所以这段代码不好:

    static List<FileContent> GetFileContents(List<string> paths)
    {
        var resultList = new List<FileContent>();

        foreach (var path in paths)
        {
            try
            {
                using (FileStream stream = File.Open(path, FileMode.Open))
                using (BinaryReader reader = new BinaryReader(stream))
                {
                    int fileLength = (int)stream.Length;
                    byte[] buffer = new byte[fileLength];
                    reader.Read(buffer, 0, fileLength);

                    resultList.Add(new FileContent(path, buffer));
                }
            }
            catch (Exception ex)
            {
                // this file can't be read, do nothing... just skip the file
            }
        }

        return resultList;
    }

正确异常处理的下一条规则是:只捕获您可以处理的特定异常。好吧,我不关心处理任何可以抛出的特定异常,我只想检查文件是否可以读取。我怎样才能以适当的最佳实践方式做到这一点?

【问题讨论】:

  • 如果没有系统的要求,您的问题将无法解决。遮阳篷是基于意见的,不会解决您的具体情况。
  • 真正正确处理异常的第一条规则是永远不要捕获一般异常?
  • @peer:您所指的系统有哪些要求?您的意思是应用程序是 WPF、控制台还是 ASP.NET?请澄清。
  • 如果您可以以编程方式检查异常,那么您应该这样做而不是 try/catch 块。所以你应该检查文件是否存在,检查你是否有打开它的权限等等,这里根本不要使用 try/catch 块。

标签: c# .net exception exception-handling


【解决方案1】:

这重复了所说的内容,但希望以某种方式让您更好地理解。

您在“跳过任何由于某种原因无法读取内容的文件”中出现逻辑错误。

如果该原因是您的代码中的错误,您不想跳过它。
您只想跳过有文件相关错误的文件。
如果 FileContent 中的 ctor 抛出错误怎么办?

例外是昂贵的。
我会测试 FileExists(并且仍然会捕获异常)
我同意乔列出的例外情况
加油,MSDN 有明确的例子说明如何捕捉各种异常

【讨论】:

    【解决方案2】:

    在我看来,将异常分为三种类型。首先是您期望并知道如何恢复的异常。其次是您知道可以在运行时避免的异常。第三种是您不希望在运行时发生但无法避免或无法实际处理的情况。

    处理第一种类型,这些是对您的特定抽象级别有效的异常类别,它们代表在该级别恢复的有效业务案例(在您的情况下,忽略)。

    应该避免第二类异常——不要偷懒。第三类异常应该放过……你需要确保你知道如何处理问题,否则你可能会让你的应用程序处于混乱或无效的状态。

    正如其他人所说,您可以通过向现有的 try 块添加更多的 catch 块来处理多个异常,它们会按照它们出现的顺序进行评估,因此如果您必须处理源自其他异常的异常,您也可以处理这些异常,首先使用更具体的。

    【讨论】:

      【解决方案3】:

      您的要求很明确 - 跳过无法读取的文件。那么通用异常处理程序有什么问题呢?它允许您以简单、干净、可读、可扩展和可维护的方式执行任务。

      如果您希望在未来任何日期以不同方式处理多个可能的异常,您只需在一般异常之上添加特定异常的捕获即可。

      所以你宁愿看下面的代码?请注意,如果您添加更多代码来处理文件的读取,您必须在此列表中添加任何新的例外。这一切都什么都不做?

      try
      {
          // find, open, read files
      }
      catch(FileNotFoundException) { }
      catch(AccessViolation) { }
      catch(...) { }
      catch(...) { }
      catch(...) { }
      catch(...) { }
      catch(...) { }
      catch(...) { }
      

      约定是指导方针,非常适合尝试遵守以创建良好的代码 - 但不要为了保持某种奇怪的适当礼仪而使代码过于复杂。

      对我来说,正确的礼仪是永远不要在浴室里说话。但是当老板在那里向你打招呼时,你会回击打招呼。因此,如果您不想以不同的方式处理多个异常,则无需捕获每个异常。


      编辑:所以我推荐以下内容

      try
      {
          // find, open, read files
      }
      catch { } // Ignore any and all exceptions
      

      上面告诉我不要关心抛出哪个异常。通过不指定异常,甚至只是 System.Exception,我允许 .NET 默认使用它。所以下面是完全相同的代码。

      try
      {
          // find, open, read files
      }
      catch(Exception) { } // Ignore any and all exceptions
      

      或者,如果您至少要记录它:

      try
      {
          // find, open, read files
      }
      catch(Exception ex) { Logger.Log(ex); }  // Log any and all exceptions
      

      【讨论】:

      • 所以你建议我像在第一篇文章的示例代码中那样捕获一般异常?
      • @matori82 是的。如果您甚至没有记录此信息,那么您实际上不需要(Exception ex)。您可以只写catch{},因为它默认为catch(Exception){}。这将进入我的帖子。
      【解决方案4】:

      在这种情况下,您可以考虑在FileNotFoundException(因为它们太多而无法捕获)和最一般的Exception 之间,还有一层IOException

      一般而言,您会尝试尽可能具体地捕获异常,但特别是如果您在捕获异常而不实际使用它们来引发错误时,您不妨捕获一组异常。即使那样,您仍会尝试使其尽可能具体

      【讨论】:

      • 感谢您提供帮助并编辑我的评论,但您输入的内容完全不是我的意思。
      【解决方案5】:

      虽然捕获和吞下非特定异常通常不被认为是一种好的做法,但风险往往被夸大了。

      毕竟,ASP.NET 会捕获在处理请求期间抛出的非特定异常,并在将其包装在 HttpUnhandledException 中之后,将重定向到错误页面并愉快地继续前进。

      在您的情况下,如果您想遵守该指南,您需要一份可以抛出的所有异常的完整列表。我相信以下列表是完整的:

      UnauthorizedAccessException IOException FileNotFoundException DirectoryNotFoundException PathTooLongException NotSupportedException(路径格式无效)。 SecurityException ArgumentException

      您可能不想捕获 SecurityException 或 ArgumentException,而其他一些异常源自 IOException,因此您可能想要捕获 IOExceptionNotSupportedExceptionUnauthorizedAccessException

      【讨论】:

      • 能否请您编写一个示例代码(基于我上面的)来演示您的方法?
      • @matori82 - 它与 bland 回答中的示例非常相似:stackoverflow.com/a/19839124/13087。基本上,对于您要忽略的每个异常(在本例中:IOException、NotSupportedException 和 UnauthorizedAccessException)都有一系列空的 catch 块。
      • 我觉得这不是一个公平的论点“毕竟,ASP.NET 将捕获在处理请求期间抛出的非特定异常,并将其包装在 HttpUnhandledException 中”在此案例 Asp.Net 实际上遵循快速失败的心态。它通常将此错误转换为 HTTP 500 或 404 之类的内容,让客户端更好地了解失败的原因。事务仍然快速失败,只是这里有一个不同的上下文,它告诉客户端有一个错误。
      【解决方案6】:

      您在一种方法中混合了不同的操作,更改代码将使您更容易提问:

      static List<FileContent> GetFileContents(List<string> paths)
      {
          var resultList = new List<FileContent>();
      
          foreach (var path in paths)
          {
                if (CanReadFile(path){
                      resultList.Add(new FileContent(path, buffer));
                }
          return resultList;
      }
      
      static bool CanReadFile(string Path){
           try{
               using (FileStream stream = File.Open(path, FileMode.Open))
                  using (BinaryReader reader = new BinaryReader(stream))
                  {
                      int fileLength = (int)stream.Length;
                      byte[] buffer = new byte[fileLength];
                      reader.Read(buffer, 0, fileLength);
                  }
           }catch(Exception){ //I do not care what when wrong, error when reading from file
               return false;
           }
           return true;
      }
      

      这样,CanReadFile 会隐藏实现以供您检查。您唯一需要考虑的是 CanReadFile 方法是否正确,或者是否需要错误处理。

      【讨论】:

      • 我知道这只是一个例子,但这是检查文件可读性的一种非常昂贵的方法。这是用同样的方法测试它的一个很好的理由。
      • @j.i.h.同意,我只是尝试实际阅读文件。文件可以在添加到列表和文件的实际读取之间被锁定,甚至被用户删除。但是,您可以在不更改添加等的情况下选择 CanReadFile 方法的不同实现,并且可以对 CanReadFile 进行单元测试和存根。
      【解决方案7】:

      我对这个问题的解决方案通常基于可能的例外的数量。如果只有几个,我会为每个指定 catch 块。如果有很多可能,我会捕获所有异常。强制开发人员总是捕获特定的异常会导致一些非常丑陋的代码。

      【讨论】:

        猜你喜欢
        • 2021-05-22
        • 2010-09-10
        • 1970-01-01
        • 2011-01-29
        • 2011-06-05
        • 1970-01-01
        • 2020-08-13
        • 1970-01-01
        • 2021-01-20
        相关资源
        最近更新 更多