我在函数中使用Return之后应该调用Dispose()吗?

9
在返回实现IDisposable接口的对象后,我是否应该调用.Dispose()方法?
myDisposableObject Gimme() {
  //Code
  return disposableResult;
  disposableResult.Dispose();
}

换句话说,我返回的对象是一个副本还是原始对象本身?谢谢 :)

@AMissico:“释放返回的对象不是方法的责任。”不幸的是,这并不总是正确的。对于某些方法和某些属性获取器,调用方需要负责释放返回的对象,而对于其他情况,则不需要,因为该对象被缓存到其他地方并在其他地方释放。显然,没有简单的规则可以找出哪种情况,除了通过试错。 - Niki
2
另外,如果我说错了,请有人纠正我,你的最后一行代码实际上永远不会被调用,因为 return 语句会退出该方法。 - Chris Haas
@nikie:在托管代码中,这总是正确的。我不知道.NET Framework库中有任何例外情况。(通常涉及Interop到非托管代码的例外情况。)显然,您不会仅因为它实现了“IDisposable”而Dispose一个对象。没有要求您调用“Dispose”。接口允许程序员在对象不再需要时“清理”非托管资源。这是.NET Framework设计目标的一部分。为消除程序员处理内存管理和与内存相关的错误的需要。 - AMissico
1
@nikie:“显然,没有简单的规则可以找出哪个除了试错之外;”我完全不同意。阅读有关该对象的文档即可。 - AMissico
对象的所有者应该处理它。这是一个工厂。工厂创建对象。除非实例是单例并且工厂拥有其生命周期,否则不应该处理它。而且,如果工厂在将对象交给您之前处理它,那么它还有什么用处呢?此外,在返回后放置任何代码(没有try/finally块)基本上是无用的,编译器应该会给出一个无法访问的代码警告。 - BrainSlugs83
显示剩余25条评论
12个回答

11
不,你不应该这样做。你返回的是对象的引用,所以不会发生复制。在.NET中,除非你明确要求,否则永远不会复制对象。
此外,即使有应该这样做的情况,你也无法使用上述代码处理对象的释放。在return语句之后的代码将永远不会被执行,并且你将收到关于无法访问的代码的警告。

9

指的是对象本身。即使你将顺序倒过来,也不要在这里调用Dispose。


8

到目前为止,还没有任何答案提到的一点是如果 Gimme() 抛出异常,应该 释放该对象。例如:

MyDisposableObject Gimme() 
{
    MyDisposableObject disposableResult = null;
    try
    {
        disposableResult = ...

        // ... Code to prepare disposableResult

        return disposableResult;
    }
    catch(Exception)
    {
        if (disposableResult != null) disposableResult.Dispose();
        throw;
    }
}

+1,有趣,不知道那个。我会开始对于容易出现异常的对象这么做。 - Camilo Martin
1
总的来说,这是对于工厂等方面的一个好建议。但是根据他发布的代码,我不太确定,也许它只是伪代码,但在他发布的代码中,它没有创建disposableResult,它可能是一个共享对象等等。对象的所有者应该是负责处理它的人...如果它创建了对象(或导致对象被创建),并且只是要设置它并将其返回,但在此过程中抛出异常,则在这种情况下应该处理它。 - BrainSlugs83

4

disposableResult.Dispose()将永远不会执行,因为它是无法访问的代码,它总是在返回前一行。请将方法调用封装在using语句中,并以这种方式处理对象。

例如:
using (DisposeableObject myDisposableObject = gimme())
{
    //code.
}

你的代码会导致编译错误,因为你使用关键字 object 命名了一个变量 :P - prostynick
2
是的,我也不认为"...code." 部分是有效的语法;-) 不管怎样,我已经修改了它。 - Ben Robinson
2
通常,//…是你使用的。 - AMissico

3
如果您使用的对象实现了 IDisposable 接口,您应该在 using 语句中构建和使用它 - 这将确保它被正确处理:
using(var mydisposableObject = new Gimme())
{
   // code
}

你的代码结构有问题,你返回了一个可释放对象,所以调用Dispose方法永远不会被执行。

我是指函数体内部,而不是外部。 - Camilo Martin
@Camilo Martin - 这样做没有意义,因为永远不会调用Dispose。您应该使用using语句包装对此函数的调用,以便在对象超出范围时正确调用Dispose - Oded
如果在“using”语句内执行了“return mydisposableObject;”,则在执行return语句和调用者第一次使用mydisposableObject之间会发生mydisposableObject的.Dispose。在大多数情况下,这将使mydisposableObject对调用者完全无用。 - supercat
这是正确的做法--他告诉你的是,你的工厂不应该处理对象的释放--而是对象的所有者(调用你的工厂的东西)应该处理它。 (除非你的工厂正在返回一个单例,它管理生命周期,但即使是这样,它也不应该在那里处理它--它应该在其他地方处理)。 - BrainSlugs83

3
.Dispose() 永远不会被执行。 编辑: 我认为不应该这样做,因为这会破坏对象。

3
如果您返回对象,那么在返回之前不应该将其释放。由调用者负责释放它。

1
你可以将你的代码放在try/finally块中。
    try{
            int a = 0;
            return;
    }
    finally{
            //Code here will be called after you return
    }

1

这一行代码:disposableResult.Dispose(); 将不会被执行。返回的“东西”不是对象的副本,而是对象的引用,因此调用者将在由 Gimme 创建的对象上进行操作,并且他(调用者)应记得处置该对象。


1

我同意不要处理这个对象,因为它是通过引用传递的。有一种特殊情况,当生成对象时,一个类包装另一个类并分发对象:您不希望分发的对象是对同一个对象的引用,因此您需要克隆或传递对象的副本并销毁原始对象。但是,如果原始对象是该类型所有生成的标准图像,并且您预计在短时间内会生成多个对象,则可以保留它,因为您可以从该图像生成新对象而无需再次实例化它。我会选择将其所有属性锁定为只读,并将只读对象转换为可读/写对象,以便在现实世界中使用。当作为值副本或克隆传递对象时,对象不应在自身内部运行任何线程,但是如果它正在线程化,则将其引用传递是完全可以的,只要引用是1对1的关系。如果您有多个指向同时读取和写入该“运行图像”对象的对象的指针,则可能会发生冲突,其中某个值尚未安全存储,下一个 pinger 请求并分配了相同的值。当我在学校时,我们没有太多讨论对象状态。面向对象的原则已经根深蒂固,但理解为什么建立它们始终需要时间。我是反向的,我学习了面向对象的样式,然后转向了更多的过程化样式。


1
欢迎来到SO!这个问题是我2.5年前提出的,所以我只能评论你的回答写作风格:请考虑稍微改进一下,因为目前它看起来有点像一堵文字墙。它也有一些错别字,这里的人们往往对此很敏感。话虽如此,再次感谢你的经过和回答。 - Camilo Martin
抱歉这里有一大段文字,只是想举一个我们需要销毁对象的实例。 - user2051624

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