滥用IDisposable以从"using"语句中获益是否被认为是有害的?

17

IDisposable 接口的目的是有序释放非托管资源。它与 using 关键字搭配使用,定义一个作用域,在该作用域结束后,相关的资源会被处理掉。

由于这个机制非常简洁,我一再尝试让类实现 IDisposable 以便能够滥用这个机制,以不符合其预期的方式来处理嵌套上下文等情况:

class Context : IDisposable
{
    // Put a new context onto the stack
    public static void PushContext() { ... }

    // Remove the topmost context from the stack
    private static void PopContext() { ... }

    // Retrieve the topmost context
    public static Context CurrentContext { get { ... } }

    // Disposing of a context pops it from the stack
    public void Dispose()
    {
        PopContext();
    }
}

在调用代码中使用的示例:

using (Context.PushContext())
{
   DoContextualStuff(Context.CurrentContext);
} // <-- the context is popped upon leaving the block

请注意,这只是一个例子,不是这个问题的主题。

Dispose() 在离开 using 语句范围时被调用的事实也可以被利用来实现各种依赖于作用域的东西,比如计时器。这也可以通过使用 try ... finally 结构来处理,但在那种情况下,程序员需要手动调用一些方法(例如 Context.Pop),而 using 结构可以为其执行。

这种使用 IDisposable 的方式与其在文档中所述的预期目的不符,然而这种诱惑仍然存在。

有没有具体的原因说明这是一个坏主意,并永远打消我的幻想,例如垃圾收集、异常处理等方面的问题?或者我应该继续滥用这种语言概念,纵情享受?


5
还有很多其他可能被误用的概念,以上只是其中之一,唯一需要考虑的是可读性,请记住你的代码应该清晰易读,并且能够被其他开发人员维护。 - Habib
4
我同意Habib的观点。在编写代码时,要时刻考虑到18个月后将与你的代码一起工作的可怜家伙。很可能这个可怜家伙就是你自己 :-) - Zohar Peled
1
这是一个主观问题,但你可以注意到ASP.NET MVC框架在这方面滥用了IDisposable - 例如FormExtensions.BeginForm - Joe
2
我已经编辑了问题,使其更清晰,我不是在寻求意见(我已经有一个:“这很好用!”),而是要找到具体的原因,为什么这种用法可能会导致问题。 - waldrumpus
我也会检查相关问题,看看我的问题是否可以被关闭为重复。 - waldrumpus
3个回答

10

因此,在asp.net MVC视图中,我们看到以下结构:

using(Html.BeginForm())
{
    //some form elements
}

一种滥用?微软间接地表示不是。

如果你有一个需要在完成后发生某些事情的结构,IDisposable通常可以很好地解决这个问题。我做过这种事情多次。


4
类似于事务范围。 - Magnus
1
同spender的意见一致。微软有时会以不释放资源的方式使用它,所以我认为这是“允许”的。话虽如此,可读性应该是你关注的重点。我认为BeginForm很清晰;但从列表中弹出可能就不太清晰了。 - Matt Grande
如果“using”行包含“Push”,那么我认为弹出是非常明显的。 - Medinoc
@Medinoc 当然,如果包含 Begin ,那么 End 就显而易见了... 但两种情况都容易被忘记。使用构造只是增加了你成功的机会。 - spender
1
为什么不直接使用 try/finally 呢?它同样易读。 - Chris Dunaway
2
@ChrisDunaway using 已经是围绕着 try/finally 的语法糖,但它还带有一个方便的标准接口。如果没有 IDisposable,调用者需要知道如何清理,而 using 则将负担放在了创建者身上。 - Andrew Hanlon

3

这样使用IDisposable接口是否滥用了它?可能是的。

仅将using用作“作用域”构造是否能使代码意图更明显,可读性更好?当然可以。

对我来说,后者胜过前者,所以我建议使用它。


为什么不直接使用 try/finally 呢?它同样易读。 - Chris Dunaway
在这种情况下,我不同意@ChrisDunaway的观点,主要是因为(a)在这种情况下,你最终会得到一个空的“finally”,(b)在我看来,因为“using”比“try/finally”更清晰地声明了作用域意图。对我来说,“using”表示“我正在使用这个对象,并希望它在之后消失”;而“try/finally”表示“我可能会在这里遇到异常,并且我想处理它”。 - Ian Kemp
我不得不反对你的说法的一部分。try/finally(没有catch子句)并不意味着任何关于异常的事情。finally将具有任何必要的清理代码,并表示“我想做以下操作,然后最终清理”。至少这是我读到的方式。 - Chris Dunaway

0

你肯定不会是第一个以那种方式“滥用”IDisposable的人。也许我最喜欢它的用途是在计时器中,就像StatsD.NET客户端所展示的那样:

using StatsdClient;
...
using (statsd.LogTiming( "site.db.fetchReport" ))
{
  // do some work
}
// At this point your latency has been sent to the server

事实上,我相当确定微软自己在一些库中使用它。我的经验法则是 - 如果它能提高可读性,就采用它。

为什么不直接使用 try/finally 呢?它同样易读。 - Chris Dunaway
@ChrisDunaway 这里的重点是在 using 块内计时操作,而不是保护异常。您可以将其读作 using myTimer, do ...,通过这样做,将原本需要三行代码(timer.Start、timer.Stop、timer.SendToServer)的内容压缩成了一行(可以说更易读的)代码。 - georgevanburgh

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