Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added alert regarding the responsibility of the GC as it relates to implementations of IDisposable #24259

Merged
merged 10 commits into from
May 19, 2021
Prev Previous commit
Next Next commit
Rename, clean up, add .csproj for CI verification. Add using declarat…
…ion, etc.
  • Loading branch information
IEvangelist committed May 18, 2021
commit a110ff8bc384afeb75343bba612370ec7edf90ff
29 changes: 16 additions & 13 deletions docs/standard/garbage-collection/using-objects.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Using objects that implement IDisposable
description: Learn how to use objects that implement the IDisposable interface in .NET. Types that use unmanaged resources implement IDisposable to allow resource reclaiming.
ms.date: 05/17/2021
ms.date: 05/18/2021
dev_langs:
- "csharp"
- "vb"
Expand All @@ -14,49 +14,52 @@ ms.assetid: 81b2cdb5-c91a-4a31-9c83-eadc52da5cf0

# Using objects that implement IDisposable

The common language runtime's garbage collector (GC) reclaims the memory used by managed objects. Typically, types that use unmanaged resources implement the <xref:System.IDisposable> or <xref:System.IAsyncDisposable> interface to allow the resources needed by unmanaged resources to be reclaimed. When you finish using an object that implements <xref:System.IDisposable>, you call the object's <xref:System.IDisposable.Dispose%2A> or <xref:System.IDisposable.DisposeAsync%2A> implementation to explicitly perform cleanup. You can do this in one of two ways:
The common language runtime's garbage collector (GC) reclaims the memory used by managed objects. Typically, types that use unmanaged resources implement the <xref:System.IDisposable> or <xref:System.IAsyncDisposable> interface to allow the resources needed by unmanaged resources to be reclaimed. When you finish using an object that implements <xref:System.IDisposable>, you call the object's <xref:System.IDisposable.Dispose%2A> or <xref:System.IAsyncDisposable.DisposeAsync%2A> implementation to explicitly perform cleanup. You can do this in one of two ways:
IEvangelist marked this conversation as resolved.
Show resolved Hide resolved

- With the C# `using` statement or declaration (`Using` in Visual Basic).
- By implementing a `try/finally` block, and calling the <xref:System.IDisposable.Dispose%2A> or <xref:System.IDisposable.DisposeAsync%2A> method in the `finally`.
- By implementing a `try/finally` block, and calling the <xref:System.IDisposable.Dispose%2A> or <xref:System.IAsyncDisposable.DisposeAsync%2A> method in the `finally`.

> [!IMPORTANT]
> The GC does ***not*** dispose your objects, as it has no knowledge of `Dispose` or `IDisposable` implementations. The GC only knows whether an object is finalizable (that is, it defines an <xref:System.Object.Finalize?displayProperty=nameWithType> method), and when the object's finalizer needs to be called. For more information, see [How finalization works](/dotnet/api/system.object.finalize#how-finalization-works). For additional details on implementing `Dispose` and `DisposeAsync`, see:
> The GC does ***not*** dispose your objects, as it has no knowledge of <xref:System.IDisposable.Dispose?displayProperty=nameWithType> or <xref:System.IAsyncDisposable.DisposeAsync?displayProperty=nameWithType>. The GC only knows whether an object is finalizable (that is, it defines an <xref:System.Object.Finalize?displayProperty=nameWithType> method), and when the object's finalizer needs to be called. For more information, see [How finalization works](/dotnet/api/system.object.finalize#how-finalization-works). For additional details on implementing `Dispose` and `DisposeAsync`, see:
>
> - [Implement a Dispose method](implementing-dispose.md)
> - [Implement a DisposeAsync method](implementing-disposeasync.md)

Objects that implement <xref:System.IDisposable?displayProperty=fullName> or <xref:System.IAsyncDisposable?displayProperty=fullName> should always be properly disposed of, regardless of variable scoping, unless otherwise explicitly stated.
Objects that implement <xref:System.IDisposable?displayProperty=fullName> or <xref:System.IAsyncDisposable?displayProperty=fullName> should always be properly disposed of, regardless of variable scoping, unless otherwise explicitly stated. Objects that define a finalizer usually call <xref:System.GC.SuppressFinalize%2A?displayProperty=nameWithType> with an argument of `this` from either their `Dispose` or DisposeAsync` implementation. This is to ensure that when their cleanup functions are called, they indicate the finalizer need not run.
IEvangelist marked this conversation as resolved.
Show resolved Hide resolved

## The using statement

The [`using` statement](../../csharp/language-reference/keywords/using-statement.md) in C# and the [`Using` statement](../../visual-basic/language-reference/statements/using-statement.md) 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 <xref:System.IO.StreamReader?displayProperty=nameWithType> object.

[!code-csharp[Conceptual.Disposable#1](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/using1.cs#1)]
[!code-vb[Conceptual.Disposable#1](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/using1.vb#1)]
:::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/UsingStatement.cs":::
:::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/using1":::

With C# 8, a [`using` declaration](../../csharp/whats-new/csharp-8.md#using-declarations) is an alternative syntax available where the braces are removed, and scoping is implicit.

:::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/UsingDeclaration.cs":::

Although the <xref:System.IO.StreamReader> class implements the <xref:System.IDisposable> interface, which indicates that it uses an unmanaged resource, the example doesn't explicitly call the <xref:System.IO.StreamReader.Dispose%2A?displayProperty=nameWithType> 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.

[!code-csharp[Conceptual.Disposable#3](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/using3.cs#3)]
[!code-vb[Conceptual.Disposable#3](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/using3.vb#3)]
:::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/TryFinallyGenerated.cs":::
:::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/using3.vb":::

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 <xref:System.IO.StreamReader> objects to read the contents of two different files.

[!code-csharp[Conceptual.Disposable#4](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/using4.cs#4)]
:::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/SingleStatementMultiple.cs":::

## Try/finally block

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:

- To include a `catch` block to handle exceptions thrown in the `try` block. Otherwise, any exceptions thrown within the `using` statement are unhandled.

- To instantiate an object that implements <xref:System.IDisposable> whose scope is not local to the block within which it is declared.

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 <xref:System.IO.StreamReader> object, and to handle any exceptions thrown by the <xref:System.IO.StreamReader> constructor and its <xref:System.IO.StreamReader.ReadToEnd%2A> method. The code in the `finally` block checks that the object that implements <xref:System.IDisposable> isn't `null` before it calls the <xref:System.IDisposable.Dispose%2A> method. Failure to do this can result in a <xref:System.NullReferenceException> exception at run time.

[!code-csharp[Conceptual.Disposable#6](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/using5.cs#6)]
[!code-vb[Conceptual.Disposable#6](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/using5.vb#6)]
:::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.disposable/cs/TryExplicitCatchFinally.cs":::
:::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.disposable/vb/TryExplicitCatchFinally.vb":::

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 <xref:System.IDisposable.Dispose%2A> method.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// <Snippet4>
using System.IO;
using System.IO;

class Example
class SingleStatementMultiple
{
static void Main()
{
char[] buffer1 = new char[50];
char[] buffer2 = new char[50];
var buffer1 = new char[50];
var buffer2 = new char[50];

using StreamReader version1 = new StreamReader("file1.txt"),
version2 = new StreamReader("file2.txt");
using StreamReader version1 = new("file1.txt"),
version2 = new("file2.txt");

int charsRead1, charsRead2 = 0;
while (version1.Peek() != -1 && version2.Peek() != -1)
Expand All @@ -22,4 +21,3 @@ static void Main()
}
}
}
// </Snippet4>
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// <Snippet6>
using System;
using System;
using System.Globalization;
using System.IO;

class Example
class TryExplicitCatchFinally
{
static void Main()
{
Expand Down Expand Up @@ -33,4 +32,3 @@ static void Main()
}
}
}
// </Snippet6>
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// <Snippet2>
using System.IO;
using System.IO;

class Example
class TryFinallyGenerated
{
static void Main()
{
char[] buffer = new char[50];
var buffer = new char[50];
StreamReader? streamReader = null;
try
{
Expand All @@ -26,4 +25,3 @@ static void Main()
}
}
}
// </Snippet2>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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.
//
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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.
//
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
' Visual Basic .NET Document
Option Strict On

' <Snippet6>
Imports System.Globalization
Imports System.Globalization
Imports System.IO

Module Example
Module TryExplicitCatchFinally
Sub Main()
Dim streamReader As StreamReader = Nothing
Try
Expand All @@ -24,4 +20,3 @@ Module Example
End Try
End Sub
End Module
' </Snippet6>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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()
IEvangelist marked this conversation as resolved.
Show resolved Hide resolved
End Try
End Sub
End Module
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>

This file was deleted.

This file was deleted.

This file was deleted.