Appending XML files and confusing disposables
There are several ways to create XML files using the .NET FCL, but none of the standard methods allows simple extension of an existing file, such as a logfile, without needing to put the entire file into memory first (because that's what would happend if I use the DOM to add nodes somewhere near the end of a tree).
Now XmlTextWriter is the ideal candidate for logging since from a user's perspective it's basically a standard
filestream writer that has some standard formatting built-in. But unfortunately, everytime I Flush(), it wants the
things I've written to be well-formed. Actually this is an advantage but not for what I'm trying to do here: if I'm extending a file, I want to rewrite the closing tag without having written the opening tag (since that's at the top of the file and was potentially created weeks ago).
So here's a quick solution I came up with. Basically what I do is derive from this class and then use its
protected XmlTextWriter to write logging stuff. An implementation of this same idea (it's not a copy/paste because I don't have that code handy) has been running for a couple of weeks and it works well:
internal class Xmlog : IDisposable
{
private bool first;
private bool disposed = false;
protected XmlTextWriter xw;
public Xmlog(string logfile)
{
if(!File.Exists(logfile))
{
xw = new XmlTextWriter(logfile, System.Text.Encoding.UTF8);
xw.WriteStartDocument(true);
xw.WriteStartElement("log");
first = true;
}
else
{
FileStream fs = File.OpenWrite(logfile);
fs.Seek(-6, SeekOrigin.End); // length of is 6 characters
xw = new XmlTextWriter(fs, System.Text.Encoding.UTF8);
first = false;
}
xw.WriteStartElement("session");
xw.WriteAttributeString("datetime", DateTime.Now.ToString());
}
public void Dispose()
{
if(!disposed)
{
xw.WriteEndElement();
if(first)
xw.WriteEndElement();
else
{
xw.Flush(); // flush here or XmlTextWriter will suspect something :)
byte[] endtag = System.Text.Encoding.UTF8.GetBytes("");
xw.BaseStream.Write(endtag, 0, endtag.Length);
}
xw.Close();
disposed = true;
}
}
}
When I was coding up this class I stumbled upon something that keeps confusing me: IDisposable. In the MSDN
documentation is an example where its use is illustrated in combination with a finalizer, demonstrating that you can clean whatever you want when the Dispose()-method is explicitly called, but when you're in the finalizer, you need to only clean unmanaged resources because during finalization the existence of those managed resources is not guaranteed anymore. But then I wonder: in the example above, if someone forgets to call the Dispose()-method, I can't use the finalizer to ensure he ends up with a valid XML file. At least, that's what I make of the way it's explained.
I ended up looking through the SLAR to see if there's any further mention of it there, but it basically contains the same information as in the MSDN documentation (except for some interesting background on how the interface came to be along with an annotation by
Jeffrey Richter where he predicts my confusion :))