javascriptjavaphprubydotnet

Throwing Exceptions in .NET C#

Types of exceptions and errors

Exceptions happen on logic errors when an operation is invalid or impossible, so the normal flow of execution is aborted. If the exception is not handled, it will crash the application. For example, a QA person may be able to find bad input or a combination of bad input and logic flaws that causes an exception. Any unhandled exception is considered a bug, but even handled events can affect user experience if they are done incorrectly. Regardless of the type of bug, it’s a developer’s goal to handle any of them gracefully so the user is able to continue using the application without losing data or abruptly crashing the program.

Developers want to avoid uncaught exceptions the most. These errors crash an application and interrupt user workflow. Because they’re unhandled, a user loses data if any is unsaved; these errors can even cause corruption with saved data. For instance, if the database doesn’t handle errors in workflow, orphaned records could occur and users are forced to contact customer support over issues that can’t be remedied on the front end.

Exceptions vs errors

You should know when to throw an exception versus an error. Throwing an exception means that you raise an event that interrupts the normal program execution. However, some workflows don’t need an exception thrown. Errors could be coded as regular function return values. For instance, a user entering the wrong URL in a browser is an error, not an exception.

For example, let’s assume that you have an integer passed to a method that determines if that integer is greater or less than zero. By default, .NET does not allow null integers (you can specify, however, that an integer is nullable), so a number will either be less than or greater than zero. By passing an integer to a method, you could log an error event if the value passed is set to zero. The assumption would always be that the integer is a whole number, so passing a zero value is not an exceptional event but rather a disallowed value.

When operations cannot be executed, developers throw an exception. Now, take a method that determines the length of a generic List<>. This method takes a List object and gets the number of values using the Count method. But, what happens when the List is null? Performing a method call on an object assumes that the object is not null. This assumption should throw an exception to indicate that there is a logic flow error in the application. Try/catch statements catch these logic flow run-time errors.

Standard exception types

.NET has some standard, common exceptions that you can handle and monitor. The following are some common base exception classes you will find in application debugging.

  • ApplicationException: Serves as a base class for application-defined exceptions.
  • InvalidOperationException: Occurs when a method call is invalid for the object’s current state.
  • ArgumentException, ArgumentNullException, and ArgumentOutOfRangeException: Thrown when the wrong object type is given to a method.
  • NullReferenceException: Thrown when a null object is referenced.
  • IndexOutOfRangeException: Thrown when the application attempts to reference an array variable outside of its index bounds.
  • AccessViolationException: Thrown when the application attempts to read or write to protected memory.
  • StackOverflowException: Thrown when the execution stack overflows due to too many nested method calls.
  • OutOfMemoryException: Thrown when the application is out of memory for execution.

User-defined exceptions

Developers aren’t stuck with .NET exceptions. Developers can create their own exceptions customized for an application. User-defined exceptions are derived from the base class Exception. The following is an example of a user-defined exception called CustomerOrderNullException.

using System;
public class CustomerOrderNullException : Exception
{
	public CustomerOrderNullException ()
	{
	}

	public CustomerOrderNullException (string message) : base(message)
	{
	}

	public CustomerOrderNullException (string message, Exception inner) : base(message, inner)
	{
	}
}

Examples of throwing exceptions

.NET's try/catch blocks will catch exceptions. You should "catch" specific errors and avoid using the general Exception class. The following is an example of catching an array reference out of its assigned boundaries.

using System;
public string GetArrayValue (string[] array, int index)
{
	try
	{
		return array[index];
	}
	catch (IndexOutOfRangeException ex)
	{
		ArgumentException newEx = new ArgumentException("Index is out of assigned range.", "index", ex);
		throw newEx;
	}
}

In the method above, a string array and index value are passed to GetArrayValue. If the index integer is higher than the array bounds, an exception will happen. Since the return statement happens within a try/catch block, the try statement handles the exception and passes the error to the “catch” section. The exception is an IndexOutOfRangeException, so the catch section will execute. The new exception created displays the message “Index is out of assigned range.” The second parameter is a string that describes the part of the statement that caused the error, then the inner exception is passed to the constructor.

Here is another example using file IO operations.

using System;
using System.IO;
class FileLogs
{
	FileStream logFile = null;
	void OpenFile(FileInfo fileName, FileMode mode) {}

	void WriteFile()
	{
		if (!this.logFile.CanWrite)
		{
			throw new System.InvalidOperationException("Cannot write to logFile");
		}
	}
}

The class above is used to write to a log file, but you can’t assume that the file is writeable. If the file does not have write permission, the rest of your code will fail. The WriteFile() method checks that the file can be written to and throws an exception if it can’t. Normal program execution can run after the exception is thrown; you’ll avoid causing an unexpected exception if you attempt to write to the log file and cause your program to crash.

Best practices for exceptions and errors

You should use exceptions when it makes sense, such as when you encounter logic errors that are an unexpected event. Here are some best practices for errors and exceptions.

  1. Use try/catch/finally blocks to handle exceptions, and rather than surrounding all your code, use fine enough granularity to recovery gracefully.

  2. Avoid unhandled exceptions. When you design classes, specify why types of exceptions each method can throw so that users of those classes can account for problems ahead of time.

  3. Monitor errors and exceptions in your code so you have visibility to problems that affect user experience.

  4. Create user-friendly error messages. By default, the user sees the Exception.Message string. Display either user-friendly messages or skip to another alternative section of the workflow.