6

I am trying to ensure my coding follows correct disposal of objects so I am enforcing these rules as errors. But I am having trouble with this section of code

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

class MyClass
{  
    public String ToXml()
    {
        var objSerializer = 
            new DataContractSerializer(GetType());
        var objStream = new MemoryStream();
        StreamReader objReader;

        String strResult;
        try
        {
            // Serialize the object
            objSerializer.WriteObject(objStream, this);

            // Move to start of stream to read out contents
            objStream.Seek(0, SeekOrigin.Begin);

            objReader = new StreamReader(objStream);

            try
            {
                // Read Contents into a string
                strResult = objReader.ReadToEnd();
            }
            finally
            {
                objReader.Dispose();
            }
        }
        finally
        {
            if (objStream != null)
            {
                // objStream.Dispose();
            }
        }

        return strResult;
    }
}

If I comment out objStream.Dispose() I get CA2000 as I am not disposing the object but if I remove the comment it then says I am disposing more than once.

What else is disposing the object? or am I just doing this wrong when dealing with multiple streams?

Steven
  • 166,672
  • 24
  • 332
  • 435
Dreamwalker
  • 3,032
  • 4
  • 30
  • 60

3 Answers3

8

This scenario has been annoying me as well now. Every couple of years I decide to refresh myself of the code analysis "rules" by running fxcop or the now built-in code analysis in Visual Studio.

I originally wrote this code, thinking I was being a good citizen for properly using usings to dispose:

using (MemoryStream msDecrypt = new MemoryStream())
{
    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
    {
        csDecrypt.Write(eop.m_Ciphertext, 0, eop.m_Ciphertext.Length);
    }

    decrypted = msDecrypt.ToArray();
}

This block of code results in a CA2202 "Do not dispose objects multiple times" The great irony about this rule, is that it's not really about a problem with your code, as much as it is protecting you about a problem in others code. Microsoft has always had a decent amount of documentation about how the Dispose pattern (http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx) should be implmented. However, by looking at the details of this code analysis rule (http://msdn.microsoft.com/en-us/library/ms182334.aspx) it reveals the purpose of this rule

"A correctly implemented Dispose method can be called multiple times without throwing an exception. However, this is not guaranteed and to avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object."

In short, this rule is all about protecting yourself from people who don't follow the rules.

Naturally I modified the code to look like this:

MemoryStream msDecrypt = new MemoryStream()    
//using (MemoryStream msDecrypt = new MemoryStream())
//{
    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
    {
        csDecrypt.Write(eop.m_Ciphertext, 0, eop.m_Ciphertext.Length);
    }

    decrypted = msDecrypt.ToArray();
//}

Now everybody on this stack overflow post is painfully aware of the new problem, our friend CA2000 "Dispose objects before losing scope" ... so at this point I just face palmed for a minute. Did a few Google searches, and found this post. That's when it dawned on me, to pass for both CA rules, you need to ensure everything is disposed once and only once for all code branches. So I set out to do this, which isn't a hard problem once you realize this is what you need to do.

Naturally, the code evolved to this:

MemoryStream msDecrypt = null;
CryptoStream csDecrypt = null;

try
{    
    msDecrypt = new MemoryStream();
    csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write);

    csDecrypt.Write(eop.m_Ciphertext, 0, eop.m_Ciphertext.Length);
    csDecrypt.FlushFinalBlock();
    decrypted = msDecrypt.ToArray();
}
finally
{
    if (csDecrypt != null)
    {
        csDecrypt.Dispose();
    }
    else if (msDecrypt != null)
    {
        msDecrypt.Dispose();
    }

}

Finally, I had code that didn't result in a CA2000 or CA2202. The moral of the story is that the USING statement, is a lot less valuable than it was in the past now that code analysis rules have evolved in this way.

There are a few different ways you can write the code to make this work, I just chose a way that doesn't mix explicit calls to dispose with using statements, because I believe this to be simpler to read as well as structured in a way that would prevent someone from just going and wrapping another using around it, resulting in the originally issue unknowingly.

Mark At Ramp51
  • 5,343
  • 1
  • 25
  • 30
  • 2
    Whilst technically true when looking at this generically, real world usage proves no problem at all with the neater "using" construct (i.e. when disposed twice on success). So for readability I would properly suppress this error ONLY when I know that the class handles it properly. I think that is the point of the MS code analysis warning (to make us think about it). The best solution would be for the code analysis rules engine to discover this itself; to give us some way to declare this (e.g. code contracts). I voted your answer up anyway as it is a good explanation and must be considered. – Tony Wall Oct 22 '15 at 08:21
1

If you dispose the StreamReader, you are also disposing the underlying stream.

If you comment out objStream.Dispose() then you run into the chance of something throwing an exception before you even get to the nested try block - which will result in your stream not getting disposed.

There's a nice explanation here: Does disposing streamreader close the stream?

Community
  • 1
  • 1
Mel
  • 3,058
  • 4
  • 26
  • 40
  • Yes I understand all that if you try the code you will notice you are disposing of objStream twice but in my actualy code I am not. Is it a case of StreamReader.Dispose disposing the passed in stream as well maybe? – Dreamwalker Apr 13 '12 at 11:22
  • 1
    IMO this is a strange design quirk of the `StreamReader` and it bugs me often, because sometimes you don't want to close the supplied stream. – Steven Apr 13 '12 at 11:23
  • @Zonder Yes, that's exactly what I'm saying. if you dispose a reader (or writer) then you are also disposing the underlying stream. so in your code (uncommeting the last dispose), it still ends up getting disposed twice. – Mel Apr 13 '12 at 11:29
  • @Steven I agree. and its hard to design around it. I usually end up disposing both stream and reader, which feels kind of redundant. imagine if I put a writer into the mix. – Mel Apr 13 '12 at 11:31
  • hmm yes ok so this is quite awkward then to have both these code analysis rules as errors. The only way around it is to make CA2202 a warning and hope the programmer remembers to catch ObjectDisposedException. Maybe IDisposed should have a IsDisposed property but then you have issues with async disposal. – Dreamwalker Apr 13 '12 at 12:10
0

This is not the answer, but you might find this code more readable:

public String ToXml()
{
    var objSerializer =
        new DataContractSerializer(GetType());

    using (var objStream = new MemoryStream())
    {
        //  Serialize the object
        objSerializer.WriteObject(objStream, this);

        // Move to start of stream to read 
        // out contents
        objStream.Seek(0, SeekOrigin.Begin);

        using (var objReader =
            new StreamReader(objStream))
        {
            // Read Contents into a string
            retirm objReader.ReadToEnd();
        }
    }
}
Steven
  • 166,672
  • 24
  • 332
  • 435
  • This is exactly what should NOT be done according to http://msdn.microsoft.com/en-us/library/ms182334 because Dispose() on the objStream object might / will be called twice. – mattanja Jul 16 '12 at 10:12
  • On the other hand: Jon Skeet says it's ok - so it is by law. http://stackoverflow.com/a/1065196/40853 – mattanja Jul 16 '12 at 10:15
  • 2
    Calling Dispose multiple times should never be a problem (except one of performance perhaps), since the `IDisposable` contract states that the implementation must be able to handle this. The MSDN documentation already notes "A correctly implemented Dispose method can be called multiple times without throwing an exception.". In this sense the CA2202 rule is very odd, or at least it should have been grouped under the category Microsoft.Performance, instead of Microsoft.Usage. – Steven Jul 16 '12 at 15:27