如果在处理之前复制了IDisposable的引用,它仍然会被处理吗?

4
考虑以下内容:
interface IFoo : IDisposable { }

class Program
{
  static void Main()
  {
    var foo = GetFoo();

    var anotherFoo = foo;

    using(anotherFoo)
    {
    }

    // Will the object on the heap be marked for collection? 
    // Or will this confuse the garbage collector 
    // as we are copying references?
  }
}

这引出了一个更重要的问题。Dispose() 实际上是做什么的?

2
“Dispose”只是一个方法(按照惯例用于资源管理),它与垃圾回收器决定是否需要回收对象无关。 - Alexei Levenkov
哦,该死!我总是忘记了!对不起。非常抱歉。我总是忘记这个。 - Water Cooler v2
@AlexeiLevenkov 请把它作为答案,我会将其标记为正确的答案。 - Water Cooler v2
1
某种程度上,http://stackoverflow.com/questions/11936216/why-does-garbage-collection-in-c-sharp-xna-not-dispose-of-render-targets-autom 的问题与此相反(他们期望收集会导致处理,而您想知道处理是否会影响收集),因此答案可能会为您填补一些空白。 - Jon Hanna
太棒了,@JonHanna。我要出门了,但看起来我将花费很多时间阅读它。它看起来非常有趣。我已经读了问题,打算回来读答案。太美了! :-) - Water Cooler v2
3个回答

5
考虑到需要显式释放除托管内存(非托管资源)之外的资源,Dispose 方法被添加;GC 并不是专门为此设计的。
此外,IDisposable 背后的机制与 GC 无关。当您遵循 Dispose 模式 时,您可以通过将类定义为可终结来将代码插入到 GC 中,但这不是实现 IDisposable 的必要部分。
释放与清理对象所持有的隐藏资源有关,而不是托管内存中的 "壳"。经常情况下,在调用 Dispose 后,对象会在尝试使用它时引发 ObjectDisposedException
回到您的代码,由于两个变量实际上指向同一个对象,在 using 块之后,foo 变量将引用一个已释放的对象。因此,在 using 后调用方法或访问 foo 的属性可能会引发异常:
var foo = GetFoo();

var anotherFoo = foo;

using(anotherFoo)
{
}

foo.doSomethingUseful(); // <<== This may throw ObjectDisposedException!

非常感谢。非常有帮助。 - Water Cooler v2
可能抛出(may throw)还是一定抛出(surely throws)? - T.Todua
@T.Todua 这取决于实现方式。ObjectDisposedException只是一个普通的异常。例如:https://github.com/npgsql/npgsql/blob/24f86ed2572ff48158b8e93aa3547d8380f4c754/src/Npgsql/NpgsqlDataReader.cs#L930。 - Artur INTECH

3
你所描述的“复制引用”对原始对象没有任何影响。它只是通过不同的名称查找相同对象的另一种方式,就像如果你有一个单独的电子邮件地址,邮件总是被寄到你的邮箱一样。无论哪种方式,你都可以得到相同的对象。因此,如果你处理其中一个,则处理另一个。
当你有以下代码时:
using (anotherFoo) 
{
}

您正在使用using语法,以确保在块完成之前发生的最后一件事情是对变量anotherFoo引用的对象上的.Dispose方法进行处理。 因此,它与using (foo) { ... }完全相同。

编辑回应评论

首先,最重要的是,IDisposable与垃圾收集没有任何关系。那是自动进行的。 在这个例子中,由于fooanotherFoo都是局部变量,而且不会通过闭包、方法调用等方式从方法中逃逸出去,因此它们将在方法调用完成后几乎立即被GC回收。

另一方面,IDisposable旨在纯粹允许您释放(通常是)外部资源,例如文件、UI原语、套接字等操作系统句柄。虽然确实希望在GC时对象处置其资源,但如果没有IDisposable,您无法控制反向操作 - IDisposable允许您在不再使用它时立即清理资源。这使您能够针对处理外部资源的问题设计更加确定性的解决方案 - 而不仅仅依赖于GC所允许的内容。


谢谢。我理解我正在做的语义和using结构的用途。只是确认垃圾收集器是否也检查引用计数作为标记对象进行收集的标准。我需要再次阅读Jeffrey Richter的书籍CLR via C#Applied .NET Framework Programming - Water Cooler v2
3
@WaterCoolerv2,.NET 中没有引用计数,因此垃圾回收从不检查引用计数。它会检查任何线程或静态方式都无法引用的对象 - 这是一个是/否问题,而不是“有多少”的问题。这与 IDisposable 的唯一关联是,如果某物已被垃圾回收,则不能再进行处理。(虽然可以进行终结)。 - Jon Hanna
谢谢你,@JonHanna。非常漂亮的答案。非常有见地。 - Water Cooler v2

1
将任何引用类型的每个变量、参数或其他存储位置视为持有类似于“Object #291”(但具有不同的数字)的令牌,并假设系统具有某种神奇的能力,可以根据该形式的令牌定位任何对象并访问其字段或告诉它执行某些操作。在某个东西上调用Dispose会告诉它:“您的服务不再需要。一旦您从此方法返回,可能随时被放弃而无需进一步通知。自行处理。”正常的期望是,如果对象已经请求了宇宙中任何外部实体代表其执行操作,则对象将告诉该外部实体它不再需要这样做。
假设Object #291是一个File。它已经要求操作系统给它独占地访问磁盘上的文件,操作系统通过返回文件句柄来响应该请求;没有该句柄,任何人都无法访问该文件。在对象#291上调用Dispose将指示它告诉操作系统它不再需要访问该文件,并且它将不再使用该句柄进行任何目的。一旦发生这种情况,Object #291将不再能够访问磁盘上的文件,并且尝试从Object #291读取数据将失败。
请注意,除非强根引用的数量降为零,否则Object #291的行为不受引用数量的影响。只有在此情况下,引用数量才会产生影响。当对象被判定为废弃时,如果该对象已经要求接收废弃通知(通过实现一个名为Finalize的方法)系统将确保在该对象停止存在之前唤醒该对象并运行其Finalize方法。请注意,其它时间的引用数量不会影响对象的行为。

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