You are allowed to implement custom exceptions that can be thrown just like any other exception. This makes sense when you want to make your exceptions distinguishable from other errors during runtime.

In this example we will create a custom exception for clear handling of problems the application may have while parsing a complex input.

Creating Custom Exception Class

To create a custom exception create a sub-class of Exception:

public class ParserException : Exception
{
    public ParserException() : 
      base("The parsing went wrong and we have no additional information.") { }
}

Custom exception become very useful when you want to provide additional information to the catcher:

public class ParserException : Exception
{
    public ParserException(string fileName, int lineNumber) : 
      base($"Parser error in {fileName}:{lineNumber}") 
    {
      FileName = fileName;
      LineNumber = lineNumber;
    }
    public string FileName {get; private set;}
    public int LineNumber {get; private set;}    
}

Now, when you catch(ParserException x) you will have additional semantics to fine-tune exception handling.

Custom classes can implement the following features to support additional scenarios.

re-throwing

During the parsing process, the original exception is still of interest. In this example it is a FormatException because the code attempts to parse a piece of string, which is expected to be a number. In this case the custom exception should support the inclusion of the ‘InnerException’:

//new constructor:
ParserException(string msg, Exception inner) : base(msg, inner) {
}

serialization

In some cases your exceptions may have to cross AppDomain boundaries. This is the case if your parser is running in its own AppDomain to support hot reloading of new parser configurations. In Visual Studio, you can use Exception template to generate code like this.

[Serializable]
public class ParserException : Exception
{
    // Constructor without arguments allows throwing your exception without
    // providing any information, including error message. Should be included
    // if your exception is meaningful without any additional details. Should
    // set message by calling base constructor (default message is not helpful).
    public ParserException()
        : base("Parser failure.")
    {}

		// Constructor with message argument allows overriding default error message.
    // Should be included if users can provide more helpful messages than
    // generic automatically generated messages.
    public ParserException(string message) 
        : base(message)
    {}

		// Constructor for serialization support. If your exception contains custom
    // properties, read their values here.
    protected ParserException(SerializationInfo info, StreamingContext context) 
        : base(info, context)
    {}
}

Using the ParserException

try
{
    Process.StartRun(fileName)
}
catch (ParserException ex)
{
    Console.WriteLine($"{ex.Message} in ${ex.FileName}:${ex.LineNumber}");
}
catch (PostProcessException x) 
{
    ...
}

You may also use custom exceptions for catching and wrapping exceptions. This way many different errors can be converted into a single error type that is more useful to the application:

try
{
    int foo = int.Parse(token);
}
catch (FormatException ex)
{
    //Assuming you added this constructor
    throw new ParserException(
      $"Failed to read {token} as number.", 
      FileName, 
      LineNumber, 
      ex);
}

When handling exceptions by raising your own custom exceptions, you should generally include a reference the original exception in the InnerException property, as shown above.