【问题标题】:Writing a practical example of IDisposable写一个 IDisposable 的实际例子
【发布时间】:2017-10-17 15:01:56
【问题描述】:

我一直在阅读有关 Dispose 模式的信息,并且我有点了解它的用途(清理资源以便我的应用程序不会泄漏内存),但我希望在一个实际示例中看到它。

我的想法是编写一个简单的应用程序,首先使用一些资源并且不释放它们,然后在一些代码更改后,适当地释放这些资源。我想看到的是代码更改之前/之后的内存使用情况,以可视化处置的帮助。

问题:我可以使用哪些对象?我试图用一些大图像(JPEG 图像 15+ MB 大小)来做,但我不能用它来构建一个实际的例子。当然,我对其他想法持开放态度。

【问题讨论】:

  • 仅供参考,如果您不处置这些资源,GC 仍会清理这些资源(前提是它们是受管理的)。一个很好的例子是调用 C++ DLL,这需要您手动分配内存,然后使用 dispose 清理/释放所有内容。
  • 你通常将它用于非托管资源,只是说
  • @rolls - 您的声明“仅供参考,如果您不处理这些资源,GC 仍会清理这些资源(假设它们是受管理的)”有点误导。你把这句话最重要的部分放在括号里,让它看起来不重要。大多数IDisposable 对象拥有非托管资源,因此它们不会被 GC 清理。

标签: c# .net memory-leaks idisposable


【解决方案1】:

此单元测试符合您的描述。它在调用Dispose 和不调用Dispose 时执行相同的操作,以显示不调用Dispose 时会发生什么。

这两种方法都创建一个文件,打开文件,写入,然后再次打开并再次写入。

第一个方法抛出一个IOException,因为StreamWriter 没有被释放。该文件已打开,无法再次打开。

第二个在尝试重新打开文件之前进行处理,并且没有例外。

[TestClass]
public class DisposableTests
{
    [TestMethod]
    [ExpectedException(typeof(IOException))]
    public void DoesntDisposeStreamWriter()
    {
        var filename = CreateFile();
        var fs = new StreamWriter(filename);
        fs.WriteLine("World");
        var fs2 = new StreamWriter(filename);
        fs2.WriteLine("Doesn't work - the file is already opened.");
    }

    [TestMethod]
    public void DisposesStreamWriter()
    {
        var filename = CreateFile();
        var fs = new StreamWriter(filename);
        fs.WriteLine("World");
        fs.Dispose();
        var fs2 = new StreamWriter(filename);
        fs2.WriteLine("This works");
        fs2.Dispose();
    }

    private string CreateFile()
    {
        var filename = Guid.NewGuid() + ".txt";
        using (var fs = new StreamWriter(filename))
        {
            fs.WriteLine("Hello");
        }
        return filename;
    }
}

您可能不会看到内存使用的问题,因为这不是 IDisposable 专门解决的问题。所有对象都使用内存,但大多数不是一次性的。垃圾收集器从不再引用的对象中回收内存(例如在方法中创建但在方法结束时超出范围的对象。)

IDisposable 用于类持有某些未进行垃圾回收的资源的情况。另一个例子是SqlConnection。这与内存无关,而是与 SQL Server 的连接有关。只有这么多可用。 (它最终会被释放,但不是以可预测的方式 - 这是题外话。)

类可能是IDisposable 的确切原因各不相同。他们往往没有共同点。 IDisposable 不在乎原因是什么。这只是意味着类中有一些东西需要清理。

【讨论】:

  • 最终在一个简单的 Winforms 应用程序中实现了您的示例。工作完美,现在关于 IDisposable 的目的和它解决的问题变得更加清晰。谢谢你。编辑:如果其他人想要,我可以分享代码。
【解决方案2】:

以下示例显示了实现 IDisposable 接口的一般最佳实践。创建一些MyResouce 实例并在不调用Dispose 方法的情况下对其进行测试,存在内存泄漏。然后在每次完成 MyResouce 实例后调用 Dispose,没有内存泄漏。 Reference

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

【讨论】:

    猜你喜欢
    • 2018-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-13
    • 2016-10-27
    • 2011-04-13
    • 2023-03-26
    • 2011-02-01
    相关资源
    最近更新 更多