1

这是我正在处理的代码的一部分(为清楚起见进行了修改):

public Stream getMyArchiveStream(string archivepath)
{
    using(var archive = ZipFile.OpenRead(_filepath))
    {
        var entry = archive.GetEntry(archivepath);
        return entry.Open();
    }
}

public void useMyArchiveStream()
{
    using(var myStream = getMyArchiveStream("test.path"))
    {
        //Do stuff
    }
}

现在这失败了,因为存档正在被处理在 getMyArchiveStream 的出口,这阻止了myStream使用

有没有办法在处理myStream时处理存档

另一种方法是让存档保持打开状态并使包含的类成为一次性的,但这在可用性方面有其自身的缺点。

背景:

我创建了一个简单的打包类(至少比 System.IO.Packaging 更简单),它将文件作为字节数组返回。显然这会消耗大量内存,我想改用流。

4

2 回答 2

0

简而言之,你正在走你不能走的捷径。正如您的问题标题已经字面上所说的那样,您正在尝试使用一个依赖于您处置的对象的对象。

您要做的是打开一个文件,从中读取有关其内部的一些信息(不是实际的内部本身),然后关闭该文件,然后尝试使用该信息实际从该文件中读取。不可能; 该文件已经关闭。就像你不能从它自己的using块内返回一个一次性对象而不最终得到一个已处置且因此不可用的对象一样,你显然也不能返回依赖于一次性对象的东西。

getMyArchiveStream所以,基本上,你的功能背后的整个思考过程都是有缺陷的。您根本不应该拥有该功能。您只需要像这样制作其他功能:

public void UseMyArchiveStream()
{
    using(var archive = ZipFile.OpenRead(_filepath))
    {
        var entry = archive.GetEntry("test.path");
        using(var myStream = entry.Open())
        {
            //Do stuff
        }
    }
}

一种选择确实是保持archive开放......但正如mjwills评论的那样,还有另一种方法可以做你想做的事,那就是给出UseMyArchiveStream一个Action<>orFunc<>作为论点。这实际上意味着上面代码中的“Do stuff”注释被对您作为参数提供的任何函数的调用所取代:

public void UseMyArchiveStream(String zipPath, String entryName, Action<Stream, String> doStuff)
{
    using (var archive = ZipFile.OpenRead(zipPath))
    {
        var entry = archive.GetEntry(entryName);
        using (var myStream = entry.Open())
        {
            doStuff(myStream, entry.FullName);
        }
    }
}

用函数演示void SaveStreamToFile(Stream file, String filename)

UseMyArchiveStream(_filepath, "test.path", (str, nm) => SaveStreamToFile(str, nm));

使用Func<>,您也可以创建一个提供返回值的重载。中的最后一个参数<>始终是返回类型。但是您可以轻松地使用泛型来使其依赖于调用输入:

public T UseMyArchiveStream<T>(String zipPath, String entryName, Func<Stream, String, T> doStuff)
{
    using (var archive = ZipFile.OpenRead(zipPath))
    {
        var entry = archive.GetEntry(entryName);
        using (var myStream = entry.Open())
        {
            return doStuff(myStream, entry.FullName);
        }
    }
}

您可以以相同的方式调用它,仅使用返回值而不是返回值的函数void。证明Boolean DostuffWithFile(Stream file, String entryName)

Boolean ok = UseMyArchiveStream(_filepath, "test.path", (str, nm) => DostuffWithFile(str, nm));

请注意,您调用的函数不必与参数的确切签名匹配。您可以通过这种调用方式完美地用本地数据替换缺少的参数。

证明Boolean DostuffWithFile(Stream file, String entryName, Boolean someOption, String outputFolder)

Boolean ok = UseMyArchiveStream(_filepath, "test.path", (str, nm) => DostuffWithFile(str, nm, true, _savePath));

只要需要提供的输入UseMyArchiveStream只是=>. 当然,你可以随心所欲地处理论点;你甚至可以只给函数整个ZipArchiveEntry对象,甚至可能是 source ZipFile,所以你可以用它做任何你想做的事情。

这种方法的唯一缺点是您实际上无法命名Action<>or的组件Func<>,因此,在这种情况下,您无法仅从函数签名中UseMyArchiveStream知道String给定的参数是否Func<Stream, String, T>将接收entry.Nameor entry.FullName。这同样适用于给出相同类型的多个参数;如果您有五个布尔选项,Func<>您可能很难准确记住哪个是哪个,而不必每次都查看代码。所以一定要在函数注释中准确记录,以免以后混淆。

于 2018-05-14T06:24:22.937 回答
-1

了解为什么调用 dispose 非常重要,这样您就知道何时必须调用它,何时不需要调用它。Dispose 和 Finalize密切相关。

Finalize 就是确保 GC 可以释放非托管资源(文件句柄、网络句柄、本机寻址的内存空间)。但是,虽然可以确定 GC会运行,但它何时运行并不确定。事实上,如果它只在应用程序关闭时运行,那是大多数实现所针对的理想情况。

Dispose 就是要确保 Finalization 是确定性的。当您处置时,您不再完成。你尽早完成最终确定的工作。您可以在需要时执行此操作,而不是在 GC 处理时执行。

有很多细节,但对我来说,归结为有两种实现 IDisposeable 的情况: * 直接处理非托管资源。在这种情况下,您首先进行最终确定。然后将 Dispose 作为便利/可用性功能。您不会真正遇到这种情况,因为大多数情况都由框架程序员处理。* 您处理任何实现 IDisposeable 的类,其唯一目的是将 Dispose 调用“中继”到所有包含实例。95% 的 Disposeable 调用都与此有关。

对于 ZipArchive,我的猜测是它实现 IDisposeable 的主要原因,是因为它必须包含文件句柄。而 Entry 可能只是实现它,因为它拥有对 ZipArchive 实例的引用。因此,它应该中继呼叫。虽然 ZipArchiveEntry 中可能还有其他需要处理的东西,但我认为这不太可能。因此,只要您确定 ZipArchive 实例已正确处理,您就应该能够减少这种使用。

于 2018-05-14T00:24:17.610 回答