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 编译器非常相似,但不完全相同。特别是,对于保护每个循环返回的 IEnumeratortry/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));
}