How to create custom exceptions in C #

Who the article is written for


This article is intended primarily for beginners in 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 .

Create a simple exception


It is recommended to create your own exception types in C # in those cases when you need to clearly separate the exception that has arisen in the code written by the programmer 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 will not do one important thing - it will not report that the user with the given name is missing, and the operation of changing his name has not been performed.

To inform about an exceptional situation, you can generate a standard exception, which is not the 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 specific application level, you need to create your own - a custom Exception, and when you get null, throw it in place of the right 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 good practice, you can omit the code inside the generated exception class at all:

public class UserNotFoundException : ApplicationException
{
}


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

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


Now, instead of Exception, we will raise the UserNotFoundException that 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, the message about the exception will be: “Error in the application.” . Which is not very informative.

In order for custom exception class code to comply with .NET guidelines, the following rules must be followed :
  • the exception class must inherit from Exception / ApplicationException;
  • the class must be marked with the [System.Serializable] attribute;
  • the class must define a standard constructor;
  • the class must define a constructor that sets the value of the inherited Message property;
  • the class must define a constructor to handle “internal exceptions”;
  • the class must define a constructor to support type serialization.


A little about the purpose of individual constructors: a constructor for handling “internal exceptions” is needed in order to pass an exception into it, which caused this exception. For more details, why do we need a constructor 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 an Exception snippet that generates an exception class that meets all the recommendations listed above.



So, after implementing the recommendations, the code of our exception should look something 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 throwing an exception, we can indicate the reason for its occurrence in more detail:

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


Adding additional fields, serializing and deserializing them
Suppose we want to add an additional field to the class of our exception that stores the name of the user whom we wanted to find, but in the end it was not found (which is why the exception was actually generated). Add an additional string field 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 must make sure that the CLR serializes and deserializes the data for 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 must pass 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 to the SerializationInfo all the fields of our exception by default (such as Message, TargetSite, HelpLink, etc.).

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

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



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

/// 


So, our user-defined exception is ready for use. You can add to it whatever your heart desires: additional fields describing the exception state, 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. Those who read the article to the end - read also the comments, there is useful information.

Also popular now: