CA2000:丢失范围之前释放对象
属性 | 值 |
---|---|
规则 ID | CA2000 |
标题 | 丢失范围之前释放对象 |
类别 | 可靠性 |
修复是中断修复还是非中断修复 | 非中断 |
在 .NET 8 中默认启用 | 否 |
原因
创建了 IDisposable 类型的本地对象,但该对象不会被释放,除非对对象的所有引用都超出范围。
默认情况下,此规则会分析整个代码库,但这是可配置的。
规则说明
如果在对某个可释放对象的所有引用超出范围之前未显式释放该对象,则当垃圾回收器运行该对象的终结器时,将在某个不确定的时间释放该对象。 由于可能发生异常事件,导致对象的终结器无法运行,因此应显式释放对象。
特殊情况
即使未释放对象,也不会由于以下类型的本地对象触发规则 CA2000:
- System.IO.Stream
- System.IO.StringReader
- System.IO.TextReader
- System.IO.TextWriter
- System.Resources.IResourceReader
将其中一个类型的对象传递给构造函数,然后将其分配给一个字段,表示释放所有权转移到新构造的类型。 也就是说,新构造的类型现在负责释放对象。 如果代码将其中一个类型的对象传递给构造函数,则即使在对对象的所有引用超出范围之前未释放该对象,也不会发生规则 CA2000 冲突。
如何解决冲突
要解决此规则的冲突,需在对对象的所有引用超出范围之前,在对象上调用 Dispose。
可使用 using
语句(Visual Basic 中的 Using
)来包装实现 IDisposable 的对象。 以这种方式包装的对象将自动在 using
块的末尾释放。 但是,以下情况不应或不能使用 using
语句进行处理:
若要返回可释放对象,该对象必须在
using
块外的try/finally
块中进行构造。请勿在
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
有关详细信息,请参阅如何禁止显示代码分析警告。
配置代码以进行分析
使用下面的选项来配置代码库的哪些部分要运行此规则。
可以仅为此规则、为适用的所有规则或为适用的此类别(可靠性)中的所有规则配置这些选项。 有关详细信息,请参阅代码质量规则配置选项。
排除特定符号
可以从分析中排除特定符号,如类型和方法。 例如,若要指定规则不应针对名为 MyType
的类型中的任何代码运行,请将以下键值对添加到项目中的 .editorconfig 文件:
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType
选项值中允许的符号名称格式(用 |
分隔):
- 仅符号名称(包括具有相应名称的所有符号,不考虑包含的类型或命名空间)。
- 完全限定的名称,使用符号的文档 ID 格式。 每个符号名称都需要带有一个符号类型前缀,例如表示方法的
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 。 |
排除特定类型及其派生类型
可以从分析中排除特定类型及其派生类型。 例如,若要指定规则不应针对名为 MyType
的类型及其派生类型中的任何代码运行,请将以下键值对添加到项目中的 .editorconfig 文件:
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType
选项值中允许的符号名称格式(用 |
分隔):
- 仅类型名称(包括具有相应名称的所有类型,不考虑包含的类型或命名空间)。
- 完全限定的名称,使用符号的文档 ID 格式,前缀为
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 及其所有派生类型。 |
相关规则
示例 1
如果要实现返回可释放对象的方法,可在没有 catch 块的情况下使用 try/finally 块来确保释放对象。 通过使用 try/finally 块,允许在故障点引发异常,并确保对象已释放。
在 OpenPort1 方法中,调用打开 ISerializable 对象 SerialPort 或对 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