📜 ⬆️ ⬇️

How to create custom exceptions in C #

For whom the article is written


This article is primarily intended for newcomers to the .NET world, but it can also be useful for developers with experience who have not fully figured out how to properly build their user-defined exceptions using C #.

Sample code for this article can be downloaded here .

Creating a simple exception


It is recommended to create your own types of exceptions in C # in cases when you need to clearly separate the exception that occurred in the programmer’s written code from the exception that occurs in the standard .NET Framework types.

For example, there is a method designed to change the username:
')
private static void EditUser(string oldUserName, string newUserName) { var userForEdit = GetUserByName(oldUserName); if (userForEdit == null) return; else userForEdit.Name = newUserName; } 


This method will solve the tasks assigned to it, but it will not do one important thing - it will not report that the user with the specified name is missing and the operation to change his name has not been performed.

To inform about the occurrence of an exceptional situation, you can generate a standard exception, which is not a recommended practice:

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


In order to be able to easily determine that an exception is generated at the level of a specific application, you need to create your own - custom Exception, and when you receive null, throw it instead of the desired user.

Creating your own Exception is not difficult - you need to define a public class that will inherit from System.Exception or System.ApplicationException . Although this is not a good practice, the code inside the created exception class can be omitted at all:

 public class UserNotFoundException : ApplicationException { } 


What is it better to inherit from, System.Exception or System.ApplicationException?
Each of these types is designed for a specific purpose. While System.Exception is a common class for all user-defined exceptions, System.ApplicationException defines exceptions that occur at the level of a particular application.

For example, a test application from this article is a separate program, so it is perfectly acceptable to inherit the exception defined by us from System.ApplicationException.


Now, instead of Exception, we will generate the UserNotFoundException we created:

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


In this case, as a message on the occurrence of the exception will be: "Error in the application." . What is not very informative.

In order for the custom exception class code to comply with the .NET recommendations, you need to adhere to the following rules :


A little about the purpose of individual constructors: a constructor for handling “internal exceptions” is needed in order to pass into it an exception that caused the exception. In more detail, why is a constructor needed to support type serialization under the spoiler “Adding additional fields, their serialization and deserialization” below.

In order to save the programmer from having to write the same code in Visual Studio, there is a “Exception” snippet that generates an exception class that complies with all the recommendations listed above.



So, after implementing the recommendations, the code for our exception should look like this:

 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) { } } 


Now, when generating an exception, we can specify the reason for its occurrence in more detail:

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


Adding additional fields, their serialization and deserialization
Suppose we want to add to the class of our exception an additional field that stores the name of the user we wanted to find, but in the end it was not found (because of what an exception was generated). Add an additional string to the exception class:

 [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) { } } 


The problem is that the data from the field we added will not be serialized and deserialized automatically. We need to ensure that the CLR serializes and deserializes the data on our exception correctly.

To serialize the field, we must override the GetObjectData method, described by the ISerializable interface. The GetObjectData method fills the SerializationInfo object with data for serialization. It is in SerializationInfo that we have to transfer the name of our field and the information stored in it:

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


The GetObjectData method for the base class must be called in order to add all the fields of our exception to the default (such as Message, TargetSite, HelpLink, etc.) in SerializationInfo .

Deserialization takes place in a similar vein. During deserialization, the constructor of our exception will be called, which accepts SerializationInfo and StreamingContext . Our task is to get data from SerializationInfo and write it into the field we created:

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



And the final touch is adding our method to the XML documentation (if you use it, of course) that it can throw an exception of a certain type:

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


So, our user-defined exception is ready to use. You can add to it everything your heart desires: additional fields describing the state of exclusion, containing additional information, etc.

PS: Added information on how to serialize and deserialize additional fields of the exception class. Details under the spoiler "Adding additional fields, their serialization and deserialization . "

PPS: Thank you for your comments and healthy criticism. For those who read the article to the end - read also the comments, there is useful information.

Source: https://habr.com/ru/post/231585/


All Articles