使用IDisposable从磁盘上删除文件

5
我有一个需求,需要将上传的HttpPostedFile保存到服务器的磁盘上,然后将其完整路径提供给某些会使用该文件的代码,最后删除该文件。我决定为处理该文件创建一个代理。代理抽象了将文件保存到磁盘和在不再使用时删除它的细节。我利用IDisposable实现了这个代理,以将已保存的文件视为未托管的资源并确保其在某个时刻被删除。当然,每次我尝试实现IDisposable时,我都会仔细检查这个模式,并发现涵盖所有最复杂实现的数十个问题和文章。
我认为大多数这样的实现对于我所需的功能来说过于复杂了,所以我使用了更简单的方法进行了实现。我的类中没有太多东西,只是保存文件和一些公共字符串,以通过文件路径访问保存的文件。还有一个Delete方法,专门用于删除文件,以及一个Dispose方法在客户端代码没有调用Delete时调用它。最后,有一个finalizer(终结器)仅调用Dispose方法。这个类是sealed的。没有任何成员本身实现IDisposable。没有重要的托管资源。据我估计,没有必要进一步干预垃圾收集,因为唯一重要的事情就是删除文件。
那么我的问题是:
1.这种处理“temp”文件的方法有什么本质上的问题吗?
2.考虑到我不需要清理受控资源,我的IDisposable实现是否存在任何问题?
请注意,在我的用例中,文件必须保存到磁盘以供另一段代码使用,并且需要通过其文件路径访问该文件,而不是通过传递流等方式。
public sealed class TempFileProxy : IDisposable
{
    private bool disposed;

    public TempFileProxy(HttpPostedFile httpPostedFile)
    {
        this.disposed = false;
        this.FileName = httpPostedFile.FileName;
        this.Directory = AppSettings("TempFileDirectory");
        this.FullPath = $@"{this.Directory}\{this.FileName}";

        httpPostedFile.SaveAs(this.FullPath);
    }

    ~TempFileProxy()
    {
        this.Dispose();
    }

    public string FullPath { get; }

    public string Directory { get; }

    public string FileName { get; }

    public void Dispose()
    {
        if (this.disposed)
        {
            return;
        }

        this.disposed = true;
        this.Delete();
    }

    public void Delete()
    {
        if (File.Exists(this.FullPath))
        {
            File.Delete(this.FullPath);
        }
    }
}

4
只要符合以下条件,这个问题就适合在 Code Review 上发帖:(a)您的代码按预期工作,(b)您的代码是真实存在的代码而非示例代码,以及(c)您将代码包含在问题正文中。如果您希望获得同行的审查来改善代码的各个方面,请在 Code Review 上发布它。 - Heretic Monkey
2个回答

2

使用IDisposable实现删除临时文件没有问题。 但是,在删除文件时要更加小心-您不希望在此操作期间抛出异常(例如,文件正在使用)。 此外,如果您已经使用常规Dispose调用处置了对象,请禁止finalizer:

~TempFileProxy() { 
    Dispose(false);
}

public void Dispose() { 
    Dispose(true); 
}

private void Dispose(bool disposing)
{
    if (disposing)
    {
        GC.SuppressFinalize(this);                
    }
    if (this.FullPath != null)
    {
        try { 
            File.Delete(this.FullPath); 
        }
        catch { }
        this.FullPath = null;
    }
}

这涉及到一个关于IDisposable的重要问题,我从未完全理解。如果我不想在Dispose中清理托管资源,我不能只是不处理它们,也不抑制终结器,让垃圾回收在适当时候自行处理吗?显然答案是否定的,因为没有任何示例展示过这种方式,但我不明白为什么不能这样做。 - bubbleking
为什么要首先使用finalizer?因为如果你的类的用户“忘记”调用Dispose,我们将在.NET垃圾回收实例时删除文件,这是在你的情况下需要finalizer的唯一原因。现在,如果用户没有忘记调用Dispose,则调用finalizer是无用的(即使finalizer绝对不执行任何操作-仍会导致显着的性能成本),因此我们告诉GC抑制它。请注意,IDisposable只是向类的用户表示您拥有某些资源的方便方式,这并不强制执行任何内容。 - Evk
那么,在调用Dispose的情况下会发生什么,Finalizer被抑制,但我在Dispose方法中没有将任何托管资源设置为null?FullPath和其他字符串是否会永远存在并且永远不会被垃圾回收? - bubbleking
您不需要处理托管资源。在此示例中,FullPath设置为null只是为了防止多次执行Dispose(因此,如果我调用Dispose两次-第二次尝试将不会发生任何事情)。您也可以使用布尔标志-相同的结果。我看到您有点困惑,认为我们在此处“处理”FullPath字符串-我们没有。因此,通过抑制终结器,您只需告诉.NET不要执行终结器中的代码,而不是不回收实例-无论如何都会回收它。 - Evk
好的,我明白了。我想我被很多教育资源所迷惑了,它们声称您可以/应该清理托管资源以及非托管资源。我的印象是GC负责清理对象及其托管成员,因此如果我不清理它们并且抑制Finalize,那么GC将永远不会清理这些成员。 - bubbleking

1

我这里有几个问题:

1)在某些外部设备上,File.Exists会出现故障。 我已经尝试了使用该文件并捕获异常的方法。

2)File.Delete可能会抛出异常。 2a)该文件正在被某个程序占用。 2b)有一个幽灵锁留下。(Windows 8 XPS查看器,我在看着你!) 2c)网络问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接