MemoryStream 而不是 byte[] 用于添加为资源的文件



我有一个 .NET 程序集,我在其中添加了许多文件作为资源(二进制,每个>500KB(。我以前一直在自动生成的 Resources 类上使用 ResourceManager.GetObject() 方法访问这些内容,该方法返回一个 byte[] .

出于性能和语法原因,我宁愿将这些二进制资源作为流而不是字节数组进行操作。我发现,通过手动编辑 .resx 文件并将 <value> 元素中的类名称从 System.Byte[] 更改为 System.IO.MemoryStream ,我能够使用 ResourceManager.GetStream() 方法成功访问资源作为流,例如

<data name="MyFile" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..ResourcesMyFile.ext;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>

成为:

<data name="MyFile" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..ResourcesMyFile.ext;System.IO.MemoryStream, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>

这种方法的唯一缺点是Visual Studio总是以byte[]形式添加新的文件资源。有没有办法让它为我设置MemoryStream类型?

您可以在 ResourceManager 上创建扩展方法。

public static class ResourceExtensions
{
    public static MemoryStream GetMemoryStream(this ResourceManager resourceManager, String name) {
        object resource = resourceManager.GetObject(name);
        if (resource is byte[]) {
            return new MemoryStream((byte[])resource);
        }
        else {
            throw new System.InvalidCastException("The specified resource is not a binary resource.");
        }
    }
}

ResourceManager resourceManager = Properties.Resources.ResourceManager;
MemoryStream stream = resourceManager.GetMemoryStream("binaryResource");

不过,这似乎也一样。

MemoryStream stream = new MemoryStream(Properties.Resources.SomeBinaryResource);

不确定我是否会修改资源文件,因为它们很容易更改,而且我确信在某些情况下 Visual Studio 会覆盖更改。

根据您的关注,这样做的问题是这会将数据的副本创建到内存中,从而产生内存占用。对于生存期很短的轻量级资源,这不是问题,但可能是一个大问题。

答案很短:您无法使用 ResourceManager 避免内存占用。问题是ResourceManager.GetObject(String)ResourceManager.GetStream(String)都会创建数据的副本。即使GetStream(String)返回一个UnmanagedMemoryStream,它实际上在内部调用GetObject(String),并且仍然会创建一个副本。如果调试应用程序并分析内存,您将看到内存仍已分配。

我尝试了多种方法来解决这个问题,方法是在unsafe上下文中使用指针,但反思没有任何效果。 ResourceManager只是没有那么灵活或优化。

但是,

我能够找到解决方案,但是它需要您使用Embedded Resources。这不会更改任何内容,除了将资源文件的生成操作设置为Embedded Resource Build Action。使用此功能,可以使用反射来创建不创建数据副本的UnmanagedMemoryStream

private UnmanagedMemoryStream GetUnmanagedMemoryStream(String embeddedResourceName) {
    Assembly assembly = Assembly.GetExecutingAssembly();
    string[] resourceNames = assembly.GetManifestResourceNames();
    string resourceName = resourceNames.SingleOrDefault(resource => resource.EndsWith(embeddedResourceName, StringComparison.InvariantCultureIgnoreCase));
    if (resourceName != null) {
        return (UnmanagedMemoryStream)assembly.GetManifestResourceStream(resourceName);
    }
    else {
        throw new System.ArgumentException("The specified embedded resource could not be found.", "embeddedResourceName");
    }
}

我没有对此进行广泛测试,但它确实有效。我的测试数据是一个 17 兆字节的小文件。我的测试应用程序的工作集内存从大约 50 MB 开始,在将资源检索到流中后,不会更改。使用ResourceManager时,它会立即增加工作集的资源大小。

您可能需要换出对EndsWith的调用,以检查清单中的正确资源名称,因为资源的名称与直接通过ResourceManager访问资源略有不同。

实际上很失望,我无法使用现有ResourceManager找到解决方案,但它不够灵活。

编辑 我在这里写了一篇关于这个主题的深入博客文章。

相关内容

  • 没有找到相关文章

最新更新