Define custom exception classes in C#

The example Throw standard exceptions in C# explains how to throw exceptions to tell the program about unexpected errors and the example See a hierarchy of useful exception classes for use in C# lists some useful exception classes. But what if none of those classes fits your situation? In that case, you can define your own custom exception class.

Derive your class from System.Exception or some other reasonable base class. Give it constructors that invoke the base class's constructors.

This example modifies the example Parse file sizes in KB, MB, GB, and so forth in C# so it throws a custom UnknownExtensionException if you enter a file size with an unknown quantity such as 1.23 EX. The following code shows the UnknownExtensionException class.

// Thrown if a file size contains an unknown exception.
public class UnknownExtensionException : FormatException
{
    public string Extension = "";
    public UnknownExtensionException() : base()
    {
    }
    public UnknownExtensionException(string extension) : base()
    {
        Extension = extension;
    }
    public UnknownExtensionException(string extension, string message)
        : base(message)
    {
        Extension = extension;
    }
    public UnknownExtensionException(string extension,
        string message, Exception inner_exception)
        : base(message, inner_exception)
    {
        Extension = extension;
    }
    public UnknownExtensionException(string extension,
        SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        Extension = extension;
    }
}

The class starts by defining an Extension field that will hold the unknown extension.

Next the class defines four constructors. Each takes the unknown extension as a first parameter and then other parameters that let the constructor invoke various constructors provided by the base class.

You can decide which constructors you want to provide. Usually custom exception classes should at least include one constructor that takes no parameters and another that takes a message as a parameter. A third that takes a message and inner exception as parameters is also common. A constructor that takes a SerializationInfo info and a StreamingContext makes it possible to deserialize the class.

This example complicates the constructors somewhat by including the extension parameter.

The main program uses the following code to demonstrate the UnknownExtensionException class.

// Convert a file size into bytes.
private void btnParse_Click(object sender, EventArgs e)
{
    txtBytes.Clear();
    txtCheck.Clear();

    try
    {
        double bytes = ParseFileSize(txtSize.Text, 1024);
        txtBytes.Text = bytes.ToString("N0");
        txtCheck.Text = ToFileSize(bytes);
    }
    catch (UnknownExtensionException ex)
    {
        // Tell the user it's a bad file name extension.
        MessageBox.Show("Unrecognized extension " +
            ex.Extension + ". " +
            "You must end the file size in one of: " +
            "bytes, KB, MB, GB, TB, PB, EB, ZB, or YB.");
    }
    catch (Exception ex)
    {
        // Just tell the user about this.
        MessageBox.Show(ex.Message);
    }
}

The code uses a try-catch block to look for errors. It uses a catch block to look specifically for UnknownExtensionExceptions. When it finds that kind of exception, it displays a message customized for it.

The program uses a second catch block to look for other exceptions and, if it finds one, displays the exception's message.

In this example UnknownExtensionException is derived from FormatException so a catch block that looks for FormatException would also catch UnknownExtensionException.

Some additional tips:

  • If a standard exception doesn't closely resemble what you need, build your own exception class. Don't try to force a class to do something for which it was not intended or you'll make the code confusing. By convention, exception class names should end with Exception.
  • You don't need to catch every possible error and then throw a new exception. For example, if you simply try to parse an integer and the user has entered an invalid value, then Integer.Parse will throw an appropriate exception for you.
  • That being said, you may still want to catch this exception anyway and re-throw it with more specific information. For example, the new exception might tell the user which value had an invalid format or that the value must be an integer.
  • Use exceptions only for errors not return values or status conditions. Use parameters or method return values to give the calling code status information.
  • Don't use exceptions to catch unexpected but allowable values. For example, most orders might have no more than 20 items but it is possible that one might have 21. In that case, don't use an exception because that will prevent the program from handling unusual but valid situations. Instead use Debug.Assert to catch this suspicious condition in a debug build but to continue running in a release build.
  • Microsoft recommends that you not directly throw Exception, SystemException, NullReferenceException, or IndexOutOfRangeException from your code. I agree with the first two because they are too non-specific to give good information about what the problem is. Instead, derive your own more specific exception classes. (I'm not sure what Microsoft has against the last two.)

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.