为什么我应该使用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,而使用语句本身就可以释放资源)

有什么具体的优势,或者我在这里遗漏了什么?

为什么我应该使用IDisposable而不是在c#中使用

在您的示例中使用语句是正确的,因为您只在方法的范围内使用资源。例如:

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;
        }
    }
}

首先需要注意的是,因为您似乎对usingIDisposable如何相互作用有点困惑:之所以能够说using (FileStream fileStream = Whatever()) { ... },正是因为FileStream类实现了IDisposable。你的同事建议你在类上实现IDisposable,这样你就可以说using (Test test = new Test()) { ... }了。

值得一提的是,我认为最初编写代码的方式比建议的更改要好得多,除非有一些令人信服的理由可以让FileStreamTest1实例的整个生命周期内保持开放。出现这种情况的一个原因是,在调用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)