Training
Module
Control variable scope and logic using code blocks in C# - Training
Use code blocks with more confidence, understanding how they impact the visibility and accessibility of both higher and lower-level constructs in your code.
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
The common language runtime's garbage collector (GC) reclaims the memory used by managed objects. Typically, types that use unmanaged resources implement the IDisposable or IAsyncDisposable interface to allow the unmanaged resources to be reclaimed. When you finish using an object that implements IDisposable, you call the object's Dispose or DisposeAsync implementation to explicitly perform cleanup. You can do this in one of two ways:
using
statement or declaration (Using
in Visual Basic).try/finally
block, and calling the Dispose or DisposeAsync method in the finally
.Important
The GC does not dispose your objects, as it has no knowledge of IDisposable.Dispose() or IAsyncDisposable.DisposeAsync(). The GC only knows whether an object is finalizable (that is, it defines an Object.Finalize() method), and when the object's finalizer needs to be called. For more information, see How finalization works. For additional details on implementing Dispose
and DisposeAsync
, see:
Objects that implement System.IDisposable or System.IAsyncDisposable should always be properly disposed of, regardless of variable scoping, unless otherwise explicitly stated. Types that define a finalizer to release unmanaged resources usually call GC.SuppressFinalize from either their Dispose
or DisposeAsync
implementation. Calling SuppressFinalize indicates to the GC that the finalizer has already been run and the object shouldn't be promoted for finalization.
The using
statement in C# and the Using
statement in Visual Basic simplify the code that you must write to cleanup an object. The using
statement obtains one or more resources, executes the statements that you specify, and automatically disposes of the object. However, the using
statement is useful only for objects that are used within the scope of the method in which they are constructed.
The following example uses the using
statement to create and release a System.IO.StreamReader object.
using System.IO;
class UsingStatement
{
static void Main()
{
var buffer = new char[50];
using (StreamReader streamReader = new("file1.txt"))
{
int charsRead = 0;
while (streamReader.Peek() != -1)
{
charsRead = streamReader.Read(buffer, 0, buffer.Length);
//
// Process characters read.
//
}
}
}
}
Imports System.IO
Module UsingStatement
Public Sub Main()
Dim buffer(49) As Char
Using streamReader As New StreamReader("File1.txt")
Dim charsRead As Integer
Do While streamReader.Peek() <> -1
charsRead = streamReader.Read(buffer, 0, buffer.Length)
'
' Process characters read.
'
Loop
End Using
End Sub
End Module
A using
declaration is an alternative syntax available where the braces are removed, and scoping is implicit.
using System.IO;
class UsingDeclaration
{
static void Main()
{
var buffer = new char[50];
using StreamReader streamReader = new("file1.txt");
int charsRead = 0;
while (streamReader.Peek() != -1)
{
charsRead = streamReader.Read(buffer, 0, buffer.Length);
//
// Process characters read.
//
}
}
}
Although the StreamReader class implements the IDisposable interface, which indicates that it uses an unmanaged resource, the example doesn't explicitly call the StreamReader.Dispose method. When the C# or Visual Basic compiler encounters the using
statement, it emits intermediate language (IL) that is equivalent to the following code that explicitly contains a try/finally
block.
using System.IO;
class TryFinallyGenerated
{
static void Main()
{
var buffer = new char[50];
StreamReader? streamReader = null;
try
{
streamReader = new StreamReader("file1.txt");
int charsRead = 0;
while (streamReader.Peek() != -1)
{
charsRead = streamReader.Read(buffer, 0, buffer.Length);
//
// Process characters read.
//
}
}
finally
{
// If non-null, call the object's Dispose method.
streamReader?.Dispose();
}
}
}
Imports System.IO
Module TryFinallyGenerated
Public Sub Main()
Dim buffer(49) As Char
Dim streamReader As New StreamReader("File1.txt")
Try
Dim charsRead As Integer
Do While streamReader.Peek() <> -1
charsRead = streamReader.Read(buffer, 0, buffer.Length)
'
' Process characters read.
'
Loop
Finally
If streamReader IsNot Nothing Then DirectCast(streamReader, IDisposable).Dispose()
End Try
End Sub
End Module
The C# using
statement also allows you to acquire multiple resources in a single statement, which is internally equivalent to nested using
statements. The following example instantiates two StreamReader objects to read the contents of two different files.
using System.IO;
class SingleStatementMultiple
{
static void Main()
{
var buffer1 = new char[50];
var buffer2 = new char[50];
using StreamReader version1 = new("file1.txt"),
version2 = new("file2.txt");
int charsRead1, charsRead2 = 0;
while (version1.Peek() != -1 && version2.Peek() != -1)
{
charsRead1 = version1.Read(buffer1, 0, buffer1.Length);
charsRead2 = version2.Read(buffer2, 0, buffer2.Length);
//
// Process characters read.
//
}
}
}
Instead of wrapping a try/finally
block in a using
statement, you may choose to implement the try/finally
block directly. It may be your personal coding style, or you might want to do this for one of the following reasons:
catch
block to handle exceptions thrown in the try
block. Otherwise, any exceptions thrown within the using
statement are unhandled.The following example is similar to the previous example, except that it uses a try/catch/finally
block to instantiate, use, and dispose of a StreamReader object, and to handle any exceptions thrown by the StreamReader constructor and its ReadToEnd method. The code in the finally
block checks that the object that implements IDisposable isn't null
before it calls the Dispose method. Failure to do this can result in a NullReferenceException exception at run time.
using System;
using System.Globalization;
using System.IO;
class TryExplicitCatchFinally
{
static void Main()
{
StreamReader? streamReader = null;
try
{
streamReader = new StreamReader("file1.txt");
string contents = streamReader.ReadToEnd();
var info = new StringInfo(contents);
Console.WriteLine($"The file has {info.LengthInTextElements} text elements.");
}
catch (FileNotFoundException)
{
Console.WriteLine("The file cannot be found.");
}
catch (IOException)
{
Console.WriteLine("An I/O error has occurred.");
}
catch (OutOfMemoryException)
{
Console.WriteLine("There is insufficient memory to read the file.");
}
finally
{
streamReader?.Dispose();
}
}
}
Imports System.Globalization
Imports System.IO
Module TryExplicitCatchFinally
Sub Main()
Dim streamReader As StreamReader = Nothing
Try
streamReader = New StreamReader("file1.txt")
Dim contents As String = streamReader.ReadToEnd()
Dim info As StringInfo = New StringInfo(contents)
Console.WriteLine($"The file has {info.LengthInTextElements} text elements.")
Catch e As FileNotFoundException
Console.WriteLine("The file cannot be found.")
Catch e As IOException
Console.WriteLine("An I/O error has occurred.")
Catch e As OutOfMemoryException
Console.WriteLine("There is insufficient memory to read the file.")
Finally
If streamReader IsNot Nothing Then streamReader.Dispose()
End Try
End Sub
End Module
You can follow this basic pattern if you choose to implement or must implement a try/finally
block, because your programming language doesn't support a using
statement but does allow direct calls to the Dispose method.
If a class owns an instance field or property and its type implements IDisposable, the class should also implement IDisposable. For more information, see Implement a cascade dispose.
.NET feedback
.NET is an open source project. Select a link to provide feedback:
Training
Module
Control variable scope and logic using code blocks in C# - Training
Use code blocks with more confidence, understanding how they impact the visibility and accessibility of both higher and lower-level constructs in your code.