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

Bra*_*ith 8 .net resx stream visual-studio-2010

我有一个.NET程序集,我已经添加了许多文件作为资源(二进制,每个> 500KB).我之前使用ResourceManager.GetObject()自动生成的Resources类上的方法访问它们,该类返回一个byte[].

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

<data name="MyFile" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Resources\MyFile.ext;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
Run Code Online (Sandbox Code Playgroud)

变为:

<data name="MyFile" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Resources\MyFile.ext;System.IO.MemoryStream, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
Run Code Online (Sandbox Code Playgroud)

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

Dav*_*son 5

您可以在上创建扩展方法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.");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

呼唤

ResourceManager resourceManager = Properties.Resources.ResourceManager;
MemoryStream stream = resourceManager.GetMemoryStream("binaryResource");
Run Code Online (Sandbox Code Playgroud)

虽然,这似乎也一样。

MemoryStream stream = new MemoryStream(Properties.Resources.SomeBinaryResource);
Run Code Online (Sandbox Code Playgroud)

我不确定是否会修改资源文件,因为它们很容易更改,而且我确定在某些情况下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");
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

我很失望,因为我无法使用现有的解决方案找到解决方案ResourceManager,但是它不够灵活。

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

  • 我担心使用您的方法,在构造和返回流之前,会将整个byte []读入内存。这就是为什么我希望能够调用GetStream而不是GetObject的原因。避免先将整个文件读入内存。 (2认同)