One of the things that pops up over and over in project development is the issue of when to create Custom Exceptions.
To come to any form of conclusion it’s necessary to understand the reason why you might create your own Exception class.
1. To allow typesafe error detection and act appropriately.
2. To add context specific data.
3. There’s no existing framework exception that signifies the error.
1. To allow typesafe error detection.
Creating a customized exception allows the receiver to act upon a specific error. Using this approach the receiver is forced to detect that specific error to perform any useful action.
Examples where creating a custom exception for this scenario to make sense include;
- situations where you may need to prompt a user to correct a problem (e.g. disk full exception)
or
- where you need to log specific exceptions.
2. To add context specific data.
By adding more context to an exception, the receiver of the exception can use this information to help track down and recover from the error.
An example; say the validation rules for an object instance fail, by attaching the validation rules to the exception the receiver may use them to display appropriate error messages on screen to help recover from the error.
E.g.
public class ValidationException : Exception { public ValidationException(ValidationResults results) : base(results.ToString()) { Results = results; } public ValidationResults Results {get;set;} } public class ValidationResults : IEnumerable<KeyValuePair<string,string>> { public IEnumerator<KeyValuePair<string, string>> GetEnumerator() { ... } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } Usage: try { ... ... } catch (ValidationException ve) { foreach (var result in ve.Results) { Console.WriteLine("Error:" + result.Key + " : " + result.Value); } }Alternately, the System.Exception class contains a Data Dictionary property that can be used to attach context without having to create your own customized exception class:
E.g.
var exception = new InvalidOperationException("ValidationException"); var validationResults = new Dictionary<string, string>(); validationResults["Name"] = "The Name value is too long."; validationResults["Postcode"] = "The Postcode is invalid."; exception.Data.Add("ValidationResults", validationResults); throw exception;The lack of type safety in the Data dictionary may be off-putting but using extension methods you can add some type safety.
public static IDictionary<string, string> GetValidationResults(this InvalidOperationException ioe) { if (ioe.Data.Contains("ValidationResults")) { return ioe.Data["ValidationResults"] as Dictionary<string,string>; } return new Dictionary<string, string>(); } public static string GetValidationMessage(this InvalidOperationException ioe, string key) { if (ioe.Data.Contains("ValidationResults")) { return ((IDictionary<string, string>) ioe.Data["ValidationResults"])[key]; } return null; }I find that using the Data dictionary approach may save you the benefit of creating a Custom Exception but at the expense of more verbose and un-intuitive code.
3. There’s no existing framework exception that signifies the error.
The detail level to which you can specify an exception type is infinite. When it comes down to it there are few framework exception types that are valid for most scenarios:
Input value causes exception; throw one of
System.ArgumentException
System.ArgumentNullException
System.ArgumentOutOfRangeExceptionFor all other errors a good default option is to use the System.InvalidOperationException.
Conclusion:
To sum up, think carefully about the specific scenario that is being addressed when writing exception logic, follow this checklist to help in your decision making.
Is there a need to act upon the specific exception and find a way to recover from it?
Yes -> Consider creating a generic custom exception.
For example create an exception named DuplicateEntityException rather than JobRateAlreadyExistsException.No -> throw an existing exception type.
Do you need to log a specific error scenario?
Yes -> Consider creating a generic custom exception.
No -> throw an existing exception type.Do you need extra information in the exception to respond effectively?
Yes -> Determine whether creating a custom exception with typesafe data members is more intuitive than using the Data dictionary on the System.Exception class.
No -> throw an existing exception type.For all other scenarios default to throwing an existing exception type.
What are your thoughts and experiences on this issue?
