调用Dispose()后图标仍可用吗?

5

我一直在努力使用Dispose正确地处理我们的代码,以解决一些事情被留下的问题。其中一个例子是图标,我注意到了一些奇怪的事情,如果我调用Icon.Dispose(),我仍然可以使用这个图标。

所以我把它提取出来放到一个小的控制台应用程序中,我完全期望它会崩溃(抛出ObjectDisposedException),但它没有... 我是否误解了Dispose在这里应该做什么?

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;

namespace DisposeTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Icon icon = new Icon(@"C:\temp\test.ico");
            icon.ToBitmap().Save(@"C:\temp\1.bmp");
            icon.Save(new FileStream(@"C:\temp\1.ico", FileMode.OpenOrCreate, FileAccess.ReadWrite));
            icon.Dispose();
            GC.Collect(); // Probably not needed, but just checking.
            icon.Save(new FileStream(@"C:\temp\2.ico", FileMode.OpenOrCreate, FileAccess.ReadWrite));
            icon.ToBitmap().Save(@"C:\temp\2.bmp");
        }
    }
}

我认为这里没有收集图标,因为仍然有指向它的引用... - Darius Kucinskas
1
似乎Save()和ToBitmap()在内部使用了一个名为iconData的byte[],但在Dispose()中没有被处理。不过我并不确定其中的原因。 - Ian
1
@Darius:这不是关于垃圾回收的问题。它是关于处理的问题。GC.Collect()是完全无关的问题。 - recursive
@recursive 这是否意味着在调用Dispose()时会释放非托管资源? - Darius Kucinskas
据我所知,应该是可以的。但这并不是语言强制执行的。它只是通过惯例和“Icon”的“Dispose”方法的实现来执行的。 - recursive
4个回答

8
我将其提取为一个小控制台应用程序,我完全预计会崩溃,但它没有。这里我是否误解了dispose应该做什么?
包含不良编程实践的程序并不一定会崩溃。如果他们崩溃了,那当然很好,但他们不必如此。据我所知,文档没有说明“使用已释放的图标导致程序崩溃”是库的支持特性,因此您不能依赖它。
(如果文档确实在某个地方说了,那么您就发现了一个错误;如果您想在Connect上报告它,那将不胜感激。)
对于一个相关的问题,人们似乎觉得很有趣,请参见有关利用C中非崩溃行为的问题: 局部变量的内存可以在其作用域之外访问吗?

最后,你的评论指出垃圾收集器可能并不相关是正确的。记住,“Dispose”的目的是丢弃非托管资源。垃圾收集器的目的是通过终结器抛弃托管内存和抛弃非托管资源。由于图标的内存仍然存在(而且清除器可能已经压制了终结器),在这里强制进行垃圾回收不太可能起作用。


感谢您的回复,"crash" 是一个不好的词。实际上,我期望会收到一个 ObjectDisposedException 异常,但非常惊讶没有收到。我想这取决于如何处理它。您提到丢弃未管理的资源,我认为这是关键所在。在使用文件路径构建图标的情况下,数据被放入托管的 byte[] 中,并且可以在没有图标句柄等情况下继续工作。 - Ian

7
在您的情况下,Dispose()方法不会执行任何操作,因此不会导致崩溃。Icon类包装器可以包装两种不同的图标数据源。在您的情况下,图标数据来自文件并存储在byte[]中。这不是需要处理的系统资源,垃圾回收器已经处理了它。
另一个图标数据源来自Windows句柄。这相当罕见,但在使用Icon.FromHandle()或System.Drawing.SystemIcons之一时会出现。现在,它包装了一个未经处理的资源,使用Dispose()很有用。
这种模式并不罕见。还有很多情况下,将Dispose()继承到一个不太合理的类中。比如MemoryStream或BackgroundWorker。传统的智慧是无论如何都要处理它们。

Hans,如果您在Dispose()之后尝试任何操作,MemoryStream会抛出异常。而Backgroundworker确实不会这样做。 - H H

4
这种行为看起来可能有点奇怪,但确实是有道理的。通常,Dispose()用于释放非托管资源;这是一种与系统中其他进程“友好相处”的方式。在这种情况下,您可能会在Dispose()期间释放HICON句柄。与Icon相关的其余(托管的)资源(包括实际的byte[]数据)将在托管对象被收集时释放。
您可以通过调用GC.Collect()来尝试完成此操作,但那并不会有任何作用,因为Icon仍然存活:它被名为icon的局部变量引用,并且当前不符合收集条件。

0

1
虽然那是好建议,但它并没有回答问题,即“为什么这个程序没有崩溃?” - Eric Lippert
我有一个讨厌的习惯,就是草率地阅读问题并回答我认为它在问什么 :) 这应该是一条评论。 - Darren Young
@Eric:绝对是这样,我总是建议使用这种方法。实际上,这就是我最初发现这种情况的方式,即从流(或类似物)创建位图,再从位图创建图标,没有一个被处理。我不确定它们是否可以被处理(即图标是否需要构造它的底层位图),因此尝试将它们包装在using中。然后为了双重检查,我将返回的图标包装在using中,期望它会失败,但惊讶地发现我的所有图标都正确呈现了。 - Ian

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