是否进行Dispose操作(CA2000)

13
我在对一个旧项目开启代码分析功能。大多数结果我都能理解,但是CA2000: 在作用域失效前释放对象很难做到正确。
例如,下面这段来自ASP.Net页面的代码:
private void BuildTable()
{
    HtmlTableRow tr = new HtmlTableRow();
    HtmlTableCell td = new HtmlTableCell();

    tr.Cells.Add(td);
    // add some controls to 'td'

    theTable.Rows.Insert(0, tr);
    // 'theTable' is an HtmlTable control on the page
}

提供 CA 消息:

CA2000 : Microsoft.Reliability : 在方法 'BuildTable()' 中,在所有引用到 'tr' 对象的作用域之外调用 System.IDisposable.Dispose。

CA2000 : Microsoft.Reliability : 在方法 'BuildTable()' 中,'td' 对象在所有异常路径上都没有被处理。在所有引用到 'td' 对象的作用域之外调用 System.IDisposable.Dispose。 (对于添加到该 'td' 的控件也有类似的消息。)

我可以解决第二个问题:

private void BuildTable()
{
    HtmlTableRow tr = new HtmlTableRow();
    HtmlTableCell td = new HtmlTableCell();

    try
    {
        tr.Cells.Add(td);
        // add some controls to 'td'

        td = null; // this line is only reached when there were no exceptions
    }
    finally
    {
        // only dispose if there were problems ('exception path')
        if (td != null) td.Dispose();
    }

    theTable.Rows.Insert(0, tr);
}

但我不认为有可能解决关于“tr”的消息。 我无法处理它,因为在方法退出后仍然需要它。

或者是我错过了什么?

顺便说一下:将theTable.Rows.Insert更改为theTable.Rows.Add会将CA消息更改为“在所有异常路径上未被处理”


try-finally只是为了防止编译器提示CA2000错误,对吧?它并没有任何有用的作用! - Arjan Einbu
1
@Arjan:大部分是的 :-),但并不完全:如果在构建表格单元时发生了某些异常,那么该表格单元现在已被处理掉。类似的代码将用于添加到<td>的控件。 - Hans Kesting
1
请参见 https://dev59.com/F2865IYBdhLWcg3wNLw7,了解如何消除CA2000警告并转移所有权。 - Ian Ringrose
5个回答

11
代码分析无法完全理解您的代码,只会在创建似乎不需要被释放的可处理对象时发出警告。在您的情况下,应该关闭此警告,因为该对象在离开方法之前不应该被释放。您可以通过自定义代码分析规则集在整个项目中关闭警告,或者在具有此警告的每个方法上关闭警告,如果很明显代码分析是错误的。

话虽如此,我推荐您在处理IDisposable对象时使用using结构。
using (var tr = new HtmlTableRow()) {
  using (var td = new HtmlTableCell()) {
    tr.Cells.Add(td);
    theTable.Rows.Insert(0, tr);
  }
}

除非您不想处理刚刚添加到表中的行和单元格,否则此代码是无意义的。


我知道 using 语句总是会释放对象,这就是为什么我在这里没有使用它。但你正确地明确了它。 - Hans Kesting
1
代码分析在这个示例中是正确的,但通常情况下,CA有助于您编写更好的代码,当您确信比CA更了解它时,请随意按右鼠标键警告,并选择“在源文件中禁止”。为了更好,还可以将命名参数'Justification="why i suppressed this warning"'添加到生成的属性中。 - eFloh

3

我认为你刚刚表明了CA2000规则在大多数代码库上并不是非常有用。

据我所知,

  • 在HtmlTableRow上调用Dispose除非在UI设计器内使用,否则没有任何作用;我从未见过任何人在Asp.net控件上调用dispose。(Winforms/WPF是不同的情况)
  • 您将td的引用存储在表格中,因此无论如何都不应该将其处理掉。

由于上述两种情况在正常代码中非常常见,因此我认为CA2000规则对于大多数代码库来说并没有价值——有太多的虚警,当它是一个真正的问题时,你很可能会在50个案例中错过1个。


3
当然。在代码仍然需要控制的情况下,不能随意丢弃控制器。但是当代码崩溃时,清理可以清理的内容并不会有坏处。 - Hans Kesting

2
这段代码可以消除两个警告(我使用using(HtmlTable)来模拟全局HtmlTable成员...):
using (HtmlTable theTable = new HtmlTable())
{
    HtmlTableRow tr = null;
    try
    {
        HtmlTableCell td = null;

        try
        {
            td = new HtmlTableCell();

            // add some controls to 'td'


            tr = new HtmlTableRow();
            tr.Cells.Add(td);

            /* td will now be disposed by tr.Dispose() */
            td = null;
        }
        finally
        {
            if (td != null)
            {
                td.Dispose();
                td = null;
            }
        }

        theTable.Rows.Insert(0, tr);

        /* tr will now be disposed by theTable.Dispose() */
        tr = null;
    }
    finally
    {
        if (tr != null)
        {
            tr.Dispose();
            tr = null;
        }
    }
}

但我认为您可以考虑使用子函数的方法来使代码更清晰:

    private static void createTable()
    {
        using (HtmlTable theTable = new HtmlTable())
        {
            createRows(theTable);
        }
    }

    private static void createRows(HtmlTable theTable)
    {
        HtmlTableRow tr = null;
        try
        {
            tr = new HtmlTableRow();
            createCells(tr);

            theTable.Rows.Insert(0, tr);

            /* tr will now be disposed by theTable.Dispose() */
            tr = null;
        }
        finally
        {
            if (tr != null)
            {
                tr.Dispose();
                tr = null;
            }
        }
    }

    private static void createCells(HtmlTableRow tr)
    {
        HtmlTableCell td = null;

        try
        {
            td = new HtmlTableCell();

            // add some controls to 'td'


            tr.Cells.Add(td);

            /* td will now be disposed by tr.Dispose() */
            td = null;
        }
        finally
        {
            if (td != null)
            {
                td.Dispose();
                td = null;
            }
        }
    }

1
在创建控件后,但在对控件进行任何操作之前,直接将控件添加到集合中。
HtmlTableRow tr = new HtmlTableRow();
theTable.Rows.Insert(0, tr);

HtmlTableCell td = new HtmlTableCell();
tr.Cells.Add(td);

// add some controls to 'td'

由于在创建控件和将其添加/插入到集合之间不能出现异常,因此无需使用try/catch。当控件添加到集合后发生异常时,页面将处理它。这样就不会出现CA2000。


2
很抱歉要说,但是当Add()调用引发异常时,您会有一个悬空的对象,因此在这种情况下也需要try-finally块。 - eFloh

0
如果您认为代码分析有误(我曾经遇到过它要求调用未实现IDisposable接口的对象的Dispose方法),或者您不认为需要对该对象进行Dispose,您可以通过以下方式始终禁止该消息。
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000: DisposeObjectsBeforeLosingScope")]
public static IDataReader RetrieveData(string conn, string sql)
{
    SqlConnection connection = new SqlConnection(conn);
    SqlCommand command = new SqlCommand(sql, conn);
    return command.ExecuteReader(CommandBehavior.CloseConnection);
    //Oops, I forgot to dispose of the command, and now I don't get warned about that.
}

1
它并不是在警告你SqlCommand,因为它没有实现IDisposable接口:它是在警告你SqlConnection,你一定要确保它被处理掉。 - StriplingWarrior

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接