临时解压缩 - 这是对 IDisposable 接口的有效使用吗?

Temporary unzip - is this a valid use of the IDisposable interface?

我想封装解压缩 zip 文件的过程,使文件可供使用,然后在不再需要时自动清理它们。我使用实现 IDisposable 接口的 class 来完成此操作,这样我就可以使用 "using" 对其进行实例化,并且文件将在超出范围时被清理,从而无需专门删除文件。因此 class、TempUnzip 可以这样使用:

    static void AccessZipFileContents(string zipFilePath)
    {
        using (var temp = new TempUnzip(zipFilePath)
        {
            var tempPath = temp.TempPath;
            if (tempPath != null)
            {
                // read/use the files in tempPath
            }
        } // files automatically get deleted when it goes out of scope! Woohoo!
    }

下面是 TempUnzip 的实现 class:

using System.IO;
using System.IO.Compression;
public class TempUnzip : IDisposable
{
    public TempUnzip(string zipFilePath)
    {
        try
        {
            var tempFolderName = Path.GetRandomFileName();
            var tempFolder = Path.GetTempPath();
            var tempPath = Path.Combine(tempFolder, tempFolderName);
            Directory.CreateDirectory(tempPath);
            ZipFile.ExtractToDirectory(zipFilePath, tempPath);
            TempPath = tempPath;
        }
        catch (Exception) { TempPath = null; }
    }

    public readonly string TempPath;

    public void Dispose()
    {
        try
        {
            if (TempPath != null)
                Directory.Delete(TempPath);
        }
        catch (Exception) { }
    }
}

这是对 IDisposable 的有效使用吗?

在这种情况下,我会说它是 IDisposable 的一个很好的例子,如果没有立即调用 Dispose 就说明你已经用完了它,那么这还不是世界末日,因此请快速调用它zip 文件不会导致异常,因为您每次都使用唯一的临时文件夹;这又一次你不会让文件指针在文件等上保持打开状态。我能看到的唯一问题是,如果磁盘 space 真的很紧,你可能想公开删除文件夹作为允许该选项的方法直接调用然后 Dispose 仅​​用于在所有情况下进行清理。

注意:在这个例子中你可能想调用 Directory.Delete(TempPath, true);因为如果文件夹不为空(您的 catch 将隐藏),您调用的方法将抛出 IOException - 请参阅 https://msdn.microsoft.com/en-us/library/62t64db3%28v=vs.110%29.aspx

这是对 IDisposable 的有效使用吗?

来自documentation

Provides a mechanism for releasing unmanaged resources.

本地磁盘上的文件肯定是非托管资源。因此,这种用法符合 IDisposable.

的既定目的

如果是这样,我是否需要实现完整的标准 IDisposable 模式?

可以。需要考虑有关终结器的常见注意事项,但您已经链接到那些。肯定不会痛的。

如果没有,是否有更好的方法来封装文件的创建和销毁,使其与对象的生命周期相关联,或者我应该完全避免这种情况?

我也喜欢用函数式方法来解决这类问题。这将使您的示例看起来像这样:

static void AccessZipFileContents(string zipFilePath)
{
    ZipManager.RunWithZipExtracted(zipFilePath, (string tempPath) =>
    {
        if (tempPath != null)
        {
            // read/use the files in tempPath
        }
    } // files automatically get deleted when it goes out of scope! Woohoo!
}

//from ZipManager.cs...
static void RunWithZipExtracted(string zipLocation, Action<string> toRun)
{
    var tempPath = CalculateTempPath();
    try
    {
        ExtractZip(zipLocation, tempPath);
        toRun(tempPath);
    }
    finally
    {
        DeleteFolder(tempPath);
    }
 } //private methods left as exercise for the reader

这样的模式完全避免了 "what if they don't call Dispose?" 的问题。

在这种情况下,拥有终结器可能是个好主意,但 MS 模式是基于 public 具有终结器的对象的想法,即几乎总是一个坏主意。相反,需要 finalization-based 清理的资源应该封装在 privately-held 对象中,这些对象的引用永远不会暴露给外界。因为内部对象是私有的,所以它们不需要使用 IDisposable 模式——相反,它们可以以最适合要求的任何方式设计。

由于多次尝试关闭文件句柄可能会产生灾难性后果,并且终结器有可能(尽管很少见)在其他代码正在使用对象(甚至对其执行 Dispose 时执行) !),编写一个健壮的 class 可能很困难。出现的一个讨厌的问题是文件访问可以阻止但终结器操作不应该。可以通过创建一个线程来解决这个问题,该线程的目的是等待文件应该被关闭和删除,然后关闭并删除文件。即使删除文件的尝试被阻止,其他终结器操作也可以继续快速进行。不幸的是,为了允许安全 finalizer-based 清理而急切地创建线程很容易浪费资源,但线程创建似乎是在终结器中执行的 excessively-heavyweight 任务。我不知道最好的解决方案是什么。