MemoryStream instead of byte [] for files added as resources?

I have a .NET assembly to which I have added several files as resources (binary,> 500 KB each). I previously accessed them using the ResourceManager.GetObject() method in the auto-generated Resources class, which returns byte[] .

For performance and syntax reasons, I prefer to use these binary resources as streams instead of byte arrays. I found that by manually editing the .resx file and changing the class name in the <value> element from System.Byte[] to System.IO.MemoryStream , I can use the ResourceManager.GetStream() method to successfully access resources as streams, for example

 <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> 

becomes:

 <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> 

The only drawback of this approach is that Visual Studio always adds new file resources to the byte[] form. Is there a way so that I can install it for a MemoryStream for me?

+4
source share
1 answer

You can create an extension method on the 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."); } } } 

Call

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

Although it seems that this is also good.

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

I am not sure that I would modify the resource file, since they are fragile to change, and I am sure that there are scripts in which Visual Studio will overwrite the changes.

The problem with this, in your opinion, is that it creates a copy of the data in memory, creating an amount of memory. For light resources that are short-lived, this is not a problem, but it can be a big problem.

The answer is short: you cannot escape the memory trace with the ResourceManager . The problem is that both ResourceManager.GetObject(String) and ResourceManager.GetStream(String) create a copy of the data. Even if GetStream(String) returns UnmanagedMemoryStream , it actually calls GetObject(String) calls internally, and a copy is still created. If you are debugging an application and viewing it, you will see that the memory is still allocated.

I tried several ways around this using pointers in the unsafe context, and reflection and nothing worked. ResourceManager simply not flexible or optimized.

However, I managed to find a solution, but for this I need to use Embedded Resources . This does not change anything, except that you set the build action of your resource files to the Embedded Resource for Build Action . Using this, you can use reflection to create an UnmanagedMemoryStream that does not create a copy of the data.

 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"); } } 

I have not tested this extensively, but it really works. My test data was a small 17 megabyte file. The working set memory of my test application starts at a speed of about 50 megabytes, and after retrieving the resource into the stream does not change. When using the ResourceManager it will immediately increase the working set by the size of the resource.

You probably need to change the call to EndsWith , which checks the correct resource name in the manifest, because the resource names are slightly different from accessing it directly through the ResourceManager .

I am really disappointed that I could not find a solution using the existing ResourceManager , but it is not flexible enough.

Edit I wrote a detailed blog article about this subject .

+5
source

Source: https://habr.com/ru/post/1411036/


All Articles