Exceptions are used to indicate that an error has occurred while running the program. Exception objects that describe an error are created and then thrown with the throw statement or expression. The runtime then searches for the most compatible exception handler.
Programmers should throw exceptions when one or more of the following conditions are true:
- The method can't complete its defined functionality. For example, if a parameter to a method has an invalid value:
sample · csharpdocs/csharp/fundamentals/exceptions/snippets/exceptions/Program.cs
using System;
using System.IO;
using exceptions;
namespace Exceptions
{
class Program
{
static async Task Main(string[] args)
{
var toast = await KitchenHelpers.ToastBreadAsync(2, 100);
ExceptionTest.Main();
TestThrowCatch();
CatchOrder.Main();
TestFinally();
}
private static void TestThrowCatch()
{
// <CatchException>
try
{
TestThrow();
}
catch (CustomException ex)
{
System.Console.WriteLine(ex.ToString());
}
// </CatchException>
}
// <ThrowException>
class CustomException : Exception
{
public CustomException(string message)
{
}
}
private static void TestThrow()
{
throw new CustomException("Custom exception in TestThrow()");
}
// </ThrowException>
// <TestFinally>
static void TestFinally()
{
FileStream? file = null;
//Change the path to something that works on your machine.
FileInfo fileInfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
file?.Close();
}
try
{
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
}
catch (IOException)
{
Console.WriteLine("OpenWrite() failed");
}
}
// </TestFinally>
class SomeSpecificException : Exception { }
private static void Structure()
{
//<TryCatchStructure>
try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
// Only catch exceptions that you know how to handle.
// Never catch base class System.Exception without
// rethrowing it at the end of the catch block.
}
//</TryCatchStructure>
// <TryFinallyStructure>
try
{
// Code to try goes here.
}
finally
{
// Code to execute after the try block goes here.
}
// </TryFinallyStructure>
// <TryCatchFinallyStructure>
try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
}
finally
{
// Code to execute after the try (and possibly catch) blocks
// goes here.
}
// </TryCatchFinallyStructure>
}
// <ThrowMoreSpecificException>
int GetInt(int[] array, int index)
{
try
{
return array[index];
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException(
"Parameter index is out of range.", e);
}
}
// </ThrowMoreSpecificException>
private static void ReThrow()
{
// <RethrowError>
try
{
// Try to access a resource.
}
catch (UnauthorizedAccessException e)
{
// Call a custom error logging procedure.
LogError(e);
// Re-throw the error.
throw;
}
// </RethrowError>
}
static void LogError(Exception ex) { }
static void CloseOptionally()
{
//<CleanupIfNotNull>
FileStream? file = null;
FileInfo fileinfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileinfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Check for null because OpenWrite might have failed.
file?.Close();
}
//</CleanupIfNotNull>
}
// <CantComplete>
static void CopyObject(SampleClass original)
{
_ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
}
// </CantComplete>
// <InvalidArg>
static int GetValueFromArray(int[] array, int index)
{
try
{
return array[index];
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException(
"Parameter index is out of range.", e);
}
}
// </InvalidArg>
// <NoCleanup>
static void CodeWithoutCleanup()
{
FileStream? file = null;
FileInfo fileInfo = new FileInfo("./file.txt");
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
file.Close();
}
// </NoCleanup>
// <WithCleanup>
static void CodeWithCleanup()
{
FileStream? file = null;
FileInfo? fileInfo = null;
try
{
fileInfo = new FileInfo("./file.txt");
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
}
finally
{
file?.Close();
}
}
// </WithCleanup>
}
public class SampleClass { }
}- An inappropriate call to an object is made, based on the object state. One example might be trying to write to a read-only file. In cases where an object state doesn't allow an operation, throw an instance of System.InvalidOperationException or an object based on a derivation of this class. The following code is an example of a method that throws an System.InvalidOperationException object:
sample · csharpdocs/csharp/fundamentals/exceptions/snippets/exceptions/ProgramLog.cs
using System;
using System.IO;
namespace Exceptions
{
// <ProgramLog>
public class ProgramLog
{
FileStream logFile = null!;
public void OpenLog(FileInfo fileName, FileMode mode) { }
public void WriteLog()
{
if (!logFile.CanWrite)
{
throw new InvalidOperationException("Logfile cannot be read-only");
}
// Else write data to the log and return.
}
}
// </ProgramLog>
}- When an argument to a method causes an exception. In this case, the original exception should be caught and an System.ArgumentException instance should be created. The original exception should be passed to the constructor of the System.ArgumentException as the System.Exception.InnerException parameter:
sample · csharpdocs/csharp/fundamentals/exceptions/snippets/exceptions/Program.cs
using System;
using System.IO;
using exceptions;
namespace Exceptions
{
class Program
{
static async Task Main(string[] args)
{
var toast = await KitchenHelpers.ToastBreadAsync(2, 100);
ExceptionTest.Main();
TestThrowCatch();
CatchOrder.Main();
TestFinally();
}
private static void TestThrowCatch()
{
// <CatchException>
try
{
TestThrow();
}
catch (CustomException ex)
{
System.Console.WriteLine(ex.ToString());
}
// </CatchException>
}
// <ThrowException>
class CustomException : Exception
{
public CustomException(string message)
{
}
}
private static void TestThrow()
{
throw new CustomException("Custom exception in TestThrow()");
}
// </ThrowException>
// <TestFinally>
static void TestFinally()
{
FileStream? file = null;
//Change the path to something that works on your machine.
FileInfo fileInfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
file?.Close();
}
try
{
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
}
catch (IOException)
{
Console.WriteLine("OpenWrite() failed");
}
}
// </TestFinally>
class SomeSpecificException : Exception { }
private static void Structure()
{
//<TryCatchStructure>
try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
// Only catch exceptions that you know how to handle.
// Never catch base class System.Exception without
// rethrowing it at the end of the catch block.
}
//</TryCatchStructure>
// <TryFinallyStructure>
try
{
// Code to try goes here.
}
finally
{
// Code to execute after the try block goes here.
}
// </TryFinallyStructure>
// <TryCatchFinallyStructure>
try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
}
finally
{
// Code to execute after the try (and possibly catch) blocks
// goes here.
}
// </TryCatchFinallyStructure>
}
// <ThrowMoreSpecificException>
int GetInt(int[] array, int index)
{
try
{
return array[index];
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException(
"Parameter index is out of range.", e);
}
}
// </ThrowMoreSpecificException>
private static void ReThrow()
{
// <RethrowError>
try
{
// Try to access a resource.
}
catch (UnauthorizedAccessException e)
{
// Call a custom error logging procedure.
LogError(e);
// Re-throw the error.
throw;
}
// </RethrowError>
}
static void LogError(Exception ex) { }
static void CloseOptionally()
{
//<CleanupIfNotNull>
FileStream? file = null;
FileInfo fileinfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileinfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Check for null because OpenWrite might have failed.
file?.Close();
}
//</CleanupIfNotNull>
}
// <CantComplete>
static void CopyObject(SampleClass original)
{
_ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
}
// </CantComplete>
// <InvalidArg>
static int GetValueFromArray(int[] array, int index)
{
try
{
return array[index];
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException(
"Parameter index is out of range.", e);
}
}
// </InvalidArg>
// <NoCleanup>
static void CodeWithoutCleanup()
{
FileStream? file = null;
FileInfo fileInfo = new FileInfo("./file.txt");
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
file.Close();
}
// </NoCleanup>
// <WithCleanup>
static void CodeWithCleanup()
{
FileStream? file = null;
FileInfo? fileInfo = null;
try
{
fileInfo = new FileInfo("./file.txt");
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
}
finally
{
file?.Close();
}
}
// </WithCleanup>
}
public class SampleClass { }
}Exceptions contain a property named System.Exception.StackTrace. This string contains the name of the methods on the current call stack, together with the file name and line number where the exception was thrown for each method. A System.Exception.StackTrace object is created automatically by the common language runtime (CLR) from the point of the throw statement, so that exceptions must be thrown from the point where the stack trace should begin.
All exceptions contain a property named System.Exception.Message. This string should be set to explain the reason for the exception. Information that is sensitive to security shouldn't be put in the message text. In addition to System.Exception.Message, System.ArgumentException contains a property named System.ArgumentException.ParamName that should be set to the name of the argument that caused the exception to be thrown. In a property setter, System.ArgumentException.ParamName should be set to value.
Public and protected methods throw exceptions whenever they can't complete their intended functions. The exception class thrown is the most specific exception available that fits the error conditions. These exceptions should be documented as part of the class functionality, and derived classes or updates to the original class should retain the same behavior for backward compatibility.
Things to consider when throwing exceptions
The following list identifies practices to consider when throwing exceptions:
- Don't use exceptions to change the flow of a program as part of ordinary execution. Use exceptions to report and handle error conditions.
- Exceptions shouldn't be returned as a return value or parameter instead of being thrown.
- Don't throw System.Exception, System.SystemException, System.NullReferenceException, or System.IndexOutOfRangeException intentionally from your own source code.
- Don't create exceptions that can be thrown in debug mode but not release mode. To identify run-time errors during the development phase, use Debug Assert instead.
Exceptions in task-returning methods
Methods declared with the async modifier have some special considerations when it comes to exceptions. Exceptions thrown in an async method are stored in the returned task and don't emerge until, for example, the task is awaited. For more information about stored exceptions, see Asynchronous exceptions.
We recommend that you validate arguments and throw any corresponding exceptions, such as System.ArgumentException and System.ArgumentNullException, before entering the asynchronous parts of your methods. That is, these validation exceptions should emerge synchronously before the work starts. The following code snippet shows an example where, if the exceptions are thrown, the System.ArgumentException exceptions would emerge synchronously, whereas the System.InvalidOperationException would be stored in the returned task.
sample · csharpdocs/csharp/fundamentals/exceptions/snippets/exceptions/TaskExceptions.cs
namespace exceptions;
class KitchenHelpers
{
public class Toast { }
// <TaskExceptions>
// Non-async, task-returning method.
// Within this method (but outside of the local function),
// any thrown exceptions emerge synchronously.
public static Task<Toast> ToastBreadAsync(int slices, int toastTime)
{
if (slices is < 1 or > 4)
{
throw new ArgumentException(
"You must specify between 1 and 4 slices of bread.",
nameof(slices));
}
if (toastTime < 1)
{
throw new ArgumentException(
"Toast time is too short.", nameof(toastTime));
}
return ToastBreadAsyncCore(slices, toastTime);
// Local async function.
// Within this function, any thrown exceptions are stored in the task.
static async Task<Toast> ToastBreadAsyncCore(int slices, int time)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
// Start toasting.
await Task.Delay(time);
if (time > 2_000)
{
throw new InvalidOperationException("The toaster is on fire!");
}
Console.WriteLine("Toast is ready!");
return new Toast();
}
}
// </TaskExceptions>
}Define exception classes
Programs can throw a predefined exception class in the System namespace (except where previously noted), or create their own exception classes by deriving from System.Exception. The derived classes should define at least three constructors: one parameterless constructor, one that sets the message property, and one that sets both the System.Exception.Message and System.Exception.InnerException properties. For example:
sample · csharpdocs/csharp/fundamentals/exceptions/snippets/exceptions/InvalidDepartmentException.cs
using System;
namespace Exceptions
{
// <DefineExceptionClass>
[Serializable]
public class InvalidDepartmentException : Exception
{
public InvalidDepartmentException() : base() { }
public InvalidDepartmentException(string message) : base(message) { }
public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }
}
// </DefineExceptionClass>
}Add new properties to the exception class when the data they provide is useful to resolving the exception. If new properties are added to the derived exception class, ToString() should be overridden to return the added information.
C# language specification
For more information, see Exceptions and The throw statement in the C# Language Specification. The language specification is the definitive source for C# syntax and usage.