Как Правильно Создавать Собственные Исключения В C#



Для кого написана статья? Эта статья предназначена в первую очередь для новичков в мире .

NET, но также может быть полезна опытным разработчикам, которые еще не до конца поняли, как правильно создавать пользовательские исключения с помощью C#.

Пример кода для этой статьи можно скачать Здесь .



Создание простого исключения

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

NET Framework. Например, есть метод, предназначенный для изменения имени пользователя:

  
  
  
  
  
  
  
  
  
   

private static void EditUser(string oldUserName, string newUserName) { var userForEdit = GetUserByName(oldUserName); if (userForEdit == null) return; else userForEdit.Name = newUserName; }

Этот метод решит возложенные на него задачи, но не сделает одну важную вещь — не сообщит, что пользователь с данным именем отсутствует и операция по изменению его имени не завершена.

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

private static void EditUser(string oldUserNane, string newUserName) { var userForEdit = GetUserByName(oldUserName); if(userForEdit == null) throw new Exception(); else userForEdit.Name = newUserName; }

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

Создать собственное исключение несложно — вам нужно определить публичный класс, который будет наследовать от Система.

Исключение или System.ApplicationException .

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

public class UserNotFoundException : ApplicationException { }

Что лучше наследовать: System.Exception или System.ApplicationException? Каждый из этих типов предназначен для определенной цели.

Тогда как Система.

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

Например, тестовое приложение из этой статьи представляет собой отдельную программу, поэтому вполне допустимо наследовать определенное нами исключение от System.ApplicationException. Теперь вместо Exception мы будем выбрасывать созданное нами UserNotFoundException:

private static void EditUser(string oldUserNane, string newUserName) { var userForEdit = GetUserByName(oldUserName); if(userForEdit == null) throw new UserNotFoundException(); else userForEdit.Name = newUserName; }

В этом случае сообщение о возникшем исключении будет иметь вид: «Ошибка в приложении».

.

Что не очень информативно.

Чтобы гарантировать, что ваш собственный код класса исключений соответствует рекомендациям .

NET, вы должны придерживаться следующих рекомендаций:

  • класс исключения должен наследовать от Exception/ApplicationException;
  • класс должен быть помечен атрибутом [System.Serializable];
  • класс должен определить стандартный конструктор;
  • класс должен определить конструктор, который устанавливает значение унаследованного свойства Message;
  • класс должен определить конструктор для обработки «внутренних исключений»;
  • класс должен определить конструктор для поддержки сериализации типа.

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

Подробнее о том, зачем нужен конструктор для поддержки сериализации типов, под спойлером.

«Добавление дополнительных полей, их сериализация и десериализация» ниже.

Чтобы избавить программиста от необходимости писать тот же код в Visual Studio есть фрагмент "Исключение" который генерирует класс исключения, соответствующий всем рекомендациям, перечисленным выше.



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

Итак, после реализации рекомендаций наш код исключения должен выглядеть примерно так:

public class UserNotFoundException : ApplicationException { public UserNotFoundException() { } public UserNotFoundException(string message) : base(message) { } public UserNotFoundException(string message, Exception inner) : base(message, inner) { } protected UserNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } }

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

throw new UserNotFoundException("User \"" + oldUserName + "\" not found in system");

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

Добавьте дополнительное строковое поле в класс исключений:

[Serializable] public class UserNotFoundException : ApplicationException { private string _userNotFoundName; public string UserNotFoundName { get { return _userNotFoundName; } set { _userNotFoundName = value; } } public UserNotFoundException() { } public UserNotFoundException(string message) : base(message) { } public UserNotFoundException(string message, Exception inner) : base(message, inner) { } protected UserNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } }

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

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

Чтобы сериализовать поле, нам нужно переопределить метод ПолучитьОбъектДанные , описываемый интерфейсом ISerializable .

Метод ПолучитьОбъектДанные заполняет объект Информация о сериализации данные для сериализации.

Ровно в Информация о сериализации мы должны передать имя нашего поля и информацию, хранящуюся в нем:

public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("UserNotFoundName", this.UserNotFoundName); }

Метод ПолучитьОбъектДанные для базового класса вам нужно позвонить, чтобы добавить к Информация о сериализации Все наши поля исключений являются значениями по умолчанию (например, Message, TargetSite, HelpLink и т. д.).

Десериализация происходит в том же духе.

Во время десериализации будет вызван наш конструктор исключений, принявший Информация о сериализации И Потоковый контекст .

Наша задача – получить от Информация о сериализации data и запишем их в созданное нами поле:

protected UserNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { if (info != null) { this._userNotFoundName = info.GetString("UserNotFoundName"); } }

И последний штрих — добавить в XML-документацию (если вы ее, конечно, используете) информацию о том, что он может выбрасывать исключение определенного типа:

/// <exception cref="UserDefinedException.UserNotFoundException" />

Итак, наше пользовательское исключение готово к использованию.

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

Подробности под спойлером «Добавление дополнительных полей, их сериализация и десериализация» .

P.P.S: Спасибо за комментарии и здоровую критику.

Для тех, кто дочитал статью до конца, также читайте комментарии, там есть полезная информация.

Теги: #c#.

net #Exceptions #.

netframeowrk #.

NET #C++

Вместе с данным постом часто просматривают: