CA2000: удалите объекты до того, как будет потеряна область действия
Свойство | Значение |
---|---|
Идентификатор правила | CA2000 |
Заголовок | Ликвидируйте объекты перед потерей области |
Категория | Надежность |
Исправление является критическим или не критическим | Не критическое |
Включен по умолчанию в .NET 9 | No |
Причина
Создается локальный объект типа IDisposable, но он не высвобождается до того, пока все ссылки на него не окажутся вне области действия.
По умолчанию это правило анализирует всю базу кода, но такое поведение можно настроить.
Описание правила
Если освобождаемый объект не высвобождается явно до того, как все ссылки на него оказываются вне области имен, объект будет высвобожден в некоторый заранее не определенный момент, когда сборщик мусора запустит завершающий метод объекта. Так как может возникнуть событие исключения, препятствующее выполнению метода завершения объекта, объект будет ликвидирован в явной форме.
Особые случаи
Правило CA2000 не срабатывает для локальных объектов следующих типов, даже если объект не освобожден:
- System.IO.Stream
- System.IO.StringReader
- System.IO.TextReader
- System.IO.TextWriter
- System.Resources.IResourceReader
Передача объекта одного из этих типов в конструктор и его последующее присваивание полю указывает на передачу владения высвобождением новому сконструированному типу. То есть за освобождение объекта теперь отвечает новый сконструированный тип. Если код передает в конструктор объект одного из этих типов, нарушение правила CA2000 не происходит, даже если объект не освобожден до того, пока все ссылки на него не окажутся вне области действия.
Устранение нарушений
Чтобы устранить нарушение этого правила, вызовите объект Dispose до того, пока все ссылки на него не окажутся вне области действия.
Для создания оболочки объектов, реализующих using
, можно использовать (Using
в 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
Дополнительные сведения см. в разделе Практическое руководство. Скрытие предупреждений анализа кода.
Настройка кода для анализа
Используйте следующие параметры, чтобы настроить, в каких частях базы кода будет выполняться это правило, и при передаче права собственности на удаление.
- Исключить определенные символы
- Исключить определенные типы и их производные типы
- Настройка передачи прав владения удалением
Кроме того, к этому правилу применяются следующие другие параметры анализа потока данных:
- dispose_analysis_kind
- interprocedural_analysis_kind
- max_interprocedural_lambda_or_local_function_call_chain
- max_interprocedural_method_call_chain
- points_to_analysis_kind
- copy_analysis
- sufficient_IterationCount_for_weak_KDF_algorithm
Эти параметры можно настроить только для этого правила, для всех правил, к которым они применяются, или для всех правил в этой категории (надежности), к которым они применяются. Дополнительные сведения см. в статье Параметры конфигурации правила качества кода.
Исключение определенных символов
Вы можете исключить определенные символы, такие как типы и методы, из анализа, задав параметр 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
идентификатором применимого правила.
dispose_ownership_transfer_at_constructor
Рассмотрим следующий пример кода.
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
утечки.
dispose_ownership_transfer_at_method_call
Рассмотрим следующий пример кода.
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