在C#中处理文件时如何正确处理异常

时间:2013-11-07 14:36:06

标签: c# .net exception exception-handling

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

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

  1. 接收文件路径列表作为参数
  2. 读取每个文件的文件内容,如果尝试这样做有任何问题,请跳过
  3. 返回表示文件内容的对象列表
  4. 因此规格很简单,以下是我开始编码的方法:

        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;
        }
    

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

7 个答案:

答案 0 :(得分:4)

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

如果您希望在将来某个日期以不同的方式处理多个可能的异常,您可以在常规异常之上添加特定异常的捕获。

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

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

答案 1 :(得分:4)

虽然通常不认为捕捉和吞下非特定例外是一种好习惯,但风险往往被夸大了。

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

在您的情况下,如果您想要遵守指南,则需要提供可以抛出的所有异常的完整列表。我相信以下列表已经完成:

UnauthorizedAccessException IOException FileNotFoundException DirectoryNotFoundException PathTooLongException NotSupportedException(路径格式不正确)。 SecurityException ArgumentException

您可能想要捕获SecurityException或ArgumentException,其他一些来自IOException,因此您可能想要捕获IOException,{{1} }和NotSupportedException

答案 2 :(得分:2)

我对此问题的解决方案通常基于可能的例外情况。如果只有少数,我为每个指定catch块。如果有很多可能,我会抓住所有例外情况。强迫开发人员始终捕获特定的异常可能会导致一些非常难看的代码。

答案 3 :(得分:2)

在这个例子中你可以考虑的事情是FileNotFoundException,你无法捕捉,因为它们太多了,而且最常见的Exception,还有层{ {1}}。

通常,您将尝试尽可能具体地捕获异常,但尤其是如果您捕获异常而不实际使用它们来抛出错误,您可能会捕获一组异常。即使这样,你也会尝试尽可能具体化

答案 4 :(得分:2)

您正在一种方法中混合不同的操作,更改代码会让您更容易问题:

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方法是否正确,或者是否需要错误处理。

答案 5 :(得分:1)

这重复了所说的内容,但希望在某种程度上让你更好地理解。

“跳过任何因某些原因无法读取内容的文件”中存在逻辑错误。

如果您的代码中存在错误原因,则不要跳过它 您只想跳过存在文件相关错误的文件 如果FileContent中的ctor抛出错误怎么办?

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

答案 6 :(得分:1)

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

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

应该避免第二类例外 - 不要懒惰。应该让第三类例外通过......您需要确保知道如何处理问题,否则您可能会使您的应用程序处于混乱或无效状态。

正如其他人所说,你可以通过向现有的try块添加更多的catch块来处理多个异常,它们按照它们出现的顺序进行评估,所以如果你必须处理从你也处理的其他异常派生的异常,首先使用更具体的一个。

相关问题