The purpose of a finally statement is to ensure that the necessary cleanup of objects, usually objects that are holding external resources, occurs immediately, even if an exception is thrown. One example of such cleanup is calling System.IO.Stream.Close on a System.IO.FileStream immediately after use instead of waiting for the object to be garbage collected by the common language runtime, as follows:
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 { }
}Example
To turn the previous code into a try-catch-finally statement, the cleanup code is separated from the working code, as follows.
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 { }
}Because an exception can occur at any time within the try block before the OpenWrite() call, or the OpenWrite() call itself could fail, we aren't guaranteed that the file is open when we try to close it. The finally block adds a check to make sure that the System.IO.FileStream object isn't null before you call the System.IO.Stream.Close method. Without the null check, the finally block could throw its own System.NullReferenceException, but throwing exceptions in finally blocks should be avoided if it's possible.
A database connection is another good candidate for being closed in a finally block. Because the number of connections allowed to a database server is sometimes limited, you should close database connections as quickly as possible. If an exception is thrown before you can close your connection, using the finally block is better than waiting for garbage collection.