一个.Net/C#对象是否应该调用自身的Dispose()方法?

35

下面是同事编写的一些示例代码。我觉得这显然是错误的,但我想确认一下。一个对象是否应该在其自己的方法中调用自己的Dispose()方法?在我看来,只有对象的所有者/创建者在完成对象后才应该调用Dispose()而不是对象本身。

这是一个.asmx Web 方法,在完成时会调用自己的Dispose()。(它是 Web 方法可能与一般问题无关) 在我们的代码库中,有时会在其他 Web 服务方法中实例化 Web 服务类,然后调用它们的方法。如果我的代码这样做来调用此方法,当方法返回时对象就“失效”了,我就不能再使用这个对象了。

[WebMethod]
public string MyWebMethod()
{
    try
    {
        return doSomething();
    }
    catch(Exception exception)
    {
        return string.Empty;
    }
    finally
    {
        Dispose(true);
    }
}

更新:

在ASP.NET中,我是否需要释放Web服务引用?

如何释放Web服务代理类?


2
让同事解释一下这背后的原因,我们可能会帮你提出不这样做的论据 :D - David Mårtensson
@David:有些情况下一个对象是可序列化但不可克隆的;序列化一个实例会使其失效,任何序列化表示只能被反序列化一次。TCP套接字就是这样的例子。 - supercat
@David:我不相信我的同事真正理解Dispose的用途。我们有一个大型对象库,所有的类都保留了实现IDispose的实用程序类的实例,只是为了将Dictionary<>实例设置为null!每次使用using()时,这真的很令人沮丧。而且,别让我开始谈论所有的返回代码而不是异常。当然,当我开始使用.Net时,我并不知道所有的东西。;-) - Tom Winter
1
向您的同事解释IDisposable的目的不是让对象消失,而是在对象外部执行清理操作。如果一个对象将是执行此类清理所需信息和动力的最后一个持有者,则应实现IDisposable。一般来说,如果一个对象的Dispose方法不会清理自身外部的实体,那么它可能不存在。 - supercat
8个回答

36

毫无疑问,这不是一个好的做法。调用方应该决定何时停止使用IDisposable对象,而不是对象本身。


1
我同意,创建对象的过程必须能够依赖于它的可用性,而不会突然出现像空指针异常这样难以调试的错误消息。 - David Mårtensson
3
在这种情况下,@David 可能会遇到 ObjectDisposedException 异常。 - Adam Lear
2
调用Dispose的正确语义是“最后一个离开房间的人请关灯”。如果其他对象仍然需要它们,对象不应该自行处理,但如果一个对象发现它不再需要,并且没有其他对象及时处理它,那么该对象应该自行处理。 - supercat
@supercat:我不同意你的最后一句话。C#有垃圾回收机制,可以在程序最方便的时候处理对象的释放;而且它做得很好。唯一需要覆盖这个机制的情况是当对象涉及非托管资源时;这就回到了负责实例化对象的代码应该负责释放它,而不是对象本身。 - NotMe
1
@Chris Lively:如果一个对象以一种需要清理的方式操纵其外部某个实体的状态,那么这种清理通常应该在对象知道它不会干扰自己想要做的事情时立即执行。代码不应该依赖于终结器进行清理,除非有充分的理由(例如,将存在少量广泛共享的对象,并且很难知道每个对象的最后一个用户何时放弃了它)。许多事情都可能阻止终结器及时运行,因此确定性处理几乎总是更好的选择。 - supercat

8

进行"自销毁"操作的有效原因很少。

线程是我经常使用的一种方法。

我有几个应用程序会“fire and forget”线程。使用这种方式可以让对象自我销毁。

这有助于保持环境清洁,而无需使用线程管理进程。


我在类似的情况下使用过它。在所有情况下,我都知道该对象不再被使用。 - Michael Silver

4
如果我在我的项目中看到这样的东西,我会问为什么,99.9999%确定我会将其删除。对我来说,这是一种红旗 / 代码异味

1

Dispose方法没有任何技术限制,可以执行任何操作。 它唯一特殊的地方在于,在某些结构中(例如foreachusing)会调用Dispose方法。 因此,如果调用是幂等的,则Dispose可能合理地用于标记对象为不再可用。

然而,我不会出于这个目的使用它,因为Dispose的语义已经被接受了。 如果我想要从类本身将对象标记为不再可用,那么我会创建一个MarkUnuseable()方法,可以由Dispose或任何其他地方调用。

通过将Dispose的调用限制为常见的模式,您可以自信地对所有类中的Dispose方法进行更改,而不会意外破坏偏离常见模式的任何代码。


0

只需将其删除,但要注意在调用它的所有对象中进行处理。


唯一的用途是当Web浏览器调用Web方法时。IIS/ASP以某种方式创建对象,因此我认为它应该是Dispose它的对象。 - Tom Winter

0

从技术上讲,如果那个“方法”是终结器,并且您按照 Microsoft 指定的 Finalise 和 IDisposable 模式 进行实现,则可以这样做。


0

虽然 .Net 对象通常不会在自身上调用 Dispose,但有时候运行在对象内部的代码可能是最后一个使用它的东西。举个简单的例子,如果 Dispose 方法可以处理部分构造对象的清理工作,那么编写如下所示的构造函数可能会很有用:

Sub New()
  Dim OK As Boolean = False
  Try
    ... do Stuff
    OK = True
  Finally
    If Not OK Then Me.Dispose
  End Try
End Sub

如果构造函数将抛出异常而没有返回,则被标记为废弃的部分构造对象将是唯一拥有信息和动力进行必要清理的东西。如果它不负责确保及时的 Dispose,那么其他任何东西都不会。

关于您的特定代码片段,模式有些不寻常,但它看起来有点像套接字可以从一个线程传递到另一个线程的方式。有一个调用返回一个字节数组并使Socket失效的方法;该字节数组可以在另一个线程中使用,以创建一个新的Socket实例,该实例接管由其他Socket建立的通信流。请注意,有关打开套接字的数据实际上是未经管理的资源,但它不能很好地包装在具有终结器的对象中,因为它经常会被移交给垃圾回收器无法看到的东西。

我可以看到在构造函数中这样做,但仅限于此。 - Tom Winter
构造函数或工厂方法应该是我认为最常见的两种情况。正如我在其他地方所指出的,另一个场景是代码交接一个像TCP套接字这样的实例。如果交接成功,则接收方将释放套接字。如果失败,并且调用方不希望仍然具有活动的套接字,则交接方法可能必须自行处理处置。 - supercat

0
不行!这是意外的行为,违反了最佳实践准则。永远不要做任何意外的事情。你的对象应该只做必要的事情来维护它的状态,同时保护调用者对象的完整性。调用者将决定何时完成(或者如果没有其他情况,则由GC完成)。

@DustinDavis:在处理类似TCP套接字这样的可序列化但无法克隆的对象时,应该如何处理呢?除了在序列化过程中让套接字自行销毁之外,还有其他方法吗?如果对象在序列化后将变得无效,是否有任何理由不让它自行销毁呢? - supercat
@supercat 如果您的实例在反序列化后将变为无效状态,则应在反序列化过程中处理它(可以在类内部或通过工厂进行处理)。如果您序列化了 socket1 实例并且它已经被释放,则该实例对于消费者来说不再可用,这可能会导致意外行为。 - Dustin Davis
@DustinDavis:序列化套接字实例类似于在办公电话系统上“停泊”呼叫。当呼叫被停泊时,显示屏将显示“已停泊在102号”(或其他号码);然后我可以通过广播系统宣布“比尔——102号来电”,然后比尔可以去一个电话,按下“102”,并接管通话。在我停泊呼叫后,第一个按下令牌“102”的人将获得对话,但其他人则不能。如果我想的话,我也可以按下“102”,但那样就没有其他人能接听了。停泊呼叫通常意味着我自己不再拥有它这就是为什么我首先要停泊的原因 - supercat
@DustinDavis:顺便说一下,将套接字序列化的例程称为DuplicateAndClose;调用这样的例程的人不应该对套接字不再可用感到惊讶。我希望在其他情况下执行类似操作的例程也应该被命名为类似的名称。关于最初的问题,我猜DoSomething()可能在语义上类似于“DuplicateAndClose”,而上面的代码将是一个包装器,以确保即使序列化失败,对象也会得到清理。 - supercat
@supercat:我的DoSomething()并没有什么特别的。它只是从数据库中返回一个字符串。什么特别的事情都没有发生。 - Tom Winter
显示剩余2条评论

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