A few more bits …
You absolutely should have a centralized exception handling policy in place. This can be as simple as wrapping Main()
in a try/catch, failing fast with a graceful error message to the user. This is the «last resort» exception handler.
Preemptive checks are always correct if feasible, but not always perfect. For example, between the code where you check for a file’s existence and the next line where you open it, the file could have been deleted or some other issue may impede your access. You still need try/catch/finally in that world. Use both the preemptive check and the try/catch/finally as appropriate.
Never «swallow» an exception, except in the most well-documented cases when you are absolutely, positively sure that the exception being thrown is livable. This will almost never be the case. (And if it is, make sure you’re swallowing only the specific exception class — don’t ever swallow System.Exception
.)
When building libraries (used by your app), do not swallow exceptions, and do not be afraid to let the exceptions bubble up. Do not re-throw unless you have something useful to add. Do not ever (in C#) do this:
throw ex;
As you will erase the call stack. If you must re-throw (which is occasionally necessary, such as when using the Exception Handling Block of Enterprise Library), use the following:
throw;
At the end of the day, the very vast majority of exceptions thrown by a running application should be exposed somewhere. They should not be exposed to end users (as they often contain proprietary or otherwise valuable data), but rather usually logged, with administrators notified of the exception. The user can be presented with a generic dialog box, maybe with a reference number, to keep things simple.
Exception handling in .NET is more art than science. Everyone will have their favorites to share here. These are just a few of the tips I’ve picked up using .NET since day 1, techniques which have saved my bacon on more than one occasion. Your mileage may vary.
Error logging is an essential part of any application, as it allows developers to track and fix issues that may arise during the use of the application. In a Windows Forms application written in C#, several options exist for implementing effective error logging. In this blog post, we will discuss the various approaches for logging errors in a Windows Forms application, and provide guidance on the best practices for implementing error logging in your own applications.
I’ll use a simple file as an output to keep the examples simple. In a real application, you’d want to use a logging framework like Serilog or NLog (maybe even log errors to elmah.io).
Use try/catch blocks to handle exceptions
One of the most basic ways to log errors in a Windows Forms application is to use try/catch blocks to handle exceptions that may occur. When an exception is thrown, the catch block can log the error to a file or database, and then display a user-friendly message to the user. This approach is useful for catching and handling specific exceptions that you anticipate may occur in your application.
Here is an example of using try/catch
blocks to handle exceptions:
try
{
// Code that may throw an exception
}
catch (Exception ex)
{
// Log the error
File.AppendAllText("error.log", ex.ToString());
// Display user-friendly message
MessageBox.Show("An error has occurred. Please try again later.");
}
It is important to note that try/catch
blocks should only be used to handle exceptions that you anticipate may occur and that you have the ability to recover from. For example, if you are parsing user input and expect that the input may not always be in the correct format, you can use a try/catch
block to catch the exception and display a user-friendly message to the user. When possible, always try to avoid exceptions as shown in this post: C# exception handling best practices.
Use the Application.ThreadException event
The Application.ThreadException
event allows you to handle unhandled exceptions that occur on the UI thread. This is useful for catching any exceptions that may not be handled by try/catch
blocks, and allows you to log the error and display a user-friendly message to the user.
Here is an example of using the Application.ThreadException
event to handle unhandled exceptions:
private void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
// Log the error
File.AppendAllText("error.log", e.Exception.ToString());
// Display user-friendly message
MessageBox.Show("An error has occurred. Please try again later.");
}
// Register the event handler
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
It is important to note that the Application.ThreadException
event should only be used to handle unhandled exceptions on the UI thread. If an exception occurs on a non-UI thread, it will not be caught by this event.
Use a global exception handler
Another option for logging errors in a Windows Forms application is to use a global exception handler. This involves creating a custom class that implements the UnhandledException
event and registers it as the global exception handler for the application. This allows you to catch and handle any unhandled exceptions that may occur in your application, and log the error for future debugging.
Here is an example of using a global exception handler to log errors:
public class GlobalExceptionHandler
{
public static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// Log the error
File.AppendAllText("error.log", e.ExceptionObject.ToString());
// Display user-friendly message
MessageBox.Show("An error has occurred. Please try again later.");
}
}
// Register the global exception handler
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(GlobalExceptionHandler.UnhandledException);
The global exception handler is useful for catching any unhandled exceptions that may not be caught by try/catch
blocks or the Application.ThreadException
event. It is important to note that the global exception handler should only be used as a last resort, as it will catch all unhandled exceptions in the application. You need to include it, though, to make sure that all exceptions are correctly handled. Even the ones you didn’t expect.
Best practices
- Use multiple approaches for logging errors: It is recommended to use a combination of
try/catch
blocks, theApplication.ThreadException
event, and a global exception handler to ensure that all errors are logged and handled in your application. - Log as much information as possible: To properly debug and fix errors, it is important to log as much information as possible about the error, such as the exception type, message, and stack trace. When using a service like elmah.io together with a logging framework, we automatically pick up all of the needed information to inspect what went wrong.
- Display user-friendly messages: When an error occurs, it is important to display a user-friendly message to the user, rather than displaying a technical error message. This helps to improve the user experience and prevent confusion.
- Handle errors on non-UI threads: If your application has background tasks or worker threads that perform operations, it is also important to handle any errors that may occur on these threads. One way to do this is to use the
Task.Run
method and specify a catch block to handle any exceptions that may occur:
Task.Run(() =>
{
// Code that may throw an exception
}).ContinueWith((t) =>
{
// Log the error
File.AppendAllText("error.log", t.Exception.ToString());
// Display user-friendly message
MessageBox.Show("An error has occurred. Please try again later.");
}, TaskContinuationOptions.OnlyOnFaulted);
Conclusion
Error logging is an important aspect of any application, and is essential for debugging and fixing issues that may arise during the use of the application. By using try/catch
blocks, the Application.ThreadException
event, and a global exception handler, you can effectively log errors in a Windows Forms application written in C#. By following the best practices outlined in this blog post, you can ensure that all errors are logged and handled in an efficient and user-friendly manner.
It is important to note that error logging is just one aspect of a comprehensive error-handling strategy. Other considerations may include monitoring and alerting, handling errors in a production environment, and providing support to end users. By carefully planning and implementing a comprehensive error-handling strategy, you can ensure that your application is reliable and provides a good user experience. Make sure to reach out if you want to hear more about how elmah.io can help you.
elmah.io: Error logging and Uptime Monitoring for your web apps
This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.
See how we can help you monitor your website for crashes
Monitor your website
Обработка исключений
Конструкция try..catch..finally
Последнее обновление: 30.12.2021
Иногда при выполнении программы возникают ошибки, которые трудно предусмотреть или предвидеть, а иногда и вовсе невозможно. Например, при передачи файла по сети может неожиданно оборваться сетевое подключение.
такие ситуации называются исключениями. Язык C# предоставляет разработчикам возможности для обработки таких ситуаций. Для этого
в C# предназначена конструкция try…catch…finally.
try { } catch { } finally { }
При использовании блока try…catch..finally вначале выполняются все инструкции в блоке try. Если в
этом блоке не возникло исключений, то после его выполнения начинает выполняться блок finally. И затем конструкция try..catch..finally
завершает свою работу.
Если же в блоке try вдруг возникает исключение, то обычный порядок выполнения останавливается, и среда CLR
начинает искать блок catch, который может обработать данное исключение. Если нужный блок
catch найден, то он выполняется, и после его завершения выполняется блок finally.
Если нужный блок catch не найден, то при возникновении исключения программа аварийно завершает свое выполнение.
Рассмотрим следующий пример:
int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); Console.WriteLine("Конец программы");
В данном случае происходит деление числа на 0, что приведет к генерации исключения. И при запуске приложения в
режиме отладки мы увидим в Visual Studio окошко, которое информирует об исключении:
В этом окошке мы видим, что возникло исключение, которое представляет тип System.DivideByZeroException,
то есть попытка деления на ноль. С помощью пункта View Details можно посмотреть более детальную информацию об исключении.
И в этом случае единственное, что нам остается, это завершить выполнение программы.
Чтобы избежать подобного аварийного завершения программы, следует использовать для обработки исключений конструкцию
try…catch…finally. Так, перепишем пример следующим образом:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); } finally { Console.WriteLine("Блок finally"); } Console.WriteLine("Конец программы");
В данном случае у нас опять же возникнет исключение в блоке try, так как мы пытаемся разделить на ноль.
И дойдя до строки
int y = x / 0;
выполнение программы остановится. CLR найдет блок catch и передаст управление этому блоку.
После блока catch будет выполняться блок finally.
Возникло исключение! Блок finally Конец программы
Таким образом, программа по-прежнему не будет выполнять деление на ноль и соответственно не будет выводить результат этого деления,
но теперь она не будет аварийно завершаться, а исключение будет обрабатываться в блоке catch.
Следует отметить, что в этой конструкции обязателен блок try. При наличии блока catch мы можем опустить блок finally:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); }
И, наоборот, при наличии блока finally мы можем опустить блок catch и не обрабатывать исключение:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } finally { Console.WriteLine("Блок finally"); }
Однако, хотя с точки зрения синтаксиса C# такая конструкция вполне корректна, тем не менее, поскольку CLR не сможет найти нужный блок
catch, то исключение не будет обработано, и программа аварийно завершится.
Обработка исключений и условные конструкции
Ряд исключительных ситуаций может быть предвиден разработчиком. Например, пусть в программе есть метод, который принимает строку, конвертирует ее в число
и вычисляет квадрат этого числа:
Square("12"); // Квадрат числа 12: 144 Square("ab"); // !Исключение void Square(string data) { int x = int.Parse(data); Console.WriteLine($"Квадрат числа {x}: {x * x}"); }
Если пользователь передаст в метод не число, а строку, которая содежит нецифровые символы, то программа выпадет в ошибку. С одной стороны,
здесь как раз та ситуация, когда можно применить блок
try..catch
, чтобы обработать возможную ошибку. Однако гораздо оптимальнее было бы проверить допустимость преобразования:
Square("12"); // Квадрат числа 12: 144 Square("ab"); // Некорректный ввод void Square(string data) { if (int.TryParse(data, out var x)) { Console.WriteLine($"Квадрат числа {x}: {x * x}"); } else { Console.WriteLine("Некорректный ввод"); } }
Метод int.TryParse()
возвращает true
, если преобразование можно осуществить, и false
— если нельзя. При допустимости преобразования переменная x
будет содержать введенное число. Так, не используя try...catch
можно обработать возможную исключительную ситуацию.
С точки зрения производительности использование блоков try..catch
более накладно, чем применение условных конструкций. Поэтому по возможности
вместо try..catch лучше использовать условные конструкции на проверку исключительных ситуаций.
frank2 0 / 0 / 2 Регистрация: 18.01.2016 Сообщений: 220 |
||||
1 |
||||
20.01.2016, 00:06. Показов 3595. Ответов 4 Метки нет (Все метки)
Всем здравствуйте! При написании программы образовалась проблема, не могу понять как решить.
Но этого не происходит и форма загружается со всеми элементами, в чем может быть ошибка?
0 |
0 / 0 / 1 Регистрация: 19.01.2016 Сообщений: 4 |
|
20.01.2016, 00:30 |
2 |
this.Close() попробуй.
0 |
79 / 102 / 44 Регистрация: 12.05.2015 Сообщений: 476 |
|
20.01.2016, 00:40 |
3 |
frank2, надо пройтись отладчиком. Возможно не заходит в блок catch.
0 |
0 / 0 / 2 Регистрация: 18.01.2016 Сообщений: 220 |
|
20.01.2016, 09:10 [ТС] |
4 |
Dmtr_, не получилось,выходит ошибка Доступ к ликвидированному объекту невозможен.
0 |
greg zakharov Покинул форум 6598 / 1481 / 355 Регистрация: 07.05.2015 Сообщений: 2,902 |
||||
20.01.2016, 09:34 |
5 |
|||
0 |
IT_Exp Эксперт 87844 / 49110 / 22898 Регистрация: 17.06.2006 Сообщений: 92,604 |
20.01.2016, 09:34 |
5 |
Исключения (Exceptions) это тип ошибки, которая происходит при выполнении приложения. Ошибки обычно означают появление неожиданных проблем. Тогда как исключения, обработка которых организована в коде, являются ожидаемыми, они происходят в коде приложений по различным причинам.
Исключения позволяют передать управление из одной части кода в другую часть. Когда срабатывает/выбрасывается исключение (exception thrown), текущий поток выполнения кода секции try прерывается, и запускается выполнение секции catch.
Обработка исключений C# осуществляется следующими ключевыми словами: try, catch, finally и throw.
try — блок try инкапсулирует проверяемый на исключение регион кода. Если любая строка кода в этом блоке вызывает срабатывание исключения, то исключение будет обработано соответствующим блоком catch.
catch — когда происходит исключение, запускается блок кода catch. Это то место, где можно обработать исключения и предпринять адекватные для него действия, например записать событие ошибки в лог, прервать работу программы, или может просто игнорировать исключение (когда блок catch пустой).
finally — блок finally позволяет выполнить какой-то определенный код приложения, если исключение сработало, или если оно не сработало.
Часто блок finally опускается, когда обработка исключения подразумевает нормальное дальнейшее выполнение программы — потому блок finally может быть просто заменен обычным кодом, который следует за блоками try и catch.
throw — ключевое слово throw используется для реального создания нового исключения, в результате чего выполнение кода попадет в соответствующие блоки catch и finally.
Пример обработки деления на ноль
Если нужный блок catch не найден, то при возникновении исключения программа аварийно завершает свое выполнение. Рассмотрим код:
int x = 5; int y = 0; int z = x / y; Console.WriteLine($"Результат: {z}"); Console.WriteLine("Конец программы"); Console.Read();
Чтобы избежать аварийного завершения программы, следует использовать для обработки исключений конструкцию try…catch:
try { int x = 5; int y = 0; int z = x / y; Console.WriteLine($"Результат: {z}"); } catch { Console.WriteLine("Возникло исключение!"); } Console.WriteLine("Конец программы"); Console.Read();
На языке C# ключевые слова try и catch используются для определения блока проверяемого кода (блок try catch). Блок try catch помещается вокруг кода, который потенциально может выбросить исключение.
Если произошло исключение, то этот блок try catch обработает исключение, гарантируя тем самым, что приложение не выдаст ошибку необработанного исключения (unhandled exception), ошибки пользователя, и эта ошибка не разрушит процесс выполнения приложения.
Обработка исключений и условные конструкции
Ряд исключительных ситуаций может быть предвиден разработчиком. Например, пусть программа предусматривает ввод числа и вывод его квадрата:
Console.WriteLine(«Введите число»);
int x = Int32.Parse(Console.ReadLine());
x *= x;
Console.WriteLine(«Квадрат числа: » + x);
Console.Read();
Если пользователь введет не число, а строку, какие-то другие символы, то программа выпадет в ошибку.
С одной стороны, здесь как раз та ситуация, когда можно применить блок try..catch, чтобы обработать возможную ошибку. Однако гораздо оптимальнее было бы проверить допустимость преобразования:
Console.WriteLine("Введите число"); int x; string input = Console.ReadLine(); if (Int32.TryParse(input, out x)) { x *= x; Console.WriteLine("Квадрат числа: " + x); } else { Console.WriteLine("Некорректный ввод"); } Console.Read();
Метод Int32.TryParse() возвращает true, если преобразование можно осуществить, и false — если нельзя.
Фильтры исключений
Фильтры исключений позволяют обрабатывать исключения в зависимости от определенных условий. Для их применения после выражения catch идет выражение when, после которого в скобках указывается условие:
catch when(условие) { }
В этом случае обработка исключения в блоке catch производится только в том случае, если условие в выражении when истинно. Например:
int x = 1; int y = 0; try { int result = x / y; } catch (DivideByZeroException) when (y == 0 && x == 0) { Console.WriteLine("y не должен быть равен 0"); } catch (DivideByZeroException ex) { Console.WriteLine(ex.Message); }
В данном случае будет выброшено исключение, так как y=0. Здесь два блока catch, и оба они обрабатывают исключения типа DivideByZeroException, то есть по сути все исключения, генерируемые при делении на ноль.
Но поскольку для первого блока указано условие y == 0 && x == 0, то оно не будет обрабатывать исключение — условие, указанное после оператора when возвращает false.
Поэтому CLR будет дальше искать соответствующие блоки catch далее и для обработки исключения выберет второй блок catch. В итоге если мы уберем второй блок catch, то исключение вообще не будет обрабатываться.
Класс Exception
Базовым для всех типов исключений является тип Exception. Этот тип определяет ряд свойств, с помощью которых можно получить информацию об исключении.
- InnerException: хранит информацию об исключении, которое послужило причиной текущего исключения;
- Message: хранит сообщение об исключении, текст ошибки;
- Source: хранит имя объекта или сборки, которое вызвало исключение;
- StackTrace: возвращает строковое представление стека вызовов, которые привели к возникновению исключения;
- TargetSite: возвращает метод, в котором и было вызвано исключение.
Например, обработаем исключения типа Exception:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch (Exception ex) { Console.WriteLine($"Исключение: {ex.Message}"); Console.WriteLine($"Метод: {ex.TargetSite}"); Console.WriteLine($"Трассировка стека: {ex.StackTrace}"); } Console.Read();
Ключевое слово finally
Если нужно определить код, который будет выполняться после выхода из блока try/catch, тогда нужно использовать блок finally. Использование этого блока гарантирует, что некоторый набор операторов будет выполняться всегда, независимо от того, возникло исключение (любого типа) или нет.
Блок finally выполняется и в том случае, если любой код в блоке try или в связанном с ним блоках catch приводит к возврату их метода. Пример:
int x = 10, y = 0, z; try { z = x / y; } catch (DivideByZeroException) { Console.WriteLine("Деление на 0"); } finally { Console.WriteLine("Конец программы"); }
Генерация исключения и оператор throw
Язык C# позволяет генерировать исключения вручную с помощью оператора throw. То есть с помощью этого оператора можно создать исключение и вызвать его в процессе выполнения.
Например, в нашей программе происходит ввод строки, и мы хотим, чтобы, если длина строки будет больше 8 символов, возникало исключение:
try { Console.Write("Введите строку: "); string message = Console.ReadLine(); if (message.Length > 8) { throw new Exception("Длина строки больше 8 символов"); } } catch (Exception e) { Console.WriteLine($"Ошибка: {e.Message}"); } Console.Read();
Подобным образом можно генерировать исключения в любом месте программы. Но существует также и другая форма использования оператора throw, когда после данного оператора не указывается объект исключения. В подобном виде оператор throw может использоваться только в блоке catch.
try { try { Console.Write("Введите строку: "); string message = Console.ReadLine(); if (message.Length > 8) { throw new Exception("Длина строки больше 8 символов"); } } catch { Console.WriteLine("Возникло исключение"); throw; } } catch (Exception ex) { Console.WriteLine(ex.Message); }