Bill Blogs in C#

Bill Wagner discusses C#, LINQ, and other items of interest

Question and Answer on Non-Deterministic Finalization
Always close files and streams

Question:

I read your article Manage C# Objects in Visual Studio magazine.

I'm having a problem using the XmlDataDocument class and was wondering if you think this is a situation where I should use IDisposable as described in your article.  The way the form works is the user will make changes and then click a "Save" button, or other events will trigger a save, which should persist changes to the XML file.

Thanks in advance for your help.

class frmMain : System.Windows.Forms.Form
{
  //code omitted

  string xmlSchema = (@"C:\Projects.xsd");
  string xmlFile = (@"C:\Projects.xml");
  XmlDataDocument xdd = new XmlDataDocument();

  public frmMain()
  {
    InitializeComponent();
    xdd.DataSet.ReadXmlSchema(xmlSchema);
    // load xml data
    XmlTextReader xrd = new XmlTextReader(xmlFile);
    xrd.MoveToContent();
    xdd.Load(xrd);
  }
 
 private void btnAddCfg_Click(object sender, System.EventArgs e)
 {                
   DataTable cfgTbl = xdd.DataSet.Tables["Configuration"];
   DataRow newCfg = cfgTbl.NewRow();
   newCfg["ConfigName"] = "NewConfiguration";
   cfgTbl.Rows.Add(newCfg);
   xdd.Save(xmlFile); // ç Error here: "The process cannot access the
   // file "C:\Projects.xml" because it is being used by
   // another process."
 }
}

Answer: Well, the answer is rather simple:  You never closed the file. When the constructor exits, the XmlTextReader is garbage. Eventually, the .NET Garbage Collector will reclaim that memory.  However, before the GC can reclaim the memory, it will call the finalizer for the XmlTextReader.  That will close the file.  If you wait long enough, the file does get closed.  Long enough is a non-deterministic amount of time.

To fix the problem, you need to explicitly close the file.  You also need to make sure that you close the file, even if an exception gets thrown in your constructor.  That means a using clause, or a finally clause. Your constructor should look like this:

public frmMain()
{
  InitializeComponent();
  xdd.DataSet.ReadXmlSchema(xmlSchema);
  // load xml data
  XmlTextReader xrd = null;
  try {
    xrd = new XmlTextReader(xmlFile);
    xrd.MoveToContent();
    xdd.Load(xrd);
  } finally
  {
    if ( xrd != null )
      xrd.Close( );
  }
}

You can’t put the XmlTextReader in a using clause, because it does not support IDisposable, even though it has a Close() method.

Changing it in this manner ensures that the file is always closed before the constructor exits.  That’s all there is to it.

I hope that helps.  Follow the link below for much more on disposing and closing objects (from my new book, Effective C#)



Implement the Standard Dispose Idiom
Not exactly what he's seeking, but close
The original aticle
The article referenced in the question
Published Wed, Dec 8 2004 4:47 PM by wwagner
Filed under: ,