Бөлісу құралы:


CA2000: удалите объекты до того, как будет потеряна область действия

Свойство Значение
Идентификатор правила CA2000
Заголовок Удалите объекты перед выходом за пределы области видимости
Категория Надежность
Исправление является критическим или не критическим неразрывный
Включен по умолчанию в .NET 10 Нет
Применимые языки C# и Visual Basic

Причина

Создается локальный объект типа IDisposable, но он не высвобождается до того, пока все ссылки на него не окажутся вне области действия.

По умолчанию это правило анализирует всю базу кода, но такое поведение можно настроить.

Описание правила

Если объект, подлежащий освобождению, не высвобождается явно до того, как все ссылки на него оказываются вне области видимости, объект будет высвобожден в неопределенный момент, когда сборщик мусора запустит финализатор объекта. Так как может возникнуть событие исключения, препятствующее выполнению метода завершения объекта, объект будет ликвидирован в явной форме.

Особые случаи

Правило CA2000 не срабатывает для локальных объектов следующих типов, даже если объект не освобожден:

Передача объекта одного из этих типов в конструктор и его последующее присваивание полю указывает на передачу права на освобождение ресурса новому сконструированному типу. То есть за освобождение объекта теперь отвечает новый сконструированный тип. Если код передает в конструктор объект одного из этих типов, нарушение правила CA2000 не происходит, даже если объект не освобожден до того, пока все ссылки на него не окажутся вне области действия.

Устранение нарушений

Чтобы устранить нарушение этого правила, вызовите метод Dispose для объекта до того, как все ссылки на него выйдут из области действия.

Для обертывания объектов, реализующих , можно использовать оператор ( в Visual Basic). Объекты, заключенные в оболочку таким способом, автоматически удаляются в конце блока using. Однако оператор using не должен или не может обрабатывать следующие ситуации:

  • Чтобы вернуть временный объект, объект должен быть построен в блоке try/finally за пределами блока using.

  • Не следует инициализировать члены высвобождаемого объекта в конструкторе инструкции using.

  • Если конструкторы, защищенные только одним обработчиком исключений, вложены в часть инструкции using, ошибка внешнего конструктора может привести к тому, что объект, созданный вложенным конструктором, никогда не закрывается. В следующем примере сбой в конструкторе StreamReader может привести к тому, что объект FileStream никогда не закрывается. В этом случае CA2000 помечает нарушение правила.

    using (StreamReader sr = new StreamReader(new FileStream("C:/myfile.txt", FileMode.Create)))
    { ... }
    
  • Для реализации шаблона удаления объектов IDisposable, динамические объекты должны использовать объект-"тень".

Когда лучше отключить предупреждения

Не подавляйте предупреждение, связанное с этим правилом, если не выполняются следующие условия:

  • Вы вызвали метод на объекте, который вызывает Dispose, например, Close.
  • Метод, который вызвал предупреждение, возвращает IDisposable объект, оборачивающий ваш объект.
  • Метод управления ресурсами не отвечает за освобождение ресурсов. То есть ответственность за удаление объекта возлагается на другой объект или оболочку, созданную в методе, и возвращается вызывающему объекту.

Отключение предупреждений

Если вы просто хотите отключить одно нарушение, добавьте директивы препроцессора в исходный файл, чтобы отключить и повторно включить правило.

#pragma warning disable CA2000
// The code that's violating the rule is on this line.
#pragma warning restore CA2000

Чтобы отключить правило для файла, папки или проекта, задайте его серьезность none в файле конфигурации.

[*.{cs,vb}]
dotnet_diagnostic.CA2000.severity = none

Дополнительные сведения см. в разделе Практическое руководство. Скрытие предупреждений анализа кода.

Настройка кода для анализа

Используйте следующие параметры, чтобы настроить, в каких частях кодовой базы это правило будет применяться, и когда следует передавать право управления освобождением.

Кроме того, к этому правилу применяются следующие другие параметры анализа потока данных:

Эти параметры можно настроить только для этого правила, для всех правил, к которым они применяются, или для всех правил в этой категории (надежности), к которым они применяются. Дополнительные сведения см. в статье Параметры конфигурации правила качества кода.

Исключение определенных символов

Вы можете исключить определенные символы, такие как типы и методы, из анализа, задав параметр excluded_symbol_names. Например, чтобы указать, что правило не должно выполняться для какого-либо кода в типах с именем MyType, добавьте следующую пару "ключ-значение" в файл EDITORCONFIG в своем проекте:

dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType

Заметка

Замените XXXX частью CAXXXX идентификатором применимого правила.

Допустимые форматы имени символа в значении параметра (разделенные |):

  • Только имя символа (включает все символы с этим именем, независимо от типа или пространства имен).
  • Полностью квалифицированные имена в формате идентификатора документации символа. Для каждого имени символа требуется префикс в виде символа, например M: для методов, T: для типов и N: для пространств имен.
  • .ctor используется для конструкторов, а .cctor — для статических конструкторов.

Примеры:

Значение параметра Итоги
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType Соответствует всем символам с именем MyType.
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType1|MyType2 Соответствует всем символам с именем MyType1 или MyType2.
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS.MyType.MyMethod(ParamType) Сопоставляет конкретный метод MyMethod с указанной полной сигнатурой.
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS1.MyType1.MyMethod1(ParamType)|M:NS2.MyType2.MyMethod2(ParamType) Сопоставляет конкретные методы MyMethod1 и MyMethod2 с соответствующими полными квалифицированными сигнатурами.

Исключить определенные типы и их производные типы

Вы можете исключить определенные типы и производные типы из анализа, задав параметр excluded_type_names_with_derived_types. Например, чтобы указать, что правило не должно выполняться в каких-либо методах типов MyType и их производных типов, добавьте следующую пару "ключ-значение" в файл .editorconfig своего проекта:

dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType

Заметка

Замените XXXX частью CAXXXX идентификатором применимого правила.

Допустимые форматы имени символа в значении параметра (разделенные |):

  • Только имя типа (включает все типы с этим именем, независимо от содержащего типа или пространства имен).
  • полные квалифицированные имена в формате идентификатора документации для символа, с необязательным префиксом T:.

Примеры:

Значение опции Итоги
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType Соответствует всем типам с именем MyType и всем их производным типам.
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType1|MyType2 Соответствует всем типам с именем MyType1 или MyType2 и всем их производным типам.
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS.MyType Соответствует конкретному типу MyType с заданным полным именем и всем производным от него типам.
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS1.MyType1|M:NS2.MyType2 Соответствует конкретным типам MyType1 и MyType2 с заданным полным именем и всем производным от них типам.

Настройка передачи управления высвобождением ресурсов

Параметры dispose_ownership_transfer_at_constructor и dispose_ownership_transfer_at_method_call настраивают передачу права на удаление.

Например, чтобы указать, что правило передает право собственности на аргументы, переданные конструкторам, добавьте следующую пару "ключ-значение" в файл .editorconfig в проекте:

dotnet_code_quality.CAXXXX.dispose_ownership_transfer_at_constructor = true

Заметка

Замените XXXX частью CAXXXX идентификатором применимого правила.

передача прав собственности при инициализации

Рассмотрим следующий пример кода.

class A : IDisposable
{
    public void Dispose() { }
}

class Test
{
    DisposableOwnerType M1()
    {
        return new DisposableOwnerType(new A());
    }
}
  • Если dotnet_code_quality.dispose_ownership_transfer_at_constructor задано значение true, то право на освобождение выделения new A() передается к возвращенному экземпляру DisposableOwnerType.
  • Если dotnet_code_quality.dispose_ownership_transfer_at_constructor установлено в false, Test.M1() имеет право распоряжаться new A(), и вызывает нарушение из-за утечки CA2000.

передача_владения_при_вызове_метода

Рассмотрим следующий пример кода.

class Test
{
    void M1()
    {
        TransferDisposeOwnership(new A());
    }
}
  • Если dotnet_code_quality.dispose_ownership_transfer_at_method_call задано значение true, то передача владения выделением new A() осуществляется методу TransferDisposeOwnership.
  • Если dotnet_code_quality.dispose_ownership_transfer_at_method_call установлено значение false, Test.M1() имеет право собственности на new A(), и это приводит к нарушению CA2000 из-за утечки.

Пример 1

При реализации метода, который возвращает объект, который можно высвободить, используйте блок try/finally без блока catch, чтобы объект был гарантированно очищен. Блок try/finally позволяет исключениям возникать в момент сбоя и гарантирует, что объект будет освобожден.

В методе OpenPort1 вызов для открытия SerialPort объекта ISerializable или вызов метода SomeMethod может завершиться ошибкой. В этой реализации возникает предупреждение CA2000.

В методе OpenPort2 объявляются два объекта SerialPort, которым затем задается значение NULL:

  • tempPort, который используется для проверки успешного выполнения операций метода.

  • port, который используется для возвращения значения метода.

В блоке tempPort создается и открывается объект try, и в том же блоке try выполняются все требуемые действия. В конце блока try открытый порт назначается объекту port, который будет возвращен, и объекту tempPort задается значение null.

Блок finally проверяет значение tempPort. Если значение не равно NULL, операция в методе завершилась неудачно, и tempPort закрывается, означая, что все ресурсы освобождены. Если операции метода завершились успешно, возвращаемый объект порта будет содержать открытый объект SerialPort. В противном случае он будет иметь значение NULL.

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

Пример 2

По умолчанию в компиляторе Visual Basic все арифметические операторы выполняют проверку на переполнение. Таким образом, любая арифметическая операция Visual Basic может вызвать исключение OverflowException. Это может привести к непредвиденным нарушениям правил, таких как CA2000. Например, следующая функция CreateReader1 приведет к нарушению правила CA2000, так как компилятор Visual Basic выдает инструкцию проверки переполнения для добавления, которая может вызвать исключение, приводящее к невозможности освобождения StreamReader.

Чтобы устранить эту проблему, можно отключить проведение проверок переполнения, выполняемых компилятором Visual Basic, в проекте или изменить код, как в следующей функции CreateReader2.

Чтобы отключить создание проверок переполнения, щелкните правой кнопкой мыши имя проекта в Проводнике решений и выберите Свойства. Выберите Компиляция>Дополнительные параметры компиляции, а затем отметьте Удалить проверки переполнения целых чисел.

Imports System.IO

Class CA2000
    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
End Class

См. также