Thursday, May 13, 2004

Beware File.OpenWrite

I was working on putting some finishing touches on FwDocGen yesterday, when I came across a subtle bug that took the help of Mike Woodring to figure out. FwDocGen is a fairly straightforward wrapper around an XSLT that takes in XML documentation and produces FlexWiki pages that document the classes, namespaces, etc. in your project. The idea is to produce the equivalent of MSDN docs for your class library, but on a wiki instead.


(Side note: FwDocGen is now released in a fairly usable form.)


Anyway, what I was seeing was that the last part of the documentation for a class would show up twice. That is, the docs would list all the methods, properties, etc., and then part of whatever came last would show up again: a little documentation fragment that was completely out of place.


Naturally my first instinct was that I had screwed up the transform and thrown in one too many <xsl:apply-templates /> calls. But everything checked out there, so I was stumped. I saw Mike was online, and since he's preternaturally smart, I pinged him. Within a few minutes, he had said the magic phrase that turned the light bulb on in my head. “Looks like a truncation problem,” he said. And it was.


The fundamental issue was that I was doing something like the following:


FileStream fs = File.OpenWrite(path);


which is a very handy convenience method for opening a file for write access. Unfortunately, as it says right in the docs, this is the equivalent of saying


FileStream fs = FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);


which is most definitely not the same thing. Why not? Because OpenOrCreate, when accessing an existing file, does not nuke out the contents that are already there. So if you have a file whose contents are


ABCDEFG


and you do a File.OpenWrite() on it, and then write “123” into it, you get


123DEFG


instead of just “123”. In other words, the file is not truncated on open. Which is probably not what you want. It certainly explained my problem, though. I'd changed the transform so some of the files were shorter, and when I re-ran the program, it wrote the shorter version over the top of the longer version, leaving the extra garbage at the end.


The fix was dead easy. I just swapped out File.OpenWrite for


FileStream fs = FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);


which works perfectly well. Somewhat confusingly (although it's well-documented), Create still works if the file is already there. But it truncates the file. Thanks Mike!

9 comments:

  1. What you wanted to use was:

    FileStream fs = File.Create(path);

    instead.

    ReplyDelete
  2. Which is equivalent to

    FileStream fs = FileStream(String, FileMode.Create, FileAccess.ReadWrite, FileShare.None);

    which, I agree, also would have worked, but is slightly different, as it opens the file for reading and for writing. Probably not an issue in most situations.

    Certainly, it's a nice shortcut API to know.

    ReplyDelete
  3. Ack...that's what I get for skimming when I was checking what it was equivalent to. I just remembered that's what I changed my .OpenWrite calls to to correct a similar problem on a project last summer.

    I'll have to file away the reading and writing bit. That's something good to know.

    ReplyDelete
  4. I doubt you'd see many situations where it's an issue, especially with Share.None in the flags. File.Create is a nice, convenient way to open a file for truncated writing in most situations...I'm just pedantic by nature. :)

    ReplyDelete
  5. just forgot the NEW on fs = FileStream ...



    :-)

    ReplyDelete
  6. Right! Thanks. Hopefully it was obvious to everyone else as well.

    ReplyDelete
  7. Thanks, that one got me as well....

    ReplyDelete
  8. Thanks for the tip. It would have got me, I had my code already but I did a search and got this doc, so I could fix the problem before happening. Thanks man!

    ReplyDelete
  9. Thanks very much for the tip! This solved a baffling problem I was having where trying to obtain a FileStream on a file I had just opened returned a "this file is already in use" exception.

    ReplyDelete