A minimal custom exception definition:
public class CustomException : Exception // inherit from Exception rather than from ApplicationException { public CustomException() { } public CustomException(string message) : base(message) { } public CustomException(string message, Exception innerException) : base(message, innerException) { } }
A custom exception that overrides the Message property:
public class CustomException : Exception { // Override the Exception.Message property. public override string Message { get { StringBuilder sb = new StringBuilder(); sb.Append("Time: " + DateTime.Now.ToString("f") + Environment.NewLine); // long date + short time sb.Append("Type: " + this.GetType().ToString() + Environment.NewLine); sb.Append("Message: " + base.Message + Environment.NewLine); if (this.InnerException != null) sb.Append("Inner Message: " + this.InnerException.Message + Environment.NewLine); return sb.ToString(); } } public CustomException() { } public CustomException(string message) : base(message) { } public CustomException(string message, Exception innerException) : base(message, innerException) { } }
A method that iterates over all inner exceptions and returns their messages and types:
public string GetExceptionDesc(Exception exc) { StringBuilder sb = new StringBuilder(); sb.AppendLine("*** EXCEPTION ***"); sb.AppendLine("DateTime: " + DateTime.Now.ToString()); sb.AppendLine("Message: " + exc.Message); sb.AppendLine("Type: " + exc.GetType().ToString()); sb.AppendLine("StackTrace: " + exc.StackTrace); AppendInnerExceptions(exc.InnerException, sb, 1); return sb.ToString(); } private void AppendInnerExceptions(Exception exc, StringBuilder sb, int level) { if (exc != null) { sb.AppendLine(String.Format("InnerException-{0} Message: {1}", level.ToString(), exc.Message)); sb.AppendLine(String.Format("InnerException-{0} Type: {1}", level.ToString(), exc.GetType().Name)); AppendInnerExceptions(exc.InnerException, sb, ++level); } }
A custom exception that implements ISerializable:
// The SerializableAttribute ensures that the custom exception // can be serialized and works correctly across application domains // (for example, when a web service returns an exception). [Serializable] public class CustomException : Exception { // Custom properties for serialization. public int ExceptionNumber { get; private set; } public string MethodName { get; private set; } // A constructor accepting values for custom properties. public CustomException(string message, Exception innerException, int exceptionNumber, string methodName) : base(message, innerException) { this.ExceptionNumber = exceptionNumber; this.MethodName = methodName; } #region ISerializable Implementation protected CustomException(SerializationInfo info, StreamingContext context) : base(info, context) { // Get the custom property out of the serialization stream and set the object's property. ExceptionNumber = info.GetInt32("ExceptionNumber"); MethodName = info.GetString("MethodName"); } // The GetObjectData method is called on serialization. public override void GetObjectData(SerializationInfo info, StreamingContext context) { // Add the custom property into the serialization stream. info.AddValue("ExceptionNumber", ExceptionNumber); info.AddValue("MethodName", MethodName); // Call the base exception class to ensure proper serialization. base.GetObjectData(info, context); } #endregion }
An exception builder is a helper method that creates and throws exceptions. A method that calls the exception builder is inlined by the compiler:
public class CustomException : Exception { public CustomException(string message) : base(message) { } } // Enum values representing errors. public enum ErrorCode { NotInitialized, InvalidArgument, TimeOut } ... // An exception builder method that creates and throws CustomException. private static void ThrowException(ErrorCode code) { string message = ""; switch (code) { case ErrorCode.NotInitialized: message = "Component is not initialized ..."; break; case ErrorCode.InvalidArgument: message = "Invalid argument passed ..."; break; case ErrorCode.TimeOut: message = "Time-out ocurred ..."; break; default: message = "Generic exception."; break; } throw new CustomException(message); } ... // This method builds exceptions using the exception builder. // As it does not use 'throw' to throw exceptions, it is inlined by the compiler. public void DoSomething() { // ... ThrowException(ErrorCode.NotInitialized); // ... }
An example of implementing a SoapException wrapper:
using System; using System.Text; using System.Web.Services.Protocols; using System.Xml; ... public class SoapExceptionWrapper { private long code; private string description; private string type; public long Code { get { return code; } } public string Description { get { return description; } } public string Type { get { return type; } } public SoapExceptionWrapper(SoapException exc) { if (exc.Detail != null && exc.Detail.InnerXml != "") { XmlDocument doc = new XmlDocument(); doc.LoadXml(exc.Detail.InnerXml); if (doc.DocumentElement != null && doc.DocumentElement.Name == "error") { this.code = Convert.ToInt64(doc.DocumentElement.SelectSingleNode("/error/code").InnerText, 16); this.description = doc.DocumentElement.SelectSingleNode("/error/description").InnerText; this.type = doc.DocumentElement.SelectSingleNode("/error/type").InnerText; } else { this.code = 0; this.description = "No error"; } } else if (exc.GetBaseException() != null) { this.code = 0; this.description = exc.GetBaseException().Message; this.type = exc.GetBaseException().GetType().ToString(); } } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append("SoapException Details:" + Environment.NewLine); sb.Append(" Code: " + this.code + Environment.NewLine); sb.Append(" Description: " + this.description + Environment.NewLine); sb.Append(" Type: " + this.type + Environment.NewLine); return sb.ToString(); } }
Exception types that are suitable to throw in custom code:
Exception | The base class for all exceptions. Throw a more specific type of exception rather than this one. |
---|---|
ArgumentException | Throw this exception when an argument to your method is invalid. |
ArgumentNullException | A specialized form of ArgumentException that you can throw when one of your arguments is null and this isn't allowed. |
ArgumentOutOfRangeException | A specialized form of ArgumentException that you can throw when an argument is outside the allowable range of values. |
FormatException | Throw this exception when an argument does not have a valid format. |
InvalidOperationException | Throw this exception when a method is called that is invalid for the object's current state. |
NotImplementedException | This exception is often used in generated code where a method has not been implemented yet. |
NotSupportedException | Throw this exception when a method is invoked that you don't support. |
ObjectDisposedException | Throw when a user of your class tries to access methods when Dispose has already been called. |
Exception types that should not be thrown in custom code:
ArithmeticException | A base class for other exceptions that occur during arithmetic operations. |
---|---|
ArrayTypeMismatchException | Thrown when you want to store an incompatible element inside an array. |
DivideByZeroException | Thrown when you try to divide a value by zero. |
IndexOutOfRangeException | Thrown when you try to access an array with an index that is less than zero or greater than the size of the array. |
InvalidCastException | Thrown when you try to cast an element to an incompatible type. |
NullReferenceException | Thrown when you try to reference an element that is null. |
OutOfMemoryException | Thrown when creating a new object fails because the CLR doesn't have enough memory available. |
OverflowException | Thrown when an arithmetic operation overflows in a checked context. |
StackOverflowException | Thrown when the execution stack is full. |
TypeInitializationException | Thrown when a static constructor throws an exception that is unhandled. |
Report an error in the iterator code as soon as possible. We use a technique described in [Source: “More Effective C#” by Bill Wagner]: we split the iterator method into two methods:
yield return
// The wrapper method performs the argument and state validation. public IEnumerable<T> GenerateSample<T>(IEnumerable<T> sequence, int sampleFrequency) { if (sequence == null) throw new ArgumentNullException(paramName: nameof(sequence), message: "..."); if (sampleFrequency < 1) throw new ArgumentException(paramName: nameof(sampleFrequency), message: "..."); return GenerateSampleImpl(); // The implementation method performs the actual work. We limit the accessibility of this method // by making it a local function (introduced in C# 7). This way we ensure that there is no way to // bypass the validation code and call the implementation method directly. Notice that the implementation // method has access to all the local variables of the the wrapper method. IEnumerable<T> GenerateSampleImpl() { int index = 0; foreach (T item in sequence) { if (index % sampleFrequency == 0) yield return item; } } }