【问题标题】:Should a .Net/C# object call Dispose() on itself?.Net/C# 对象是否应该对自身调用 Dispose()?
【发布时间】:2011-06-30 00:53:08
【问题描述】:

以下是同事编写的一些示例代码。这对我来说显然是错误的,但我想检查一下。一个对象应该从它自己的方法之一中调用它自己的 Dispose() 方法吗?在我看来,只有对象的所有者/创建者才应该调用 Dispose() 来处理对象而不是对象本身。

这是一个 .asmx 网络方法,它在完成后会自行调用 Dispose()。 (它是一个 Web 方法这一事实可能与一般问题有关。)在我们的代码库中,我们有时会在其他 Web 服务的方法中实例化 Web 服务类,然后在它们上调用方法。如果我的代码这样做是为了调用这个方法,那么当方法返回时,对象就会吐司,我不能再真正使用该对象了。

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

更新: 找到了一些相关的链接:

Do I need to dispose a web service reference in ASP.NET?

Dispose a Web Service Proxy class?

【问题讨论】:

  • 让同事解释这背后的原因,我们可能会帮助您讨论为什么不这样做:D
  • @David:在某些情况下,对象是可序列化但不可克隆的;序列化实例会使它无效,并且任何序列化的表示只能反序列化一次。 TCP 套接字就是这样的。
  • @David:我不相信我的同事真的理解 Dispose 的用途。我们有一个大型对象库,并且所有类都保留一个实现 IDispose 的实用程序类的实例,以便它可以将 Dictionary 实例设置为 null!在不需要的时候一直使用 using() 是令人沮丧的。不要让我开始使用所有返回码而不是异常。当然,当我开始使用 .Net 时,我并不知道一切。 ;-)
  • 向您的同事解释 IDisposable 的目的不是让对象消失,而是对对象 外部 的实体执行清理。如果对象将成为执行此类清理所需的信息和动力的最后持有者,则该对象应实现 IDisposable。一般来说,如果一个对象的 Dispose 方法不打算清理它自身之外的实体,它也可能不存在。

标签: c# .net web-services dispose idisposable


【解决方案1】:

执行“自行处置”操作的正当理由很少。 线程是我经常使用的一种。

我有几个“触发并忘记”线程的应用程序。 使用这种方法可以让对象自行处置。

这有助于在没有线程管理器进程的情况下保持环境清洁。

【讨论】:

  • 我在类似的情况下使用过它。在所有情况下,我都知道该对象将不再被使用。
【解决方案2】:

不!这是出乎意料的行为,违反了最佳实践准则。永远不要做任何出乎意料的事情。您的对象应该只执行维护其状态所需的操作,同时为调用者​​保护对象的完整性。调用者将决定何时完成(或 GC,如果没有别的)。

【讨论】:

  • @DustinDavis:除了让套接字在序列化例程中自行处理之外,应该如何处理类似于 TCP 套接字的东西,它可以有效地序列化但不可克隆?如果对象在序列化后将无效,是否有任何理由不应该自行处理?
  • @supercat 如果您的实例在反序列化后将无效,那么您应该在反序列化过程中处理它(在类内部或通过工厂)。如果您序列化 socket1 实例并且它自行处理,那么 socket1 将不再被代码使用,这会使消费者感到困惑并可能导致意外行为。
  • @DustinDavis:序列化套接字实例类似于在办公室电话系统上“驻留”一个呼叫。当呼叫被暂留时,显示屏会显示“Parked on 102”(或其他号码);然后我可以在 PA 上宣布“Bill--拨打 102”,然后 Bill 可以去一个电话,输入“102”,然后接听电话。在我驻留呼叫后,第一个打入令牌“102”的人会得到对话,但其他人不能。如果我愿意,我可以自己打“102”,但没有其他人可以。停放电话通常意味着我自己不再拥有它这就是我首先停放的原因
  • @DustinDavis:顺便说一句,序列化套接字的例程称为 DuplicateAndClose;调用此类例程的人不应该对套接字不再可用感到非常惊讶。我希望在其他场景中执行类似操作的例程应该使用类似的名称。关于最初的问题,我猜测 DoSomething() 可能在语义上类似于“DuplicateAndClose”,并且上面的代码将是一个包装器,以确保即使序列化失败也能清理对象。跨度>
  • @supercat 如果有一个名为 DuplicateAndClose() 的方法,那么是的,应该没有意外,在这种情况下,处理需要处理的东西应该是可以接受的。我正在查看进行序列化的消费者的一般用法。
【解决方案3】:

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

子新() Dim OK As Boolean = False 尝试 ... 做东西 好的 = 真 最后 If not OK Then Me. Dispose 结束尝试 结束子

如果构造函数要抛出异常而不返回,那么将被放弃的部分构造的对象将是唯一拥有信息和动力进行必要清理的对象。如果它不能确保及时处置,那么其他任何事情都不会。

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

【讨论】:

  • 我可以看到在构造函数中这样做,但仅此而已。
  • 构造函数或工厂方法是我认为最常见的两种情况。正如我在其他地方所指出的,另一种情况是传递诸如 TCP 套接字之类的活动实例的代码。如果切换成功,接收方将处理套接字。如果不成功,并且调用代码不会期望仍然有一个活动的套接字,则切换方法可能必须自己处理处置。
【解决方案4】:

技术上是的,如果该“方法”是终结者,并且您正在实施 Microsoft 指定的 Finalise and IDisposable pattern

【讨论】:

    【解决方案5】:

    只需将其删除,但请注意将其放置在所有调用它的对象中。

    【讨论】:

    • 它获取的唯一用途是当网络浏览器调用网络方法时。 IIS/ASP 以某种方式创建了对象,所以我假设它应该是 Dispose 的对象。
    【解决方案6】:

    Dispose 方法可以做什么没有技术限制。唯一特别之处在于 Dispose 在某些构造中被调用(foreachusing)。因此,Dispose 可以合理地用于将对象标记为不再可用,尤其是在调用是幂等的情况下。

    但是,由于 Dispose 的公认语义,我不会将它用于此目的。如果我想在类本身中将一个对象标记为不再可用,那么我将创建一个可以由 Dispose 或任何其他地方调用的 MarkUnuseable() 方法。

    通过将对 Dispose 的调用限制为普遍接受的模式,您购买了对所有类中的 Dispose 方法进行更改的能力,并确信您不会意外破坏任何偏离常见模式的代码。

    【讨论】:

      【解决方案7】:

      如果我在我的一个项目中看到它,我会问为什么,我 99.9999% 肯定我会删除它

      对我来说这是一种red flag / code smells

      【讨论】:

        【解决方案8】:

        当然这不是一个好习惯。调用者应该决定何时结束使用 IDisposable 对象,而不是对象本身。

        【讨论】:

        • 我同意,创建对象的进程必须能够依赖它是可用的,而不是突然得到一个很难调试的空指针异常之类的错误消息。
        • @David ObjectDisposedException 在这种情况下。
        • 调用 Dispose 的正确语义是“最后一个离开房间的人请把灯关掉吗”。如果其他对象仍然需要对象,则对象不应自行处理,但如果一个对象发现不再需要它,并且没有其他人会及时处理它,则该对象应自行处理。
        • @supercat:我不同意你的最后一句话。 C# 具有垃圾收集功能,以便在程序最方便的时候处理对象处理;它非常擅长。唯一应该覆盖它的时间是对象处理非托管资源;回到负责实例化对象的代码应该负责释放它,而不是对象本身。
        • @Chris Lively:如果一个对象以需要清理的方式在其自身外部操纵某个实体的状态,则通常应在对象知道它不会干扰时立即执行此类清理它想做什么。代码不应该依赖终结器进行清理,除非有充分的理由(例如,会有少量广泛共享的对象,并且很难知道对象的每个最后一个用户何时都放弃了它)。许多事情都会阻止终结器及时运行,因此确定性处置几乎总是更好。
        猜你喜欢
        • 2023-01-10
        • 1970-01-01
        • 1970-01-01
        • 2011-11-23
        • 2013-01-23
        • 2011-05-04
        • 2013-08-09
        • 1970-01-01
        • 2011-04-13
        相关资源
        最近更新 更多