Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dispose of a MemoryStream object

The following code is used to stitch together existing PDFs [As an aside we are using TallComponents to do the actual stitching, in case you were wondering what PDFUtility is]:

PDFUtility.Document docFinal = new PDFUtility.Document();
PDFUtility.Document docToAdd = null;
byte[] combinedFile;

foreach (byte[] content in fileContents)
{
    MemoryStream fileContentStream = new MemoryStream(content);
    docToAdd = new PDFUtility.Document(fileContentStream);
    docFinal.Pages.AddRange(docToAdd.Pages.CloneToArray());
}
using (MemoryStream stream = new MemoryStream())
{
    docFinal.Write(stream);
    combinedFile = stream.ToArray();
}

The glaring problem with this code is this command:

MemoryStream fileContentStream = new MemoryStream(content);

The memory stream fileContentStream is not getting disposed, potentially (I believe) holding onto resoures longer than needed.

The obvious solution would be to wrap the creation of the MemoryStream in a using block. The code would then look like this:

PDFUtility.Document docFinal = new PDFUtility.Document();
PDFUtility.Document docToAdd = null;
byte[] combinedFile;

foreach (byte[] content in fileContents)
{
    using (MemoryStream stream = new MemoryStream())
    {
        MemoryStream fileContentStream = new MemoryStream(content);
        docToAdd = new PDFUtility.Document(fileContentStream);
        docFinal.Pages.AddRange(docToAdd.Pages.CloneToArray());
    }
}
using (MemoryStream stream = new MemoryStream())
{
    docFinal.Write(stream);
    combinedFile = stream.ToArray();
}

The use of the using block in the above code causes the code to fail on this line (because the streams were previously disposed):

docFinal.Write(stream);

One possible solution would be to keep track of all the MemoryStream instances and dispose of them after they are done being used. This is the code for that:

PDFUtility.Document docFinal = new PDFUtility.Document();
PDFUtility.Document docToAdd = byte[] combinedFile;
List<MemoryStream> streams = new List<MemoryStream>();
foreach (byte[] content in fileContents)
{
    MemoryStream fileContentStream = new MemoryStream(content);
    streams.Add(fileContentStream); //EACH INSTANCE OF A STREAM IS TRACKED
    docToAdd = new PDFUtility.Document(fileContentStream);
    docFinal.Pages.AddRange(docToAdd.Pages.CloneToArray());
}
using (MemoryStream stream = new MemoryStream())
{
    docFinal.Write(stream);
    combinedFile = stream.ToArray();
}
streams.ForEach(s => s.Dispose()); //DISPOSE OF ALL STREAMS HERE

The code above works. I am simply delaying the Dispose until after the the final document is written out.

However, this doesn't seem like the "best" solution. Is there any way to implement using blocks (and thus guaranteeing the objects are properly disposed?

like image 986
Shai Cohen Avatar asked Nov 06 '15 21:11

Shai Cohen


1 Answers

using blocks are not much more than syntactic sugar for try-finally blocks.

Depending on how the using block is used, you end up with two types of try-finally blocks.

Case 1:

// This code ...
using( var thing = new Thing() ) {
    thing.DoOperation();
}

// ... turns into this scoped try-finally:
{
    var thing = new Thing();
    try {
        thing.DoOperation();
    }
    finally {
        thing.Dispose();
        thing = null;
    }
}

Case two:

// This code ...
var thing = new Thing();
using( thing ) {
    thing.DoOperation();
}

// ... turns into this code
var thing = new Thing();
try {
    thing.DoOperation();
}
finally {
    thing.Dispose();
    // Note the lack of a null assignment.
}

With this knowledge, you can modify your third solution to use a finally block to ensure that your MemoryStream objects are always cleaned up.

PDFUtility.Document docFinal = new PDFUtility.Document();
PDFUtility.Document docToAdd = byte[] combinedFile;
List<MemoryStream> streams = new List<MemoryStream>();

try 
{
    foreach (byte[] content in fileContents)
    {
        MemoryStream fileContentStream = new MemoryStream(content);
        streams.Add(fileContentStream); //EACH INSTANCE OF A STREAM IS TRACKED
        docToAdd = new PDFUtility.Document(fileContentStream);
        docFinal.Pages.AddRange(docToAdd.Pages.CloneToArray());
    }
    using (MemoryStream stream = new MemoryStream())
    {
        docFinal.Write(stream);
        combinedFile = stream.ToArray();
    }

}
finally 
{
    streams.ForEach(s => s.Dispose()); //DISPOSE OF ALL STREAMS HERE
}
like image 173
antiduh Avatar answered Oct 28 '22 04:10

antiduh