写一个 IDisposable 的实际例子
Writing a practical example of IDisposable
我一直在阅读 Dispose 模式,我有点理解它的用途(清理资源,这样我的应用程序就不会泄漏内存),但我想在一个实际的例子中看到它。
我的想法是编写一个简单的应用程序,首先使用一些资源但不释放它们,然后在更改一些代码后适当地释放这些资源。我想看到的是内存使用情况 before/after 代码更改以可视化处理的帮助。
问:我可以使用哪些对象?我尝试用一些大图像(JPEG 图像 15+ MB 大小)来做,但我不能用它构建一个实际的例子。当然,我对其他想法持开放态度。
以下示例展示了实现 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.
}
}
此单元测试执行您所描述的内容。它在调用 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
适用于 class 保留某些未被垃圾收集的资源的情况。另一个例子是SqlConnection
。这与内存无关,与 SQL 服务器的连接有关。只有这么多可用。 (它最终会被释放,但不是以可预测的方式——这是题外话。)
class 可能 IDisposable
的确切原因各不相同。他们通常没有共同点。 IDisposable
不关心是什么原因。这只是意味着 class 中有些东西需要清理。
我一直在阅读 Dispose 模式,我有点理解它的用途(清理资源,这样我的应用程序就不会泄漏内存),但我想在一个实际的例子中看到它。
我的想法是编写一个简单的应用程序,首先使用一些资源但不释放它们,然后在更改一些代码后适当地释放这些资源。我想看到的是内存使用情况 before/after 代码更改以可视化处理的帮助。
问:我可以使用哪些对象?我尝试用一些大图像(JPEG 图像 15+ MB 大小)来做,但我不能用它构建一个实际的例子。当然,我对其他想法持开放态度。
以下示例展示了实现 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.
}
}
此单元测试执行您所描述的内容。它在调用 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
适用于 class 保留某些未被垃圾收集的资源的情况。另一个例子是SqlConnection
。这与内存无关,与 SQL 服务器的连接有关。只有这么多可用。 (它最终会被释放,但不是以可预测的方式——这是题外话。)
class 可能 IDisposable
的确切原因各不相同。他们通常没有共同点。 IDisposable
不关心是什么原因。这只是意味着 class 中有些东西需要清理。