今天,我想用文件执行操作,所以我想出了这段代码
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
这里有什么特别的优点或遗漏了吗?
答案 0 :(得分:8)
是正确的,因为您仅在方法的范围内使用资源。例如:
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;
}
}
}
答案 1 :(得分:8)
首先要注意一点,因为您似乎对using
和IDisposable
之间的互动方式感到有些困惑:您之所以能说出using (FileStream fileStream = Whatever()) { ... }
,正是因为FileStream
类实现IDisposable
。您的同事建议您在 课程上实施IDisposable
,以便您能够说using (Test test = new Test()) { ... }
。
对于它的价值,我认为你最初编写代码的方式比建议的更改更强烈,除非有一些令人信服的理由说明为什么你可能希望FileStream
在{的整个生命周期内保持开放状态Test1
{1}}实例。可能出现这种情况的一个原因是,在Test1
的构造函数被调用之后,该文件可能会从其他来源发生变化,在这种情况下,您将被困在较旧的数据副本。保持FileStream
打开的另一个原因可能是,如果您特意想要在Test1
对象处于活动状态时将文件锁定从其他位置写入。
通常,最好尽快释放资源,原始代码似乎就是这样做的。我有点怀疑的一件事是,工作是在你的构造函数中完成的,而不是在一些从外部显式调用的方法(解释:http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/)。但这完全是另一回事,并且与是否要使您的班级实施IDisposable
的问题无关。
答案 2 :(得分:5)
根据您提供的信息,绝对没有理由在IDisposable
上实施Test
或终结器。
仅实现终结器以释放非托管资源(窗口句柄,GDI句柄,文件句柄)。除非您正在使用Win32 API或其他东西,否则通常不必执行此操作。 Microsoft已在FileStream
中为您提供了这样的内容,因此您无需担心文件句柄。
终结器用于在对象被垃圾回收时清理非托管资源。
由于垃圾收集器在决定收集对象之前可能需要很长时间,因此您可能希望有一种方法来触发清理。不,GC.Collect()
不是正确的方法。 ;)
要允许尽早清理本机资源而不必等待垃圾收集器,您可以在类上实现IDisposable
。这使调用者可以在不等待GC的情况下触发清理。这样做不会导致您的对象被GC释放。所有这一切都是免费的原生资源。
如果一个对象拥有另一个Disposable对象,那么拥有对象也应该实现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
。如果你这样做,那可能是糟糕的设计。
答案 3 :(得分:4)
“No One”给出的答案是正确的,using
块只能用于实现IDisposable
接口的类,并且对它的解释是完美的。你这边的问题是“为什么我需要在Test class上添加IDisposable,但在代码审查时,我被要求在Test class上实现IDisposable接口和Finalizer方法。”
答案很简单
1)按照许多开发人员遵循的编码标准,在使用某些资源的类上实现IDisposable
总是好的,一旦该对象的范围超过该类中的Dispose方法将确保所有资源已经释放
2)已编写的类永远不会在将来不会进行任何更改,如果进行了此类更改并添加了新资源,则开发人员知道他必须在Dispose函数中释放这些资源。
答案 4 :(得分:2)
我们为什么要使用IDisposable
简短的回答是,任何未实现IDisposable的类都不能用于使用。
使用语句时本身可以释放资源
不,它本身不能释放资源。
正如我上面所写,你需要实现IDisposable以便能够使用。现在,当您实现IDisposable时,您将获得Dispose方法。在这种方法中,您可以编写所有代码,这些代码应该处理在不再需要该对象时需要处理掉的所有资源。
USING的目的是当一个对象超出其范围时,它将调用dispose方法,就是这样。
实施例
using(SomeClass c = new SomeClass())
{ }
将转换为
try
{
SomeClass c = new SomeClass();
}
finally
{
c.Dispose();
}
答案 5 :(得分:0)
我认为问题更像是“我应该立即处理文件还是使用访问该文件的类的Dispose方法?”
这取决于:如果您在我看来只在构造函数中访问该文件,则没有理由实现IDisposable。使用是正确的方式
否则,如果您在其他方法中也使用相同的文件,也许这是一个很好的做法,打开文件一次,并确保在Dispose方法中关闭它(实现IDisposable)