Бросать Win32Exception - PullRequest
       17

Бросать Win32Exception

19 голосов
/ 23 марта 2009

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

В настоящее время вызовы нативных функций выглядят примерно так:

// NativeFunction returns true when successful and false when an error
// occurred. When an error occurs, the MSDN docs usually tell you that the
// error code can be discovered by calling GetLastError (as long as the
// SetLastError flag has been set in the DllImport attribute).
// Marshal.GetLastWin32Error is the equivalent managed function, it seems.
if (!WinApi.NativeFunction(param1, param2, param3))
    throw new Win32Exception();

Строка, которая вызывает исключение, может быть эквивалентно переписана, как я полагаю:

throw new Win32Exception(Marshal.GetLastWin32Error());

Теперь все хорошо, потому что он генерирует исключение, надлежащим образом содержащее код ошибки Win32, который был установлен, а также (обычно) удобочитаемое описание ошибки как свойство Message объекта Exception. Тем не менее, я подумал, что было бы целесообразно изменить / обернуть хотя бы некоторые из этих исключений, если не все, чтобы они выдавали чуть более контекстно-ориентированное сообщение об ошибке, т.е. использовался. Я рассмотрел несколько альтернатив для этого:

  1. Указание пользовательского сообщения об ошибке в конструкторе для Win32Exception.

    throw new Win32Exception(Marshal.GetLastWin32Error(), "My custom error message.");
    
  2. Обертывание Win32Exception в другом объекте Exception для сохранения исходного кода ошибки и сообщения (Win32Exception теперь является InnerException родительского исключения).

    throw new Exception("My custom error message.", Win32Exception(Marshal.GetLastWin32Error()));
    
  3. То же, что и 2, за исключением использования другого Win32Exception в качестве исключения оболочки.

  4. То же, что и 2, за исключением использования пользовательского класса, производного от Exception, в качестве исключения оболочки.

  5. То же, что и 2, за исключением использования исключения BCL (библиотеки базовых классов) в качестве родительского при подходе. Не уверен, стоит ли даже устанавливать InnerException на Win32Exception в этом случае (возможно, для низкоуровневой оболочки, но не для высокоуровневого / абстрактного интерфейса, который не дает понять, что за Win32 происходит взаимодействие сцены?)

По сути, я хочу знать следующее: какова рекомендуемая практика работы с ошибками Win32 в .NET? Я вижу, что это делается с открытым исходным кодом разными способами, но мне было любопытно, есть ли какие-либо рекомендации по проектированию. Если нет, я бы заинтересовался вашими личными предпочтениями здесь. (Возможно, вы даже не используете ни один из вышеперечисленных методов?)

Ответы [ 3 ]

4 голосов
/ 23 марта 2009

Это на самом деле не относится к исключениям Win32; вопрос в том, когда два разных случая ошибок должны быть идентифицированы двумя разными производными от исключения типами, и когда они должны выдавать один и тот же тип с разными значениями, хранящимися в нем?

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

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

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

3 голосов
/ 23 марта 2009

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

Если вызывающий ваш класс / метод поймет, что он вызывает Win32 в той или иной форме, я бы использовал вариант 1), который вы указали. Это кажется самым «понятным» для меня. (Однако, если это так, я бы назвал ваш класс таким образом, чтобы было ясно, что Win32 API будет использоваться напрямую). При этом в BCL есть исключения, которые на самом деле подкласс Win32Exception, чтобы быть более понятным, а не просто обернуть его. Например, SocketException происходит от Win32Exception. Я никогда не использовал этот подход лично, но он кажется потенциально чистым способом справиться с этим.

Если вызывающая сторона вашего класса не будет знать, что вы вызываете Win32 API напрямую, я бы обработал исключение и использовал пользовательское, более описательное исключение, которое вы определили. Например, если я использую ваш класс, и нет никаких признаков того, что вы используете API-интерфейс Win32 (поскольку вы используете его внутри по какой-то конкретной, неочевидной причине), у меня не было бы оснований подозревать, что я может потребоваться обработать Win32Exception. Вы всегда можете задокументировать это, но мне кажется более разумным отловить это и дать исключение, которое будет иметь большее значение в вашем конкретном бизнес-контексте. В этом случае я мог бы обернуть исходное исключение Win32Exception как внутреннее исключение (т. Е. Ваш случай 4), но в зависимости от того, что вызвало внутреннее исключение, я мог бы этого не делать.

Кроме того, во многих случаях Win32Exception генерируется из собственного вызова, но в BCL есть и другие исключения, которые более актуальны. Это тот случай, когда вы вызываете нативный API, который не упакован, но есть аналогичные функции, которые ОБРАТЕНЫ в BCL. В этом случае я бы, вероятно, перехватил исключение, удостоверился, что это именно то, что я ожидал, но затем бросил бы стандартное исключение BCL на его место. Хорошим примером этого является использование SecurityException вместо создания Win32Exception.

В общем, я бы избегал указанных вами вариантов 2 и 3.

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

Вариант три кажется избыточным - на самом деле нет преимущества перед вариантом 1.

2 голосов
/ 23 марта 2009

Лично я бы сделал # 2 или # 4 ... Желательно # 4. Оберните Win32Exception внутри вашего исключения, которое зависит от контекста. Как это:

void ReadFile()
{
    if (!WinApi.NativeFunction(param1, param2, param3))
        throw MyReadFileException("Couldn't read file", new Win32Exception());
}

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

...