Wrapping multiple IDisposables with using gives CA2202 [duplicate] - c#

This question already has answers here:
CA2202, how to solve this case
(12 answers)
Closed 8 years ago.
When I wrap IDisposable objects in usings like this I get the code analysis warning Code analysis error CA2202: Do not dispose objects multiple times, http://msdn.microsoft.com/en-us/library/ms182334.aspx
using (StringWriter textWriter = new StringWriter())
{
using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings))
{
serializer.Serialize(xmlWriter, value);
}
return textWriter.ToString();
}
This however doesn't return any errors
using (StringWriter textWriter = new StringWriter())
{
XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings);
serializer.Serialize(xmlWriter, value);
return textWriter.ToString();
}
Also when doing this the xmlWriter dispose will trigger CA2202
StringWriter textWriter = null;
XmlWriter xmlWriter = null;
try
{
textWriter = new StringWriter();
xmlWriter = XmlWriter.Create(textWriter, settings);
serializer.Serialize(xmlWriter, value);
return textWriter.ToString();
}
finally
{
if (textWriter != null)
textWriter.Dispose();
// The xmlWriter dispose will trigger CA2202
if (xmlWriter != null)
xmlWriter.Dispose();
}
Which pattern shall I use? It seems strange to not dispose disposable elements.

Ath the end of the inner using statement XmlWriter is being disposed. When this happens the textWriter element is also disposed because XmlWriter internally disposes object it wraps.
Since Dispose should be idempotent (repeatable calls does not have side effects) I wouldn't worry about this warning.

A simple google find this answer: https://stackoverflow.com/a/8096857/2027232
The problem isn't because of the nested usings. They're fine and generally recommended. The problem here is that XmlReader will dispose the TextReader if you pass an XmlReaderSettings with CloseInput == true, but the CA2202 rule isn't smart enough that your code won't go down that branch. Keep your nested usings, and suppress the CA2202 violation as a false positive. If you want to be extra careful, use an XmlReaderSettings with CloseInput set to false, but that is the default value, so it's not strictly necessary.
BTW, there are similar CA2202 problem scenarios for a variety of stream and reader types. Unfortunately, they're not all the same as this one, so the best case handling can differ depending on which type is cause the problem.

Related

Is it possible to manually line break on a specific attribute when using XmlWriter in Indent mode?

XmlWriter allows configuring indentation when using XmlWriter.Create and XmlWriterSettings.
In general, I want Indent = true and NewLineOnAttributes = false, except when writing xmlns namespace declarations at the beginning of the file, where I would like to have new lines between each xmlns namespace for readability.
Is it possible to force XmlWriter to do a line break after writing a specific attribute, and otherwise follow general indentation rules?
I tried using WriteWhitespace and WriteRaw with \n:
using System;
using System.Text;
using System.Xml;
namespace XmlWriterIndent
{
class Program
{
static void Main(string[] args)
{
var output = new StringBuilder();
using (var writer = XmlWriter.Create(output, new XmlWriterSettings { Indent = true, NewLineOnAttributes = true }))
{
writer.WriteStartDocument();
writer.WriteStartElement("Node");
writer.WriteAttributeString("key1", "value1");
writer.WriteAttributeString("key2", "value2");
writer.WriteAttributeString("xmlns", "n1", null, "scheme://mynamespace.com");
writer.WriteRaw("\n");
writer.WriteAttributeString("xmlns", "n2", null, "scheme://anothernamespace.com");
writer.WriteEndElement();
writer.WriteEndDocument();
}
var xml = output.ToString();
Console.WriteLine(xml);
}
}
}
Unfortunately this throws an exception saying the XML document would be invalid.
UPDATE: Actually, after checking more carefully, the exception is not in the WriteRaw method itself, but rather in the following WriteAttributeString call, as I am calling these methods in a loop for all namespaces.
It looks like WriteRaw moves the XmlWriter into the element content state somehow. Is it possible to use WriteRaw or somehow insert whitespace between attributes without changing the writer state?
UPDATE: Added self-contained example. Actually, it looks like in general namespace declarations are ignored even when using NewLineOnAttributes, i.e. all attributes have new lines except namespace declarations, which are somehow handled differently despite being regular attributes.
Unfortunately, I'm approaching the conclusion that the XmlWriter API is simply broken, as there is no way to do raw formatting of XML, since the WriteRaw forces a change in the writer state.
Looking into the actual source code at referencesource shows that the special write method WriteIndent is used to handle indentation inside XmlWriter. This method has special behavior that doesn't change the state, but there seems to be no way to access it or the underlying data stream, so it seems impossible to work around this without a full reimplementation of the entire XML writer stack:
https://referencesource.microsoft.com/#System.Xml/System/Xml/Core/XmlEncodedRawTextWriter.cs,1739
The XmlWriter API has no way to do raw formatting of XML attributes. WriteRaw would be the appropriate method to call, but the internal XmlWellFormedWriter returned by XmlWriter.Create always advances the writer state when this method is called, advancing the XML state machine to content. If we are in the middle of writing attributes, this finishes the start tag of the element and moves to content, which is not where we want to write our custom indentation.
Several internal XmlWriter classes implement more low-level WriteRaw methods, but there seems to be no way of accessing them, as XmlWriter.Create always wraps created writers with a XmlWellFormedWriter instance before returning.
Therefore, the only way to workaround the issue is to define and instantiate a custom XmlWriter class which controls both the underlying stream and the base XmlWriter. That way we can bypass the XmlWriter API and write directly into the stream when we need to do our custom indentation.
There are a couple of limitations with the current solution:
XmlWriter implementations do not write directly to the stream, and instead keep their own internal buffers for efficiency. That means that whenever we want to bypass the XmlWriter we need to call Flush to make sure that our stream is in the right position;
In order for indentation to make sense, we need to keep track of the correct indent level. To do this for the whole document would be possible, but tedious. For simplicity, this solution only formats the top level xmlns declarations;
For completeness, we need to deal with both Stream and TextWriter as possible output types.
Finally, the XmlWriter abstract class has dozens of methods to override which we don't care about, but that need to be bridged to the underlying writer. For conciseness, I have omitted all but the relevant overrides:
class XmlnsIndentedWriter : XmlWriter
{
bool isRootElement;
int indentLevel = -1;
readonly Stream stream;
readonly TextWriter textWriter;
readonly XmlWriter writer;
private XmlnsIndentedWriter(Stream output, XmlWriter baseWriter)
{
stream = output;
writer = baseWriter;
}
private XmlnsIndentedWriter(TextWriter output, XmlWriter baseWriter)
{
textWriter = output;
writer = baseWriter;
}
public static new XmlWriter Create(StringBuilder output, XmlWriterSettings settings)
{
var writer = XmlWriter.Create(output, settings);
return new XmlnsIndentedWriter(new StringWriter(output, CultureInfo.InvariantCulture), writer);
}
public static new XmlWriter Create(Stream stream, XmlWriterSettings settings)
{
var writer = XmlWriter.Create(stream, settings);
return new XmlnsIndentedWriter(stream, writer);
}
// snip: override all methods in the XmlWriter class
private void WriteRawText(string text)
{
writer.Flush();
if (stream != null)
{
// example only, this could be optimized with buffers, etc.
var buf = writer.Settings.Encoding.GetBytes(text);
stream.Write(buf, 0, buf.Length);
}
else if (textWriter != null)
{
textWriter.Write(text);
}
}
public override void WriteStartDocument()
{
isRootElement = true;
writer.WriteStartDocument();
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
if (isRootElement)
{
if (indentLevel < 0)
{
// initialize the indent level;
// length of local name + any control characters / prefixes, etc.
indentLevel = localName.Length + 1;
}
else
{
// do not track indent for the whole document;
// when second element starts, we are done
isRootElement = false;
indentLevel = -1;
}
}
writer.WriteStartElement(prefix, localName, ns);
}
public override void WriteEndAttribute()
{
writer.WriteEndAttribute();
if (indentLevel >= 0)
{
RawText(Environment.NewLine + new string(' ', indentLevel));
}
}
}
There is a choice to add the indentation either before each attribute, or after.
Here I have opted for the latter, as it seems to be the only option if you want to also indent the default xmlns declaration. This declaration is written out after the writer state moves to content, and there seems to be no way of intercepting it otherwise.

Serialize(TextWriter, Object) vs Serialize(XmlWriter, Object)

The XmlSerializer.Serialize Method has overloads that accept TextWriter and XmlWriter.
My question is what are the practical differences between these two overloads in the follow examples? (list is a List<MyObjectModel>)
Example 1 (with TextWriter):
XmlSerializer serializer = new XmlSerializer(typeof(MyObjectModel));
using (TextWriter writer = new StreamWriter(savePath))
{
serializer.Serialize(writer, list);
}
Example 2 (with XmlWriter):
XmlSerializer serializer = new XmlSerializer(typeof(MyObjectModel));
using (XmlWriter writer = XmlWriter.Create(savePath))
{
serializer.Serialize(writer, list);
}
So far I've noticed that:
1) TextWriter seems to automatically perform indenting for you.
2) The default encoding for both is UTF-8.
I found the difference in the source code reference:
public void Serialize(TextWriter textWriter, object o, XmlSerializerNamespaces namespaces) {
XmlTextWriter xmlWriter = new XmlTextWriter(textWriter);
xmlWriter.Formatting = Formatting.Indented;
xmlWriter.Indentation = 2;
Serialize(xmlWriter, o, namespaces);
}
In short, the TextWriter overload uses a XmlTextWriter underneath the hood and sets the formatting for you.

How to control the encoding while serializing and deserializing?

I'm using serialization to a string as follows.
public static string Stringify(this Process self)
{
XmlSerializer serializer = new XmlSerializer(typeof(Process));
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, self,);
return writer.ToString();
}
}
Then, I deserialize using this code. Please note that it's not an actual stringification from above that's used. In our business logic, it makes more sense to serialize a path, hence reading in from said path and creating an object based on the read data.
public static Process Processify(this string self)
{
XmlSerializer serializer = new XmlSerializer(typeof(Process));
using (XmlReader reader = XmlReader.Create(self))
return serializer.Deserialize(reader) as Process;
}
}
This works as supposed to except for a small issue with encoding. The string XML that's produced, contains the addition encoding="utf-16" as an attribute on the base tag (the one that's about XML version, not the actual data).
When I read in, I get an exception because of mismatching encodings. As far I could see, there's no way to specify the encoding for serialization nor deserialization in any of the objects I'm using.
How can I do that?
For now, I'm using a very brute work-around by simply cutting of the excessive junk like so. It's Q&D and I want to remove it.
public static string Stringify(this Process self)
{
XmlSerializer serializer = new XmlSerializer(typeof(Process));
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, self,);
return writer.ToString().Replace(" encoding=\"utf-16\"", "");
}
}

Advise on code analysis, disposing more than once

I'm just looking for some advice from someone more experienced than me really (wont be hard).
The following code...
XmlSerializer serializer = new XmlSerializer(typeof(Installation));
using (var sw = new StringWriter()) {
using (var xw = XmlWriter.Create(sw)) {
serializer.Serialize(xw, Installation);
}
xmlResult = sw.ToString();
}
has the following report in the code analysis...
CA2202 Do not dispose objects multiple times Object 'sw' can be
disposed more than once in method
'Views_Commissioning_installationSubsidyForm.SaveInstall(string)'. To
avoid generating a System.ObjectDisposedException you should not call
Dispose more than one time on an object.: Lines:
766 nc1_DealerPortal installationSubsidyForm.aspx.cs 766
Can anyone explain how I'm disposing of 'sw' more than once? What am I doing wrong here?
StringWriter will be disposed by the XmlWriter, so by having 2 using statements it will get disposed twice change you code as below:
XmlSerializer serializer = new XmlSerializer(typeof(Installation));
var sw = new StringWriter())
using (var xw = XmlWriter.Create(sw))
{
serializer.Serialize(xw, Installation);
xmlResult = sw.ToString();
}

streaming XML serialization in .net

I'm trying to serialize a very large IEnumerable<MyObject> using an XmlSerializer without keeping all the objects in memory.
The IEnumerable<MyObject> is actually lazy..
I'm looking for a streaming solution that will:
Take an object from the IEnumerable<MyObject>
Serialize it to the underlying stream using the standard serialization (I don't want to handcraft the XML here!)
Discard the in memory data and move to the next
I'm trying with this code:
using (var writer = new StreamWriter(filePath))
{
var xmlSerializer = new XmlSerializer(typeof(MyObject));
foreach (var myObject in myObjectsIEnumerable)
{
xmlSerializer.Serialize(writer, myObject);
}
}
but I'm getting multiple XML headers and I cannot specify a root tag <MyObjects> so my XML is invalid.
Any idea?
Thanks
The XmlWriter class is a fast streaming API for XML generation. It is rather low-level, MSDN has an article on instantiating a validating XmlWriter using XmlWriter.Create().
Edit: link fixed. Here is sample code from the article:
async Task TestWriter(Stream stream)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Async = true;
using (XmlWriter writer = XmlWriter.Create(stream, settings)) {
await writer.WriteStartElementAsync("pf", "root", "http://ns");
await writer.WriteStartElementAsync(null, "sub", null);
await writer.WriteAttributeStringAsync(null, "att", null, "val");
await writer.WriteStringAsync("text");
await writer.WriteEndElementAsync();
await writer.WriteCommentAsync("cValue");
await writer.WriteCDataAsync("cdata value");
await writer.WriteEndElementAsync();
await writer.FlushAsync();
}
}
Here's what I use:
using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;
using System.Text;
using System.IO;
namespace Utils
{
public class XMLSerializer
{
public static Byte[] StringToUTF8ByteArray(String xmlString)
{
return new UTF8Encoding().GetBytes(xmlString);
}
public static String SerializeToXML<T>(T objectToSerialize)
{
StringBuilder sb = new StringBuilder();
XmlWriterSettings settings =
new XmlWriterSettings {Encoding = Encoding.UTF8, Indent = true};
using (XmlWriter xmlWriter = XmlWriter.Create(sb, settings))
{
if (xmlWriter != null)
{
new XmlSerializer(typeof(T)).Serialize(xmlWriter, objectToSerialize);
}
}
return sb.ToString();
}
public static void DeserializeFromXML<T>(string xmlString, out T deserializedObject) where T : class
{
XmlSerializer xs = new XmlSerializer(typeof (T));
using (MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(xmlString)))
{
deserializedObject = xs.Deserialize(memoryStream) as T;
}
}
}
}
Then just call:
string xml = Utils.SerializeToXML(myObjectsIEnumerable);
I haven't tried it with, for example, an IEnumerable that fetches objects one at a time remotely, or any other weird use cases, but it works perfectly for List<T> and other collections that are in memory.
EDIT: Based on your comments in response to this, you could use XmlDocument.LoadXml to load the resulting XML string into an XmlDocument, save the first one to a file, and use that as your master XML file. For each item in the IEnumerable, use LoadXml again to create a new in-memory XmlDocument, grab the nodes you want, append them to the master document, and save it again, getting rid of the new one.
After you're finished, there may be a way to wrap all of the nodes in your root tag. You could also use XSL and XslCompiledTransform to write another XML file with the objects properly wrapped in the root tag.
You can do this by implementing the IXmlSerializable interface on the large class. The implementation of the WriteXml method can write the start tag, then simply loop over the IEnumerable<MyObject> and serialize each MyObject to the same XmlWriter, one at a time.
In this implementation, there won't be any in-memory data to get rid of (past what the garbage collector will collect).

Categories