1
\$\begingroup\$

I'm working on a feature for work that requires me to combine a set number of memory streams based on specific conditions.

Previous Code

Before based on documentation/examples online. In order to create a PDF to print, you have to create a Memory Stream and save that document into a Memory Stream. Here lies my old code.

    PdfDocument testDocument1 = new PdfDocument(testParametersToDesignUniquePDF1);
    PdfDocument testDocument2 = new PdfDocument(testParametersToDesignUniquePDF2);
    PdfDocument testDocument3 = new PdfDocument(testParametersToDesignUniquePDF3);
    MemoryStream testDocument1Stream = new MemoryStream();
    MemoryStream testDocument2Stream = new MemoryStream();
    MemoryStream testDocument3Stream = new MemoryStream();
    testDocument1.Save(testDocument1Stream);
    testDocument2.Save(testDocument2Stream);
    testDocument3.Save(testDocument3Stream);

    Stream[] source = { testDocument1, testDocument2};

    if (testCondition)
    {
        source = { testDocument1Stream, testDocument2Stream, testDocument3Stream}
    }

As you can see its only a matter of time before my code gets chunkier as for every PdfDocument, I create I have to create a new memory stream. There's also a limitation of having a static array that I have to keep initializing a new array as more condition applies. I fixed this code with the following below.

New Code

My Code Refactor is to create a dynamic List that I can add x number of streams based on x conditions. Then convert back to a static size array after I'm done. It saves a lot of extra code creating memory streams and saving my documents on it.

    List<MemoryStream> memoryStreams = new List<MemoryStream>();

    memoryStreams.Add(ConvertToMemoryStream(new PdfDocument(testParametersToDesignUniquePDF1)));
    memoryStreams.Add(ConvertToMemoryStream(new PdfDocument(testParametersToDesignUniquePDF2)));

    if (testCondition)
    {
       memoryStreams.Add(ConvertToMemoryStream(new PdfDocument(testParametersToDesignUniquePDF3)));
    }

    //Convert dynamic linked list back to an array of steam.
    Stream[] source = memoryStreams.ToArray();

Method to convert PdfDocument to a stream.

 private MemoryStream ConvertToMemoryStream(PdfDocument document)
    {
        MemoryStream stream = new MemoryStream();

        document.Save(stream);

        return stream;
    }

I tested this code to have the same performance and output as the original code. However, the code has more flexibility adding more conditions and PDF's. It is also a lot more readable than the previous code. However, I am worried about a few things.

  1. Deallocation - I might not be taking care of disposing my streams if I manipulate it this way.
  2. Time of Deallocation - I'm worried that since I don't have a reference to my streams that it might get disposed on its own before actually using it when I Print the PDF's showing a blank PDF.
\$\endgroup\$
  • 2
    \$\begingroup\$ Curious, why do you iterate through the list to create an array instead of using List<T>.ToArray()? \$\endgroup\$ – tinstaafl Feb 14 at 5:39
  • \$\begingroup\$ Great point! I changed it into one line of code =) \$\endgroup\$ – Mochi Feb 14 at 15:47
  • \$\begingroup\$ Do you really need a collection of streams? If their only purpose is to print, you could use a temporary stream inside a Print() method of a class. \$\endgroup\$ – Jerome Viveiros Feb 15 at 18:21
  • 1
    \$\begingroup\$ @JeromeViveiros The PdfDocumentBase.Merge method requires a static array of stream of pdfs to merge all my required arrays based on conditions. I needed a collection to dynamically add pdfs based on conditions. I don't store it anywhere, I simply gather all the pdfs I need, collect them in a dynamic list as above, revert it to a static array of stream, and print. \$\endgroup\$ – Mochi Feb 16 at 0:43
  • 1
    \$\begingroup\$ @Mochi Well, that's a pity. I'd consider reusing streams then, via something like github.com/microsoft/Microsoft.IO.RecyclableMemoryStream. Also, in the one answer below, be careful with the ToMemoryStream method, which looks like it will return a disposed stream. \$\endgroup\$ – Jerome Viveiros Feb 17 at 5:42
1
\$\begingroup\$

You might need to create a class handler that would handle the PdfDocument and MemoryStream, which will make things easier to handle and disposed as well. Then, you can just adjust the handler to your application requirements. here is an example :

public class PdfDocumentManager
{
    // Use Dictionary to keep both PdfDocument and MemoryStream paired.
    // Also, this means, you'll only save unique PdfDocuments and work on them. 
    // if you want to save a PdfDocument multiple times (say different copy of the original copy)
    // use List<KeyValuePair<PdfDocument, MemoryStream>> instead.
    private readonly Dictionary<PdfDocument, MemoryStream> _storage = new Dictionary<PdfDocument, MemoryStream>();

    public PdfDocumentManager() { }

    public void Add(PdfDocument document)
    {
        // don't forget to validate 

        _storage.Add(document, ToMemoryStream(document));
    }

    public void AddRange(IEnumerable<PdfDocument> documents)
    {
        foreach(var document in documents)
        {
            // validate each document before adding it.     
            Add(document);
        }
    }

    public void Remove(PdfDocument document)
    {
        // code to remove the saved stream.
        // choose MemoryStream or PdfDocument or Both if you want.
    }

    private MemoryStream ToMemoryStream(PdfDocument document)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            document.Save(stream);
            return stream;
        }
    }

    private PdfDocument ToPDFDocument(MemoryStream stream)
    {
        // code to convert back to memory stream.
        // just search the values to return its key.
        // if there is no match, then create a new PdfDocument along with MemoryStream, then return it back.
    }

    public IEnumerable<PdfDocument> GetAvailableDocuments()
    {
        return _storage.Keys;
    }

    public IEnumerable<MemoryStream> GetAvailableStreams()
    {
        return _storage.Values;
    }


}

simple add usage :

// initiate 
var pdfManager = new PdfDocumentManager();

// add single document 
pdfManager.Add(new PdfDocument(testParametersToDesignUniquePDF1));
pdfManager.Add(new PdfDocument(testParametersToDesignUniquePDF2));

// add multiple PdfDocument
var pdfDocumentList = new List<PdfDocument>
{
    new PdfDocument(testParametersToDesignUniquePDF1),
    new PdfDocument(testParametersToDesignUniquePDF2)
}

pdfManager.AddRange(pdfDocumentList);

The methods in this class are just for demonstration purpose only. You can bind the idea to your actual needs. With this handler, you can even make it a singleton class or make _storage static to have only one storage for the duration of the application ..etc. You can also move some related conditions inside the class to validate the documents (for instance, if you check if that document existed or not ..etc). This would give you also an easier way to handle the disposable objects.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.