Как правильно обрабатывать исключения при работе с файлами в C#

Я прочитал много блогов / статей / глав книг о правильной обработке исключений, и все же эта тема мне не ясна. Я попытаюсь проиллюстрировать свой вопрос следующим примером.

рассмотрим метод класса, который имеет следующие требования:

  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. из спецификаций говорится, что метод должен "пропустить любой файл, содержимое которого не может быть прочитано по какой-либо причине". Поэтому там может быть много разных причин для этого (например. файл не существует, доступ к файлу запрещен из-за отсутствия разрешений безопасности, файл заблокирован и используется каким-либо другим приложением и т. д...) но дело в том, что мне все равно, что причина в том, что я просто хочу прочитать содержимое файла, если это возможно, или пропустить файл, если нет. Мне все равно, в чем ошибка...

Итак, как правильно реализовать этот метод?

OK первое правило правильной обработки исключений-никогда не ловить общее исключение. Таким образом, этот код не хорош:

    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 ответов


хотя обычно не считается хорошей практикой ловить и глотать неспецифические исключения, риски часто завышены.

В конце концов, ASP.NET поймает неспецифическое исключение, которое возникает во время обработки запроса, и после его упаковки в HttpUnhandledException, перенаправит на страницу ошибки и счастливо продолжит свой путь.

в вашем случае, если вы хотите уважать руководство, вам нужен полный список всех исключения, которые могут быть брошены. Я считаю, что следующий список завершен:

UnauthorizedAccessException IOException FileNotFoundException DirectoryNotFoundException PathTooLongException NotSupportedException (путь не в допустимом формате). SecurityException ArgumentException

вы, вероятно, не захотите поймать SecurityException или ArgumentException, а некоторые из других происходят от IOException, поэтому вы, вероятно, захотите поймать IOException, NotSupportedException и UnauthorizedAccessException.


ваши требования ясны-пропустите файлы, которые нельзя прочитать. Итак, в чем проблема с обработчиком общих исключений? Это позволяет выполнять задачу простым, чистым, читаемым, масштабируемым и ремонтопригодным способом.

если в любой будущей дате вы хотите обрабатывать несколько возможных исключений по-разному, вы можете просто добавить выше общего исключения улов для конкретного(ых).

Итак, вы бы предпочли увидеть код ниже? Обратите внимание, что если вы добавите больше кода для обработки чтения файлов, вы должны добавьте новые исключения в этот список. Все это, чтобы ничего не делать?

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

Конвенции являются руководящими принципами и отлично подходят для того, чтобы попытаться придерживаться, чтобы создать хороший код, но не слишком усложнять код, чтобы поддерживать какое - то странное чувство правильного этикета.

для меня правильный этикет-никогда не разговаривать в ванной. Но когда босс здоровается с тобой, ты отвечаешь ему тем же. Так что, если ты не хочешь справиться ... несколько исключений по-разному, вам не нужно ловить каждый.


Edit: поэтому я рекомендую следующее

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

вышесказанное говорит мне не заботиться о том, какое исключение выбрасывается. Не указывая исключение, даже просто систему.Исключение, я разрешил .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

мое решение этого вопроса обычно основано на количестве возможных исключений. Если их всего несколько, я указываю блоки catch для каждого. Если есть много возможных, я улавливаю все исключения. Принуждение разработчиков всегда улавливать определенные исключения может привести к очень уродливому коду.


вы смешиваете различные действия в одном методе, изменение кода сделает вас вопрос проще awnser:

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 или если он нуждается в исправлении ошибок.


то, что вы можете рассмотреть в этом случае, это то, что между FileNotFoundException, который вы не можете поймать, потому что их слишком много, и самый общий Exception, есть еще слой IOException.

В общем, вы попытаетесь поймать свои исключения как можно более конкретными, но особенно если вы ловите исключения, фактически не используя их для создания ошибки, вы можете также поймать группу исключений. Даже тогда, однако, вы попытаетесь сделать его конкретным как можно


на мой взгляд, разделить исключения на три типа. Во-первых, исключение, которое вы ожидаете и знаете, как восстановить. Во-вторых, это исключения, которых вы можете избежать во время выполнения. В-третьих, вы не ожидаете, что это произойдет во время выполнения, но не можете избежать или реально справиться.

обрабатывать первый тип, это класс исключений, которые действительны для вашего определенного уровня абстракции и которые представляют допустимые бизнес-случаи для восстановления на этом уровне (в вашем случае, игнорирование.)

второго класса исключений следует избегать - не ленитесь. Третий класс исключений должен быть пропущен... вы должны убедиться, что вы знаете, как справиться с проблемой, иначе вы можете оставить свою заявку в заблуждение или недопустимое состояние.

Как говорили другие, вы можете обрабатывать несколько исключений, добавляя больше блоков catch к существующему блоку try, они оцениваются в порядке их появления, поэтому, если вам нужно обрабатывать исключения, которые производные от других исключений, которые вы также обрабатываете, сначала используйте более конкретный.


это повторяет то, что сказано, но, надеюсь, таким образом, чтобы вы лучше поняли.

У вас есть логическая ошибка в "пропустить любой файл, содержимое которого не может быть прочитано по какой-либо причине".

Если эта причина является ошибкой в вашем коде, вы не хотите ее пропускать.
Вы хотите пропустить только файлы, которые имеют ошибки, связанные с файлами.
Что если чтор в FileContent бросал ошибку?

и исключения дороги.
Я бы проверил для FileExists (и все еще ловят исключения)
И я согласен с исключениями, перечисленными Джо
Come on MSDN имеет четкие примеры того, как поймать различные исключения