C# Language
Обработка исключений
Поиск…
Основная обработка исключений
try
{
/* code that could throw an exception */
}
catch (Exception ex)
{
/* handle the exception */
}
Обратите внимание, что обработка всех исключений с помощью одного и того же кода часто не является наилучшим подходом.
Это обычно используется, когда какие-либо внутренние процедуры обработки исключений терпят неудачу, в крайнем случае.
Обработка определенных типов исключений
try
{
/* code to open a file */
}
catch (System.IO.FileNotFoundException)
{
/* code to handle the file being not found */
}
catch (System.IO.UnauthorizedAccessException)
{
/* code to handle not being allowed access to the file */
}
catch (System.IO.IOException)
{
/* code to handle IOException or it's descendant other than the previous two */
}
catch (System.Exception)
{
/* code to handle other errors */
}
Будьте осторожны, чтобы исключения оценивались по порядку и применялось наследование. Поэтому вам нужно начать с самых конкретных и закончить с их предком. В любой заданной точке будет выполнен только один блок catch.
Использование объекта исключения
Вы можете создавать исключения и создавать исключения из своего собственного кода. Создание экземпляра исключения выполняется так же, как и любой другой объект C #.
Exception ex = new Exception();
// constructor with an overload that takes a message string
Exception ex = new Exception("Error message");
Затем вы можете использовать ключевое слово throw
для повышения исключения:
try
{
throw new Exception("Error");
}
catch (Exception ex)
{
Console.Write(ex.Message); // Logs 'Error' to the output window
}
Примечание. Если вы выбрали новое исключение внутри блока catch, убедитесь, что исходное исключение передано как «внутреннее исключение», например
void DoSomething()
{
int b=1; int c=5;
try
{
var a = 1;
b = a - 1;
c = a / b;
a = a / c;
}
catch (DivideByZeroException dEx) when (b==0)
{
// we're throwing the same kind of exception
throw new DivideByZeroException("Cannot divide by b because it is zero", dEx);
}
catch (DivideByZeroException dEx) when (c==0)
{
// we're throwing the same kind of exception
throw new DivideByZeroException("Cannot divide by c because it is zero", dEx);
}
}
void Main()
{
try
{
DoSomething();
}
catch (Exception ex)
{
// Logs full error information (incl. inner exception)
Console.Write(ex.ToString());
}
}
В этом случае предполагается, что исключение не может быть обработано, но в сообщение добавляется некоторая полезная информация (и исходное исключение по-прежнему можно получить через ex.InnerException
внешним блоком исключения).
Он покажет что-то вроде:
System.DivideByZeroException: не может делить на b, потому что он равен нулю -> System.DivideByZeroException: Попытка деления на ноль.
в UserQuery.g__DoSomething0_0 () в C: [...] \ LINQPadQuery.cs: строка 36
--- Конец внутренней проверки стека исключений ---
в UserQuery.g__DoSomething0_0 () в C: [...] \ LINQPadQuery.cs: строка 42
в UserQuery.Main () в C: [...] \ LINQPadQuery.cs: строка 55
Если вы попробуете этот пример в LinqPad, вы заметите, что номера строк не очень значимы (они не всегда помогают вам). Но передача полезного текста ошибки, как было предложено выше, часто значительно сокращает время отслеживания местоположения ошибки, которая в этом примере явно соответствует линии
c = a / b;
в функции DoSomething()
.
Наконец, блок
try
{
/* code that could throw an exception */
}
catch (Exception)
{
/* handle the exception */
}
finally
{
/* Code that will be executed, regardless if an exception was thrown / caught or not */
}
Блок try / catch / finally
может быть очень удобен при чтении из файлов.
Например:
FileStream f = null;
try
{
f = File.OpenRead("file.txt");
/* process the file here */
}
finally
{
f?.Close(); // f may be null, so use the null conditional operator.
}
За блоком try должен следовать либо catch
либо finally
. Однако, поскольку нет блокировки catch, выполнение приведет к завершению. Перед завершением будут выполнены инструкции внутри блока finally.
В чтении файла мы могли использовать using
блок как FileStream
(что возвращает OpenRead
) реализует IDisposable
.
Даже если в блоке try
есть оператор return
блок finally
, как правило, выполняется; есть несколько случаев, когда это не будет:
- Когда происходит StackOverflow .
-
Environment.FailFast
- Процесс приложения убит, как правило, внешним источником.
Реализация IErrorHandler для служб WCF
Реализация IErrorHandler для служб WCF - отличный способ централизовать обработку ошибок и протоколирование. Представленная здесь реализация должна поймать любое необработанное исключение, которое вызывается в результате вызова одной из ваших служб WCF. В этом примере также показано, как вернуть пользовательский объект и как вернуть JSON, а не XML по умолчанию.
Внедрение IErrorHandler:
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace BehaviorsAndInspectors
{
public class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception ex)
{
// Log exceptions here
return true;
} // end
public void ProvideFault(Exception ex, MessageVersion version, ref Message fault)
{
// Get the outgoing response portion of the current context
var response = WebOperationContext.Current.OutgoingResponse;
// Set the default http status code
response.StatusCode = HttpStatusCode.InternalServerError;
// Add ContentType header that specifies we are using JSON
response.ContentType = new MediaTypeHeaderValue("application/json").ToString();
// Create the fault message that is returned (note the ref parameter) with BaseDataResponseContract
fault = Message.CreateMessage(
version,
string.Empty,
new CustomReturnType { ErrorMessage = "An unhandled exception occurred!" },
new DataContractJsonSerializer(typeof(BaseDataResponseContract), new List<Type> { typeof(BaseDataResponseContract) }));
if (ex.GetType() == typeof(VariousExceptionTypes))
{
// You might want to catch different types of exceptions here and process them differently
}
// Tell WCF to use JSON encoding rather than default XML
var webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty);
} // end
} // end class
} // end namespace
В этом примере мы присоединяем обработчик к поведению службы. Вы также можете присоединить это к IEndpointBehavior, IContractBehavior или IOperationBehavior аналогичным образом.
Приложите к поведению обслуживания:
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace BehaviorsAndInspectors
{
public class ErrorHandlerExtension : BehaviorExtensionElement, IServiceBehavior
{
public override Type BehaviorType
{
get { return GetType(); }
}
protected override object CreateBehavior()
{
return this;
}
private IErrorHandler GetInstance()
{
return new ErrorHandler();
}
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } // end
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
var errorHandlerInstance = GetInstance();
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(errorHandlerInstance);
}
}
void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } // end
} // end class
} // end namespace
Конфигурации в Web.config:
...
<system.serviceModel>
<services>
<service name="WebServices.MyService">
<endpoint binding="webHttpBinding" contract="WebServices.IMyService" />
</service>
</services>
<extensions>
<behaviorExtensions>
<!-- This extension if for the WCF Error Handling-->
<add name="ErrorHandlerBehavior" type="WebServices.BehaviorsAndInspectors.ErrorHandlerExtensionBehavior, WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<ErrorHandlerBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
....
</system.serviceModel>
...
Вот несколько ссылок, которые могут быть полезны по этой теме:
https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.100).aspx
Другие примеры:
IErrorHandler, похоже, не обрабатывает мои ошибки в WCF .. любые идеи?
Как заставить пользовательский обработчик ошибок WCF возвращать ответ JSON с кодом не-ОК http?
Как установить заголовок Content-Type для запроса HttpClient?
Создание пользовательских исключений
Вам разрешено выполнять пользовательские исключения, которые могут быть выбрасываться точно так же, как и любое другое исключение. Это имеет смысл, если вы хотите, чтобы ваши исключения отличались от других ошибок во время выполнения.
В этом примере мы создадим настраиваемое исключение для четкой обработки проблем, которые может иметь приложение при анализе сложного ввода.
Создание пользовательского класса исключений
Чтобы создать настраиваемое исключение, создайте подкласс класса Exception
:
public class ParserException : Exception
{
public ParserException() :
base("The parsing went wrong and we have no additional information.") { }
}
Пользовательское исключение становится очень полезным, когда вы хотите предоставить дополнительную информацию улову:
public class ParserException : Exception
{
public ParserException(string fileName, int lineNumber) :
base($"Parser error in {fileName}:{lineNumber}")
{
FileName = fileName;
LineNumber = lineNumber;
}
public string FileName {get; private set;}
public int LineNumber {get; private set;}
}
Теперь, когда вы catch(ParserException x)
вас будет дополнительная семантика для точной настройки обработки исключений.
Пользовательские классы могут реализовать следующие функции для поддержки дополнительных сценариев.
повторно метания
Во время процесса синтаксического анализа исходное исключение по-прежнему представляет интерес. В этом примере это FormatException
потому что код пытается разобрать кусок строки, который, как ожидается, будет числом. В этом случае пользовательское исключение должно поддерживать включение « InnerException »:
//new constructor:
ParserException(string msg, Exception inner) : base(msg, inner) {
}
сериализация
В некоторых случаях ваши исключения могут пересекать границы AppDomain. Это так, если ваш парсер работает в своем AppDomain для поддержки горячей перезагрузки новых конфигураций парсера. В Visual Studio вы можете использовать шаблон Exception
для генерации кода, подобного этому.
[Serializable]
public class ParserException : Exception
{
// Constructor without arguments allows throwing your exception without
// providing any information, including error message. Should be included
// if your exception is meaningful without any additional details. Should
// set message by calling base constructor (default message is not helpful).
public ParserException()
: base("Parser failure.")
{}
// Constructor with message argument allows overriding default error message.
// Should be included if users can provide more helpful messages than
// generic automatically generated messages.
public ParserException(string message)
: base(message)
{}
// Constructor for serialization support. If your exception contains custom
// properties, read their values here.
protected ParserException(SerializationInfo info, StreamingContext context)
: base(info, context)
{}
}
Использование исключения ParserException
try
{
Process.StartRun(fileName)
}
catch (ParserException ex)
{
Console.WriteLine($"{ex.Message} in ${ex.FileName}:${ex.LineNumber}");
}
catch (PostProcessException x)
{
...
}
Вы также можете использовать пользовательские исключения для исключений catching и wrapping. Таким образом, многие различные ошибки могут быть преобразованы в один тип ошибки, который более полезен для приложения:
try
{
int foo = int.Parse(token);
}
catch (FormatException ex)
{
//Assuming you added this constructor
throw new ParserException(
$"Failed to read {token} as number.",
FileName,
LineNumber,
ex);
}
При обработке исключений, создавая собственные пользовательские исключения, вы обычно должны включать ссылку на исходное исключение в свойстве InnerException
, как показано выше.
Проблемы безопасности
Если разоблачение причины исключения может поставить под угрозу безопасность, позволяя пользователям видеть внутреннюю работу вашего приложения, может быть плохая идея обернуть внутреннее исключение. Это может применяться, если вы создаете библиотеку классов, которая будет использоваться другими.
Вот как вы могли бы создать настраиваемое исключение без обертывания внутреннего исключения:
try
{
// ...
}
catch (SomeStandardException ex)
{
// ...
throw new MyCustomException(someMessage);
}
Заключение
При создании настраиваемого исключения (с оберткой или с помощью развернутого нового исключения) вы должны создать исключение, имеющее смысл для вызывающего. Например, пользователь библиотеки классов может не знать о том, как эта библиотека выполняет свою внутреннюю работу. Исключения, вызванные зависимостями библиотеки классов, не имеют смысла. Скорее, пользователь хочет исключение, которое имеет отношение к тому, как библиотека классов использует эти зависимости ошибочным способом.
try
{
// ...
}
catch (IOException ex)
{
// ...
throw new StorageServiceException(@"The Storage Service encountered a problem saving
your data. Please consult the inner exception for technical details.
If you are not able to resolve the problem, please call 555-555-1234 for technical
assistance.", ex);
}
Исключение Анти-шаблоны
Глотание исключений
Всегда следует перебрасывать исключение следующим образом:
try
{
...
}
catch (Exception ex)
{
...
throw;
}
Повторное бросание исключения, как показано ниже, приведет к запутыванию исходного исключения и потеряет исходную трассировку стека. Никогда не следует этого делать! Трассировка стека до улова и ретрона будет потеряна.
try
{
...
}
catch (Exception ex)
{
...
throw ex;
}
Обработка исключений в бейсболе
Не следует использовать исключения в качестве замены для обычных конструкций управления потоком, таких как операторы if-then и while. Этот анти-шаблон иногда называют обработкой исключений для бейсбола .
Вот пример анти-шаблона:
try
{
while (AccountManager.HasMoreAccounts())
{
account = AccountManager.GetNextAccount();
if (account.Name == userName)
{
//We found it
throw new AccountFoundException(account);
}
}
}
catch (AccountFoundException found)
{
Console.Write("Here are your account details: " + found.Account.Details.ToString());
}
Вот лучший способ сделать это:
Account found = null;
while (AccountManager.HasMoreAccounts() && (found==null))
{
account = AccountManager.GetNextAccount();
if (account.Name == userName)
{
//We found it
found = account;
}
}
Console.Write("Here are your account details: " + found.Details.ToString());
улов (исключение)
Почти нет (некоторые говорят, что нет!) Причин, чтобы поймать общий тип исключения в вашем коде. Вы должны поймать только те типы исключений, которые вы ожидаете, потому что в противном случае вы скрываете ошибки в своем коде.
try
{
var f = File.Open(myfile);
// do something
}
catch (Exception x)
{
// Assume file not found
Console.Write("Could not open file");
// but maybe the error was a NullReferenceException because of a bug in the file handling code?
}
Лучше сделать:
try
{
var f = File.Open(myfile);
// do something which should normally not throw exceptions
}
catch (IOException)
{
Console.Write("File not found");
}
// Unfortunatelly, this one does not derive from the above, so declare separatelly
catch (UnauthorizedAccessException)
{
Console.Write("Insufficient rights");
}
Если произойдет какое-либо другое исключение, мы автоматически разрешим приложение сбой, поэтому он непосредственно работает в отладчике, и мы можем исправить эту проблему. Мы не должны отправлять программу, где любые другие исключения, кроме этих, происходят в любом случае, так что это не проблема с крахом.
Ниже приведен неверный пример, так как он использует исключения для работы с ошибкой программирования. Это не то, для чего они предназначены.
public void DoSomething(String s)
{
if (s == null)
throw new ArgumentNullException(nameof(s));
// Implementation goes here
}
try
{
DoSomething(myString);
}
catch(ArgumentNullException x)
{
// if this happens, we have a programming error and we should check
// why myString was null in the first place.
}
Совокупные исключения / множественные исключения из одного метода
Кто говорит, что вы не можете бросить несколько исключений в один метод. Если вы не привыкли играть с AggregateExceptions, у вас может возникнуть соблазн создать свою собственную структуру данных, чтобы представить, что многие вещи идут не так. Есть, конечно, другая структура данных, которая не является исключением, была бы более идеальной, такой как результаты проверки. Даже если вы играете с AggregateExceptions, вы можете быть на стороне приема и всегда обращаться с ними, не понимая, что они могут вам пригодиться.
Весьма правдоподобно, что метод выполняется, и даже если это будет провал в целом, вы захотите выделить несколько вещей, которые поступили не так в тех исключениях, которые были выбраны. В качестве примера это поведение можно увидеть с помощью методов параллельных вычислений, которые были разбиты на несколько потоков, и любое число из них могло генерировать исключения, и об этом необходимо сообщать. Вот глупый пример того, как вы могли бы воспользоваться этим:
public void Run()
{
try
{
this.SillyMethod(1, 2);
}
catch (AggregateException ex)
{
Console.WriteLine(ex.Message);
foreach (Exception innerException in ex.InnerExceptions)
{
Console.WriteLine(innerException.Message);
}
}
}
private void SillyMethod(int input1, int input2)
{
var exceptions = new List<Exception>();
if (input1 == 1)
{
exceptions.Add(new ArgumentException("I do not like ones"));
}
if (input2 == 2)
{
exceptions.Add(new ArgumentException("I do not like twos"));
}
if (exceptions.Any())
{
throw new AggregateException("Funny stuff happended during execution", exceptions);
}
}
Вложение исключений и попытка блокировки.
Один из них может вставить одно исключение / try
catch
block внутри другого.
Таким образом, можно управлять небольшими блоками кода, которые способны работать без нарушения всего вашего механизма.
try
{
//some code here
try
{
//some thing which throws an exception. For Eg : divide by 0
}
catch (DivideByZeroException dzEx)
{
//handle here only this exception
//throw from here will be passed on to the parent catch block
}
finally
{
//any thing to do after it is done.
}
//resume from here & proceed as normal;
}
catch(Exception e)
{
//handle here
}
Примечание. Избегайте проглатывания исключений при бросании в родительский блок catch
Лучшие практики
Cheatsheet
ДЕЛАТЬ | НЕ |
---|---|
Управляющий поток с контрольными инструкциями | Управляющий поток с исключениями |
Следить за проигнорированным (поглощенным) исключением путем ведения журнала | Игнорировать исключение |
Повторить исключение, используя throw | Исключение повторного броска - throw new ArgumentNullException() или throw ex |
Выбросить предопределенные системные исключения | Выбросить пользовательские исключения, аналогичные предопределенным системным исключениям |
Выбросить пользовательское / предопределенное исключение, если оно имеет решающее значение для логики приложения | Выбросить пользовательские / предопределенные исключения, чтобы указать предупреждение в потоке |
Ловить исключения, которые вы хотите обработать | Поймать каждое исключение |
НЕ управляйте бизнес-логикой с исключениями.
Контроль потока НЕ должен выполняться исключениями. Вместо этого используйте условные утверждения. Если элемент управления можно сделать с инструкцией if-else
, не используйте исключения, поскольку он снижает читаемость и производительность.
Рассмотрите следующий фрагмент г-ном Бад-Практиками:
// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
Console.WriteLine(myObject.ToString());
}
Когда выполнение достигает Console.WriteLine(myObject.ToString());
приложение выкинет исключение NullReferenceException. Г-н Bad Practices понял, что myObject
имеет значение null и отредактировал его фрагмент, чтобы поймать и обработать NullReferenceException
:
// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
try
{
Console.WriteLine(myObject.ToString());
}
catch(NullReferenceException ex)
{
// Hmmm, if I create a new instance of object and assign it to myObject:
myObject = new object();
// Nice, now I can continue to work with myObject
DoSomethingElseWithMyObject();
}
}
Поскольку предыдущий фрагмент кода охватывает только логику исключения, что мне делать, если myObject
на данный момент не равен нулю? Где я должен освещать эту часть логики? Сразу после Console.WriteLine(myObject.ToString());
? Как насчет после try...catch
block?
Как насчет г-на лучших практик? Как он справится с этим?
// This is a snippet example for DO
object myObject;
void DoingSomethingWithMyObject()
{
if(myObject == null)
myObject = new object();
// When execution reaches this point, we are sure that myObject is not null
DoSomethingElseWithMyObject();
}
Г-н Лучшие практики достигли той же логики с меньшим количеством кода и понятной и понятной логикой.
НЕ перебрасывать исключения
Переброски исключений стоят дорого. Это негативно сказывается на производительности. Для кода, который обычно терпит неудачу, вы можете использовать шаблоны проектирования для сведения к минимуму проблем с производительностью. В этом разделе описываются два шаблона проектирования, которые полезны, когда исключения могут значительно влиять на производительность.
НЕ поглощайте исключения без регистрации
try
{
//Some code that might throw an exception
}
catch(Exception ex)
{
//empty catch block, bad practice
}
Никогда не проглатывайте исключения. Игнорирование исключений сэкономит этот момент, но позже создаст хаос для ремонтопригодности. При регистрации исключений вы всегда должны регистрировать экземпляр исключения, чтобы отслеживать полную трассировку стека, а не только сообщение об исключении.
try
{
//Some code that might throw an exception
}
catch(NullException ex)
{
LogManager.Log(ex.ToString());
}
Не перехватывайте исключения, которые вы не можете обработать
Многие ресурсы, такие как этот , настоятельно рекомендуют вам рассмотреть, почему вы ловите исключение в том месте, где вы его ловите. Вы должны только поймать исключение, если вы можете обработать его в этом месте. Если вы можете что-то сделать, чтобы помочь устранить проблему, например, попробовать альтернативный алгоритм, подключиться к резервной базе данных, попробовать другое имя файла, подождать 30 секунд и повторить попытку или уведомить администратора, вы можете поймать ошибку и сделать это. Если вы ничего не можете сделать правдоподобно и разумно, просто «отпустите», и пусть исключение будет обрабатываться на более высоком уровне. Если исключение является достаточно катастрофическим, и нет разумного выбора, кроме как для всей программы, чтобы сбой из-за серьезности проблемы, то пусть это сбой.
try
{
//Try to save the data to the main database.
}
catch(SqlException ex)
{
//Try to save the data to the alternative database.
}
//If anything other than a SqlException is thrown, there is nothing we can do here. Let the exception bubble up to a level where it can be handled.
Необработанное и исключение потоков
AppDomain.UnhandledException Это событие предоставляет уведомление о неперехваченных исключениях. Оно позволяет приложению регистрировать информацию об исключении до того, как обработчик по умолчанию системы сообщит об исключении для пользователя и прекратит применение. Если имеется достаточная информация о состоянии приложения, другие могут быть предприняты действия, такие как сохранение программных данных для последующего восстановления. Предупреждение рекомендуется, так как данные программы могут быть повреждены, если исключения не обрабатываются.
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
}
Application.ThreadException Это событие позволяет вашему приложению Windows Forms обрабатывать иначе необработанные исключения, возникающие в потоках Windows Forms. Прикрепите обработчики событий к событию ThreadException для устранения этих исключений, которые оставят ваше приложение в неизвестном состоянии. По возможности исключения должны обрабатываться блоком обработки структурированных исключений.
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
Application.ThreadException += new ThreadExceptionEventHandler(ThreadException);
}
И, наконец, обработка исключений
static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
// your code
}
static void ThreadException(object sender, ThreadExceptionEventArgs e)
{
Exception ex = e.Exception;
// your code
}
Выброс исключения
Ваш код может и часто должен вызывать исключение, если что-то необычное произошло.
public void WalkInto(Destination destination)
{
if (destination.Name == "Mordor")
{
throw new InvalidOperationException("One does not simply walk into Mordor.");
}
// ... Implement your normal walking code here.
}