Best Practices for Handling Exceptions

Microsoft Silverlight will reach end of support after October 2021. Learn more.

A well-designed set of error handling code blocks can make a program more robust and less prone to crashing because the application handles such errors. The following list contains suggestions on best practices for handling exceptions:

  • Know when to set up a try/catch block. For example, you can programmatically check for a condition that is likely to occur without using exception handling. In other situations, using exception handling to catch an error condition is appropriate.

    The following example uses an if statement to check whether a connection is closed. You can use this method instead of throwing an exception if the connection is not closed.

       If conn.State <> ConnectionState.Closed Then
          conn.Close()
       End If
    
       if(conn.State != ConnectionState.Closed)
          conn.Close();
    

    In the following example, an exception is thrown if the connection is not closed.

       Try
          conn.Close()
       Catch ex As InvalidOperationException
          'Do something with the error or ignore it.
       End Try
    
       try {
         conn.Close();
       }
       catch(InvalidOperationException ex) {
         //Do something with the error or ignore it.
       }
    

    The method you choose depends on how often you expect the event to occur. If the event is truly exceptional and is an error (such as an unexpected end-of-file), using exception handling is better because less code is executed in the normal case. If the event happens routinely, using the programmatic method to check for errors is better. In this case, if an exception occurs, the exception will take longer to handle.

  • Use try/finally blocks around code that can potentially generate an exception and centralize your catch statements in one location. In this way, the try statement generates the exception, the finally statement closes or deallocates resources, and the catch statement handles the exception from a central location.

  • Always order exceptions in catch blocks from the most specific to the least specific. This technique handles the specific exception before it is passed to a more general catch block.

  • End exception class names with the word "Exception". For example:

    Public Class EmployeeListNotFoundException
        Inherits Exception
    
    public class MyFileNotFoundException : Exception {
    }
    
  • When creating user-defined exceptions, you must ensure that the metadata for the exceptions is available to code executing remotely, including when exceptions occur across application domains. For example, suppose Application Domain A creates Application Domain B, which executes code that throws an exception. For Application Domain A to properly catch and handle the exception, it must be able to find the assembly containing the exception thrown by Application Domain B. If Application Domain B throws an exception that is contained in an assembly under its application base, but not under Application Domain A's application base, Application Domain A will not be able to find the exception and the common language runtime will throw a FileNotFoundException. To avoid this situation, you can deploy the assembly containing the exception information in two ways:

    • Put the assembly into a common application base shared by both application domains

      - or -

    • If the domains do not share a common application base, sign the assembly containing the exception information with a strong name and deploy the assembly into the global assembly cache.

  • In C# and C++, use at least the three common constructors when creating your own exception classes. For an example, see How to: Create User-Defined Exceptions.

  • In most cases, use the predefined exceptions types. Define new exception types only for programmatic scenarios. Introduce a new exception class to enable a programmer to take a different action in code based on the exception class.

  • For most applications, derive custom exceptions from the Exception class.

  • Include a localized description string in every exception. When the user sees an error message, it is derived from the description string of the exception that was thrown, rather than from the exception class.

  • Use grammatically correct error messages, including ending punctuation. Each sentence in a description string of an exception should end in a period.

  • Provide Exception properties for programmatic access. Include extra information in an exception (in addition to the description string) only when there is a programmatic scenario where the additional information is useful.

  • Return null for extremely common error cases. For example, Open returns null if the file is not found, but throws an exception if the file is locked.

  • Design classes so that an exception is never thrown in normal use. For example, a FileStream class exposes another way of determining whether the end of the file has been reached. This avoids the exception that is thrown if you read past the end of the file. The following example shows how to read to the end of the file.

    Class FileRead
    
        Public Sub Open(ByVal fileToRead As FileStream)
    
            ' This If statement is optional
            ' as it is very unlikely that
            ' the stream would ever be null
            If IsDBNull(fileToRead) Then
                Throw New System.ArgumentNullException()
            End If
    
            Dim b As Integer
    
            ' Set the stream position to the beginning of the file.
            fileToRead.Seek(0, SeekOrigin.Begin)
    
            ' Read each byte to the end of the file.
            For i As Integer = 0 To fileToRead.Length
                b = fileToRead.ReadByte()
                ' Do something else with the byte.
            Next
        End Sub
    
    End Class
    
    class FileRead {
        public void Open(FileStream fileToRead) 
        {
    
            // This if statement is optional
            // as it is very unlikely that
            // the stream would ever be null.
            if (fileToRead == null)
            {
                throw new System.ArgumentNullException();
            }
    
            int b;
    
            // Set the stream position to the beginning of the file.
            fileToRead.Seek(0, SeekOrigin.Begin);
    
            // Read each byte to the end of the file.
            for (int i = 0; i < fileToRead.Length; i++)
            {
                b = fileToRead.ReadByte();
                // Do something else with the byte.
            }
        }
    } 
    
  • Throw an InvalidOperationException if a property set or method call is not appropriate given the object's current state.

  • Throw an ArgumentException or a class derived from ArgumentException if invalid parameters are passed.

  • The stack trace begins at the statement where the exception is thrown and ends at the catch statement that catches the exception. Be aware of this fact when deciding where to place a throw statement.

  • Use exception builder methods. It is common for a class to throw the same exception from different places in its implementation. To avoid excessive code, use helper methods that create the exception and return it. For example:

    Class File
       Private fileName As String
    
       Public Function Read(bytes As Integer) As Byte()
          If Not ReadFile(handle, bytes) Then
             Throw NewFileIOException()
          End If
       End Function 'Read
    
       Function NewFileIOException() As FileException
          Dim description As String = __unknown ' Build localized string, including fileName.
          Return New FileException(description) '
       End Function 'NewFileIOException
    End Class 'File
    
    class File {
        string fileName;
        public byte[] Read(int bytes) {
            if (!ReadFile(handle, bytes))
                throw NewFileIOException();
        }
        FileException NewFileIOException() {
            string description = // Build localized string, including fileName.
            return new FileException(description);
         }
    }
    

    Alternatively, use the exception's constructor to build the exception. This is more appropriate for global exception classes, such as ArgumentException.

  • Throw exceptions instead of returning an error code or HRESULT.

  • Clean up intermediate results when throwing an exception. Callers should be able assume that there are no side effects when an exception is thrown from a method.

See Also

Other Resources