CA2000: Dispose objects before losing scope

TypeName DisposeObjectsBeforeLosingScope
CheckId CA2000
Category Microsoft.Reliability
Breaking change Non-breaking

Cause

A local object of an IDisposable type is created, but the object is not disposed before all references to the object are out of scope.

By default, this rule analyzes the entire codebase, but this is configurable.

Rule description

If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead.

Special cases

Rule CA2000 does not fire for local objects of the following types even if the object is not disposed:

Passing an object of one of these types to a constructor and then assigning it to a field indicates a dispose ownership transfer to the newly constructed type. That is, the newly constructed type is now responsible for disposing of the object. If your code passes an object of one of these types to a constructor, no violation of rule CA2000 occurs even if the object is not disposed before all references to it are out of scope.

How to fix violations

To fix a violation of this rule, call Dispose on the object before all references to it are out of scope.

You can use the using statement (Using in Visual Basic) to wrap objects that implement IDisposable. Objects that are wrapped in this manner are automatically disposed at the end of the using block. However, the following situations should not or cannot be handled with a using statement:

  • To return a disposable object, the object must constructed in a try/finally block outside of a using block.

  • Do not initialize members of a disposable object in the constructor of a using statement.

  • When constructors that are protected by only one exception handler are nested in the acquisition part of a using statement, a failure in the outer constructor can result in the object created by the nested constructor never being closed. In the following example, a failure in the StreamReader constructor can result in the FileStream object never being closed. CA2000 flags a violation of the rule in this case.

    using (StreamReader sr = new StreamReader(new FileStream("C:\myfile.txt", FileMode.Create)))
    { ... }
    
  • Dynamic objects should use a shadow object to implement the dispose pattern of IDisposable objects.

When to suppress warnings

Do not suppress a warning from this rule unless:

  • You've called a method on your object that calls Dispose, such as Close
  • The method that raised the warning returns an IDisposable object that wraps your object
  • The allocating method does not have dispose ownership; that is, the responsibility to dispose the object is transferred to another object or wrapper that's created in the method and returned to the caller

Configurability

If you're running this rule from FxCop analyzers (and not with legacy analysis), you can configure the analysis for this rule.

Excluded symbol names

You can configure which parts of your codebase to exclude from analysis. For example, to specify that the rule should not run on any code within types named MyType, add the following key-value pair to an .editorconfig file in your project:

dotnet_code_quality.CA2000.excluded_symbol_names = MyType

Allowed symbol name formats in the option value (separated by '|'):

  • Symbol name only (includes all symbols with the name, regardless of the containing type or namespace)
  • Fully qualified names in the symbol's documentation ID format. Each symbol name requires a symbol kind prefix, such as "M:" prefix for methods, "T:" prefix for types, "N:" prefix for namespaces, etc.
  • .ctor for constructors and .cctor for static constructors

Examples:

Option Value Summary
dotnet_code_quality.CA2000.excluded_symbol_names = MyType Matches all symbols named 'MyType' in the compilation
dotnet_code_quality.CA2000.excluded_symbol_names = MyType1|MyType2 Matches all symbols named either 'MyType1' or 'MyType2' in the compilation
dotnet_code_quality.CA2000.excluded_symbol_names = M:NS.MyType.MyMethod(ParamType) Matches specific method 'MyMethod' with given fully qualified signature
dotnet_code_quality.CA2000.excluded_symbol_names = M:NS1.MyType1.MyMethod1(ParamType)|M:NS2.MyType2.MyMethod2(ParamType) Matches specific methods 'MyMethod1' and 'MyMethod2' with respective fully qualified signature

You can configure all of these options for just this rule, for all rules, or for all rules in this category (Design). For more information, see Configure FxCop analyzers.

Example

If you're implementing a method that returns a disposable object, use a try/finally block without a catch block to make sure that the object is disposed. By using a try/finally block, you allow exceptions to be raised at the fault point and make sure that object is disposed.

In the OpenPort1 method, the call to open the ISerializable object SerialPort or the call to SomeMethod can fail. A CA2000 warning is raised on this implementation.

In the OpenPort2 method, two SerialPort objects are declared and set to null:

  • tempPort, which is used to test that the method operations succeed.

  • port, which is used for the return value of the method.

The tempPort is constructed and opened in a try block, and any other required work is performed in the same try block. At the end of the try block, the opened port is assigned to the port object that will be returned and the tempPort object is set to null.

The finally block checks the value of tempPort. If it is not null, an operation in the method has failed, and tempPort is closed to make sure that any resources are released. The returned port object will contain the opened SerialPort object if the operations of the method succeeded, or it will be null if an operation failed.

public SerialPort OpenPort1(string portName)
{
   SerialPort port = new SerialPort(portName);
   port.Open();  //CA2000 fires because this might throw
   SomeMethod(); //Other method operations can fail
   return port;
}

public SerialPort OpenPort2(string portName)
{
   SerialPort tempPort = null;
   SerialPort port = null;
   try
   {
      tempPort = new SerialPort(portName);
      tempPort.Open();
      SomeMethod();
      //Add any other methods above this line
      port = tempPort;
      tempPort = null;

   }
   finally
   {
      if (tempPort != null)
      {
         tempPort.Close();
      }
   }
   return port;
}
Public Function OpenPort1(ByVal PortName As String) As SerialPort

   Dim port As New SerialPort(PortName)
   port.Open()    'CA2000 fires because this might throw
   SomeMethod()   'Other method operations can fail
   Return port

End Function

Public Function OpenPort2(ByVal PortName As String) As SerialPort

   Dim tempPort As SerialPort = Nothing
   Dim port As SerialPort = Nothing

   Try
      tempPort = New SerialPort(PortName)
      tempPort.Open()
      SomeMethod()
      'Add any other methods above this line
      port = tempPort
      tempPort = Nothing

   Finally
      If Not tempPort Is Nothing Then
         tempPort.Close()
      End If

   End Try

   Return port

End Function

Example

By default, the Visual Basic compiler has all arithmetic operators check for overflow. Therefore, any Visual Basic arithmetic operation might throw an OverflowException. This could lead to unexpected violations in rules such as CA2000. For example, the following CreateReader1 function will produce a CA2000 violation because the Visual Basic compiler is emitting an overflow checking instruction for the addition that could throw an exception that would cause the StreamReader not to be disposed.

To fix this, you can disable the emitting of overflow checks by the Visual Basic compiler in your project or you can modify your code as in the following CreateReader2 function.

To disable the emitting of overflow checks, right-click the project name in Solution Explorer and then click Properties. Click Compile, click Advanced Compile Options, and then check Remove integer overflow checks.

Public Function CreateReader1(ByVal x As Integer) As StreamReader
   Dim local As New StreamReader("C:\Temp.txt")
   x += 1
   Return local
End Function


Public Function CreateReader2(ByVal x As Integer) As StreamReader
   Dim local As StreamReader = Nothing
   Dim localTemp As StreamReader = Nothing
   Try
      localTemp = New StreamReader("C:\Temp.txt")
      x += 1
      local = localTemp
      localTemp = Nothing
      Finally
         If (Not (localTemp Is Nothing)) Then
            localTemp.Dispose()
         End If
   End Try
   Return local
End Function

See also