为什么我应该使用IDisposable而不是在c#中使用
本文关键字:我应该 IDisposable 为什么 | 更新日期: 2023-09-27 18:30:01
今天,我想用一个文件执行一个操作,所以我想出了这个代码
class Test1
{
Test1()
{
using (var fileStream = new FileStream("c:''test.txt", FileMode.Open))
{
//just use this filestream in a using Statement and release it after use.
}
}
}
但在代码审查中,我被要求实现IDisposable接口和Finalizer方法
class Test : IDisposable
{
Test()
{
//using some un managed resources like files or database connections.
}
~Test()
{
//since .NET garbage collector, does not call Dispose method, i call in Finalize method since .net garbage collector calls this
}
public void Dispose()
{
//release my files or database connections
}
}
但是,我的问题是,我为什么要这样做?
尽管我无法证明我的方法,但为什么我们要使用IDisposable,而使用语句本身就可以释放资源)
有什么具体的优势,或者我在这里遗漏了什么?
在您的示例中使用语句是正确的,因为您只在方法的范围内使用资源。例如:
Test1()
{
using (FileStream fs = new FileStream("c:''test.txt", FileMode.Open))
{
byte[] bufer = new byte[256];
fs.Read(bufer, 0, 256);
}
}
但如果资源是在一个方法之外使用的,则应该创建Dispose方法。此代码错误:
class Test1
{
FileStream fs;
Test1()
{
using (var fileStream = new FileStream("c:''test.txt", FileMode.Open))
{
fs = fileStream;
}
}
public SomeMethod()
{
byte[] bufer = new byte[256];
fs.Read(bufer, 0, 256);
}
}
正确的做法是实现IDisposable
,以确保文件在使用后会被释放。
class Test1 : IDisposable
{
FileStream fs;
Test1()
{
fs = new FileStream("c:''test.txt", FileMode.Open);
}
public SomeMethod()
{
byte[] bufer = new byte[256];
fs.Read(bufer, 0, 256);
}
public void Dispose()
{
if(fs != null)
{
fs.Dispose();
fs = null;
}
}
}
首先需要注意的是,因为您似乎对using
和IDisposable
如何相互作用有点困惑:之所以能够说using (FileStream fileStream = Whatever()) { ... }
,正是因为FileStream
类实现了IDisposable
。你的同事建议你在类上实现IDisposable
,这样你就可以说using (Test test = new Test()) { ... }
了。
值得一提的是,我认为最初编写代码的方式比建议的更改要好得多,除非有一些令人信服的理由可以让FileStream
在Test1
实例的整个生命周期内保持开放。出现这种情况的一个原因是,在调用Test1
的构造函数之后,文件可能会从其他源发生更改,在这种情况下,您将使用数据的旧副本。保持FileStream
打开的另一个原因可能是,如果您特别希望在Test1
对象处于活动状态时锁定文件,防止从其他地方写入。
一般来说,尽快释放资源是一种很好的做法,你的原始代码似乎就是这样做的。有一点我有点怀疑,这项工作是在你的构造函数中完成的,而不是在从外部显式调用的方法中完成的(解释:http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/)。但这完全是另一回事,与是否让类实现IDisposable
的问题无关。
"No One"给出的答案是正确的,using
块只能用于实现IDisposable
接口的类,对此的解释是完美的。您的问题是"为什么我需要在Test类上添加IDisposable,但在代码审查时,我被要求在Test类中实现IDisposaable接口和Finalizer方法。"
答案很简单
1) 根据许多开发人员遵循的编码标准,在使用某些资源的类上实现IDisposable
总是很好的,一旦该对象的范围超过该类中的Dispose方法,就会确保所有资源都已释放
2) 已经编写的类永远不会在将来进行任何更改,如果进行了这样的更改并添加了新的资源,那么开发人员就知道他必须在Dispose函数中释放这些资源。
根据您提供的信息,绝对没有理由在Test
上实现IDisposable
或终结器。
仅实现终结器以释放非托管资源(窗口句柄、GDI句柄、文件句柄)。除非您正在调用Win32 API或其他程序,否则通常不必执行此操作。微软已经在FileStream
中为您包装好了这个,这样您就不必担心文件句柄了。
终结器用于在垃圾收集对象时清理非托管资源。
由于垃圾收集器可能需要很长时间才能决定收集对象,因此您可能希望有一种触发清理的方法。不,GC.Collect()
不是正确的方法。)
为了允许在不等待垃圾收集器的情况下尽早清理本机资源,您可以在类上实现IDisposable
。这使得调用方可以在不等待GC的情况下触发清理。这不会导致GC释放对象。它所做的只是释放本机资源。
在一个对象拥有另一个一次性对象的情况下,拥有对象的对象也应该实现IDisposable
,并简单地调用另一个对象的Dispose()
。
示例:
class Apple : IDisposable
{
HWND Core;
~Apple() { Free(); }
Free()
{
if(Core != null)
{
CloseHandle(Core);
Core = null;
}
}
Dispose() { Free(); }
}
class Tree : IDisposable
{
List<Apple> Apples;
Dispose()
{
foreach(var apple in Apples)
apple.Dispose();
}
}
请注意,Tree
没有终结器。它实现Dispose
是因为它必须关心Apple
清理。Apple
有一个终结器来确保它清理Core
资源。Apple
允许通过调用Dispose()
进行早期清理
您不需要Dispose
,当然也不需要终结器的原因是,您的类Test
不拥有任何非托管或IDisposable
的成员字段。您确实创建了一个FileStream
,它是一次性的,但您在离开该方法之前将其清理干净。它不属于Test
对象。
这种情况有一个例外。如果您正在编写一个您知道将由其他人继承的类,并且其他人可能必须实现IDisposable
,那么您应该继续实现IDisposable
。否则,调用方将不知道如何处理对象(甚至可以在不强制转换的情况下进行处理)。然而,这是一种代码气味。通常情况下,您不会从类继承并向其添加IDisposable
。如果您这样做,则可能是糟糕的设计。
为什么我们应该使用IDisposable
简而言之,任何不实现IDisposable的类都不能与using一起使用。
当使用语句时,它本身可以释放资源
不,它自己不能释放资源。
正如我在上面所写的,您需要实现IDisposable以便能够使用。现在,当您实现IDisposable时,您将获得一个Dispose方法。在这个方法中,您编写了所有的代码,这些代码应该负责在不再需要对象时需要处理的所有资源。
USING的目的是当一个对象超出其作用域时,它将调用dispose方法,就这样。
示例
using(SomeClass c = new SomeClass())
{ }
将转换为
try
{
SomeClass c = new SomeClass();
}
finally
{
c.Dispose();
}
我认为问题更像是"我应该立即处理文件还是使用访问该文件的类的dispose方法?"
这取决于:在我看来,如果你只在构造函数中访问文件,就没有理由实现IDisposable。使用是正确的方法
否则,如果您在其他方法中也使用相同的文件,那么最好打开一次文件,并确保在Dispose方法中关闭它(实现IDisposable)