为什么这个方法会导致代码分析错误CA2000:调用Dispose()?

7
我正在使用“Microsoft Minimal Rules”代码分析集构建我的项目,并且该方法给我返回了CA2000警告:
private Timer InitializeTimer(double intervalInSeconds)
{
    Timer timer = null;

    try
    {
        timer = new Timer { Interval = intervalInSeconds * 1000, Enabled = true };
        timer.Elapsed += timer_Elapsed;
        timer.Start();
    }
    catch
    {
         if (timer != null)
         {
             timer.Dispose();
         }
    }
    return timer;
}

该方法只是从以秒为单位的间隔创建一个新的 System.Timers.Timer。我有三个这样的定时器在运行(每秒钟一个,每分钟一个和每半小时一个)。也许最好只有一个定时器,并在经过事件处理程序中检查是否已经过了一分钟或半小时,但我不知道,目前这种方法更容易,因为它是继承的代码,我还不想破坏一切。

该方法给我带来了臭名昭著的

Warning 21  CA2000 : Microsoft.Reliability : In method 'TimerManager.InitializeTimer(double)', call System.IDisposable.Dispose on object '<>g__initLocal0' before all references to it are out of scope.

现在我在catch中调用了Dispose,我认为这已经足够了?我还在类自己的IDisposable实现中释放了所有的定时器。
我在这里漏掉了什么?

4
移除对象初始化语法-这会触发警告(因为对象已经存在、构造,但在初始化程序运行时没有分配给 timer)。我正在尝试查找处理此问题的重复问题。 - Damien_The_Unbeliever
1
可能是在using块中使用对象初始化程序会生成代码分析警告CA2000的重复问题。 - Damien_The_Unbeliever
3
不要因为一个愚蠢的工具而放弃初始化程序语法。这个警告是虚假的,直到调用Start()之前没有什么需要处理的。吞噬异常并返回一个已释放的对象,那才是需要抱怨的事情。 - Hans Passant
1
@HansPassant - 由于Interval可能会抛出ArgumentException,因此分析正确,如果没有调用Dispose,则该可处理对象将被泄漏。 没有任何文档记录(我找不到)表明如果未调用Start,则无需Dispose计时器(尽管当前实现可能是这样)。 - Damien_The_Unbeliever
如果有文档记录,我们就不会有这个网站了。有时候我们必须动动自己的脑筋。 - Hans Passant
4个回答

2

只有在出现异常情况下才调用Dispose方法(顺便说一句,你不应该使用catch-all块来处理异常,但这是另一个故事)。如果没有异常发生,就不需要释放Timer对象。

要么添加一个finally块并将Dispose移动到其中,要么使用using块。


由于计时器在“非异常”情况下返回,这应该没问题吧?或者说,为什么它总是需要被处理呢? - user166390
好的,但我希望计时器在方法之后仍然存在,因为它被分配给了我的类中的一个字段。我正在IDisposable实现中处理我的字段。我的try-catch是为了抑制警告。 - Davio
@pst 是的,那应该没问题,但 CodeAnalysis 可能无法检测到它。你可以抑制那个特定的事件。 - Christian.K

1

好的,我这样编辑了:

private Timer InitializeTimer(double intervalInSeconds)
    {
        Timer tempTimer = null;
        Timer timer;
        try
        {
            tempTimer = new Timer();
            tempTimer.Interval = intervalInSeconds * 1000;
            tempTimer.Enabled = true;
            tempTimer.Elapsed += timer_Elapsed;
            tempTimer.Start();
            timer = tempTimer;
            tempTimer = null;
        }
        finally
        {
            if (tempTimer != null)
            {
                tempTimer.Dispose();
            }
        }
        return timer;
    }

这是根据CA2000文档,它没有发出警告。我忽略了对象初始化语法创建临时对象的事实,这些对象可能无法被处理。

谢谢,大家!


你可能想把finally改成一个带有rethrow的catch。否则,即使没有抛出异常,你也会在从方法返回计时器之前处理它。 - Nicole Calinoiu
@NicoleCalinoiu 只有在 temp 不为 null 且 temp 只有在没有到达 try 块的最后一行时才会处理,这意味着发生了异常。这只是避免捕获和重新抛出异常的方法。 - juharr

1
警告告诉您正在创建一个可丢弃的对象,但并非在所有情况下都进行处理。如果您在其他某个方法中正确地处理了它,则可以安全地抑制此警告(您可以使用SuppressMessageAttribute来实现)。

这是我在其他情况下(Unity的LifeTimeManager)正在做的事情,但在这里做之前想先检查一下。 - Davio
是的,我经常会在长时间运行的对象(例如定时器)中收到这个警告。它只是提醒你要在其他地方处理这个对象的释放。 - Marek Dzikiewicz

-1

我认为使用 "using" 而不是 "try/finally" 更好 参考资料

private Timer InitializeTimer(double intervalInSeconds)
{
    Timer timer;
    using (var tempTimer = new Timer())
    {
        tempTimer.Interval = intervalInSeconds * 1000;
        tempTimer.Enabled = true;
        tempTimer.Elapsed += timer_Elapsed;
        tempTimer.Start();
        timer = tempTimer;
    }
    return timer;
}

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