CodeAnalysis return 可以误报 CA2202 吗?还是我的代码真的有问题?
Can a CodeAnalysis return a false positive of CA2202? or is really something wrong with my code?
我遇到了与 here 解释相同的问题,但迭代了 EnvDTE.Processes
.
在我链接用户@Plutonix 的问题中确认这是一个虚假警告,我认为他提到了 obj.Getenumerator()
提及所以我假设我的问题也会被认为是错误警告,但是,如果这是错误警告,我想知道的不仅仅是肯定,还有论据说这是错误警告。
这是警告:
CA2202 Do not dispose objects multiple times Object
'procs.GetEnumerator()' can be disposed more than once in method
'DebugUtil.GetCurrentVisualStudioInstance()'. To avoid generating a
System.ObjectDisposedException you should not call Dispose more than
one time on an object.: Lines:
214 Elektro.Application.Debugging DebugUtil.vb 214
这是代码,procs
对象是警告中涉及的对象,但我没有看到任何一次性对象:
Public Shared Function GetCurrentVisualStudioInstance() As DTE2
Dim currentInstance As DTE2 = Nothing
Dim processName As String = Process.GetCurrentProcess.MainModule.FileName
Dim instances As IEnumerable(Of DTE2) = DebugUtil.GetVisualStudioInstances
Dim procs As EnvDTE.Processes
For Each instance As DTE2 In instances
procs = instance.Debugger.DebuggedProcesses
For Each p As EnvDTE.Process In procs
If (p.Name = processName) Then
currentInstance = instance
Exit For
End If
Next p
Next instance
Return currentInstance
End Function
PS: 请注意,代码块取决于其他成员,但它们与此问题无关。
简短版本:对我来说,这看起来像是代码分析组件中的错误。
长版(嘿,你骗我花了我下午和晚上的大部分时间来破译这个,所以你不妨花一点时间阅读它:))...
我做的第一件事是查看 IL。与我的猜测相反,它 而不是 包含对同一对象的 Dispose()
的多次调用。那个理论到此为止。
但是,该方法确实包含对 Dispose()
的两次单独调用,只是针对不同的对象。到这个时候,我已经确信这是一个错误。我已经看到在处理相关 class 时触发 CA2202,其中一个 class 实例 "owns" 另一个 class 实例,并且两个实例都已处理。虽然不方便且值得抑制,但警告在这些情况下似乎是有效的;其中一个对象确实被处理了两次。
但在这种情况下,我有两个单独的 IEnumerator
对象;一个不拥有另一个,甚至与另一个没有关系。处置一个不会处置另一个。因此,代码分析就此发出警告是错误的。但是具体 是什么让它感到困惑?
经过多次试验,我想出了这个近乎最小的代码示例:
Public Class A
Public ReadOnly Property B As B
Get
Return New B
End Get
End Property
End Class
Public Interface IB
Function GetEnumerator() As IEnumerator
End Interface
Public Class B : Implements IB
Public Iterator Function GetEnumerator() As IEnumerator Implements IB.GetEnumerator
Yield New C
End Function
End Class
Public Class C
Dim _value As String
Public Property Value As String
Get
Return _value
End Get
Set(value As String)
_value = value
End Set
End Property
End Class
Public Shared Function GetCurrentVisualStudioInstance2() As A
For Each a As A In GetAs()
For Each c As C In a.B
If (c.Value = Nothing) Then
Return a
End If
Next c
Next a
Return Nothing
End Function
Public Shared Iterator Function GetAs() As IEnumerable(Of A)
Yield New A()
End Function
这会产生与您在其他代码示例中看到的相同的虚假 CA2202。有趣的是,对接口 IB
的声明和实现的一个小改动会导致警告消失:
Public Interface IB : Inherits IEnumerable
End Interface
Public Class B : Implements IB
Public Iterator Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
Yield New C
End Function
End Class
不知何故,代码分析被 GetEnumerator()
的非 IEnumerable
实现搞糊涂了。 (更奇怪的是,您正在使用的实际类型,DTE API 中的 Processes
接口,都继承了 IEnumerable
和 声明其自己的 GetEnumerator()
方法……但后者是代码分析混淆的根源,而不是组合。
有了它,我尝试在 C# 中重现该问题,但发现我做不到。我编写了一个 C# 版本,其结构与 VB.NET 版本中的类型和方法完全相同,但它在没有警告的情况下通过了代码分析。于是又看了看IL
我发现 C# 编译器生成的代码与 VB.NET 编译器非常相似,但不完全相同。特别是,对于保护每个循环返回的 IEnumerator
的 try
/finally
块,这些循环的所有初始化都在 外部 执行try
块,而在 VB.NET 版本中,初始化在内部执行。
显然,这也足以防止代码分析对一次性对象的使用感到困惑。
鉴于它似乎是 VB.NET 的 For Each
实现和嵌套循环的组合,一种解决方法是仅以不同方式实现该方法。无论如何我更喜欢 LINQ 语法,这里是你的方法的 LINQ 化版本,编译时没有代码分析警告:
Public Shared Function GetCurrentVisualStudioInstance() As DTE2
Dim processName As String = Process.GetCurrentProcess.MainModule.FileName
Return GetVisualStudioInstances.FirstOrDefault(
Function(instance)
Return instance.Debugger.DebuggedProcesses.Cast(Of EnvDTE.Process).Any(
Function(p)
Return p.Name = processName
End Function)
End Function)
End Function
为了完整起见,C# 版本(因为所有这些代码都是在 C# 实现转换为 VB.NET 然后扩展以处理 "current instance" 情况时开始的):
public static DTE2 GetCurrentVisualStudioInstance()
{
string processName = Process.GetCurrentProcess().MainModule.FileName;
return GetVisualStudioInstances()
.FirstOrDefault(i => i.Debugger.DebuggedProcesses
.Cast<EnvDTE.Process>().Any(p => p.Name == processName));
}
我遇到了与 here 解释相同的问题,但迭代了 EnvDTE.Processes
.
在我链接用户@Plutonix 的问题中确认这是一个虚假警告,我认为他提到了 obj.Getenumerator()
提及所以我假设我的问题也会被认为是错误警告,但是,如果这是错误警告,我想知道的不仅仅是肯定,还有论据说这是错误警告。
这是警告:
CA2202 Do not dispose objects multiple times Object 'procs.GetEnumerator()' can be disposed more than once in method 'DebugUtil.GetCurrentVisualStudioInstance()'. To avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object.: Lines: 214 Elektro.Application.Debugging DebugUtil.vb 214
这是代码,procs
对象是警告中涉及的对象,但我没有看到任何一次性对象:
Public Shared Function GetCurrentVisualStudioInstance() As DTE2
Dim currentInstance As DTE2 = Nothing
Dim processName As String = Process.GetCurrentProcess.MainModule.FileName
Dim instances As IEnumerable(Of DTE2) = DebugUtil.GetVisualStudioInstances
Dim procs As EnvDTE.Processes
For Each instance As DTE2 In instances
procs = instance.Debugger.DebuggedProcesses
For Each p As EnvDTE.Process In procs
If (p.Name = processName) Then
currentInstance = instance
Exit For
End If
Next p
Next instance
Return currentInstance
End Function
PS: 请注意,代码块取决于其他成员,但它们与此问题无关。
简短版本:对我来说,这看起来像是代码分析组件中的错误。
长版(嘿,你骗我花了我下午和晚上的大部分时间来破译这个,所以你不妨花一点时间阅读它:))...
我做的第一件事是查看 IL。与我的猜测相反,它 而不是 包含对同一对象的 Dispose()
的多次调用。那个理论到此为止。
但是,该方法确实包含对 Dispose()
的两次单独调用,只是针对不同的对象。到这个时候,我已经确信这是一个错误。我已经看到在处理相关 class 时触发 CA2202,其中一个 class 实例 "owns" 另一个 class 实例,并且两个实例都已处理。虽然不方便且值得抑制,但警告在这些情况下似乎是有效的;其中一个对象确实被处理了两次。
但在这种情况下,我有两个单独的 IEnumerator
对象;一个不拥有另一个,甚至与另一个没有关系。处置一个不会处置另一个。因此,代码分析就此发出警告是错误的。但是具体 是什么让它感到困惑?
经过多次试验,我想出了这个近乎最小的代码示例:
Public Class A
Public ReadOnly Property B As B
Get
Return New B
End Get
End Property
End Class
Public Interface IB
Function GetEnumerator() As IEnumerator
End Interface
Public Class B : Implements IB
Public Iterator Function GetEnumerator() As IEnumerator Implements IB.GetEnumerator
Yield New C
End Function
End Class
Public Class C
Dim _value As String
Public Property Value As String
Get
Return _value
End Get
Set(value As String)
_value = value
End Set
End Property
End Class
Public Shared Function GetCurrentVisualStudioInstance2() As A
For Each a As A In GetAs()
For Each c As C In a.B
If (c.Value = Nothing) Then
Return a
End If
Next c
Next a
Return Nothing
End Function
Public Shared Iterator Function GetAs() As IEnumerable(Of A)
Yield New A()
End Function
这会产生与您在其他代码示例中看到的相同的虚假 CA2202。有趣的是,对接口 IB
的声明和实现的一个小改动会导致警告消失:
Public Interface IB : Inherits IEnumerable
End Interface
Public Class B : Implements IB
Public Iterator Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
Yield New C
End Function
End Class
不知何故,代码分析被 GetEnumerator()
的非 IEnumerable
实现搞糊涂了。 (更奇怪的是,您正在使用的实际类型,DTE API 中的 Processes
接口,都继承了 IEnumerable
和 声明其自己的 GetEnumerator()
方法……但后者是代码分析混淆的根源,而不是组合。
有了它,我尝试在 C# 中重现该问题,但发现我做不到。我编写了一个 C# 版本,其结构与 VB.NET 版本中的类型和方法完全相同,但它在没有警告的情况下通过了代码分析。于是又看了看IL
我发现 C# 编译器生成的代码与 VB.NET 编译器非常相似,但不完全相同。特别是,对于保护每个循环返回的 IEnumerator
的 try
/finally
块,这些循环的所有初始化都在 外部 执行try
块,而在 VB.NET 版本中,初始化在内部执行。
显然,这也足以防止代码分析对一次性对象的使用感到困惑。
鉴于它似乎是 VB.NET 的 For Each
实现和嵌套循环的组合,一种解决方法是仅以不同方式实现该方法。无论如何我更喜欢 LINQ 语法,这里是你的方法的 LINQ 化版本,编译时没有代码分析警告:
Public Shared Function GetCurrentVisualStudioInstance() As DTE2
Dim processName As String = Process.GetCurrentProcess.MainModule.FileName
Return GetVisualStudioInstances.FirstOrDefault(
Function(instance)
Return instance.Debugger.DebuggedProcesses.Cast(Of EnvDTE.Process).Any(
Function(p)
Return p.Name = processName
End Function)
End Function)
End Function
为了完整起见,C# 版本(因为所有这些代码都是在 C# 实现转换为 VB.NET 然后扩展以处理 "current instance" 情况时开始的):
public static DTE2 GetCurrentVisualStudioInstance()
{
string processName = Process.GetCurrentProcess().MainModule.FileName;
return GetVisualStudioInstances()
.FirstOrDefault(i => i.Debugger.DebuggedProcesses
.Cast<EnvDTE.Process>().Any(p => p.Name == processName));
}