markjprice / cs10dotnet6

Repository for the Packt Publishing book titled "C# 10 and .NET 6 - Modern Cross-Platform Development" by Mark J. Price
858 stars 375 forks source link

Chapter 09. Disposing of file resources #90

Open Dreamoochy opened 2 years ago

Dreamoochy commented 2 years ago

There's a code on pp. 383, 384:

try
{
  ...
  xmlFileStream = File.Create(xmlFile);
  xml = XmlWriter.Create(xmlFileStream, new XmlWriterSettings { Indent = true });
  ...
}
...
finally
{
  if (xml != null)
  {
    xml.Dispose();
    WriteLine("The XML writer's unmanaged resources have been disposed.");
    if (xmlFileStream != null)
    {
      xmlFileStream.Dispose();
      WriteLine("The file stream's unmanaged resources have been disposed.");
    }
  }
}

Why is xmlFileStream disposal nested in (xml != null) check? If I'm not mistaken, theoretically an exception can occur inside XmlWriter.Create. In that case xml will remain null, therefore xmlFileStream will not be disposed.

markjprice commented 2 years ago

Good point. I will move it outside the if statement. I have added an errata: https://github.com/markjprice/cs10dotnet6/blob/main/errata.md#page-384---disposing-of-file-resources

Dreamoochy commented 2 years ago

Some addition. In section "Simplifying disposal by using the using statement" (pp. 385,386) there are two code blocks:

using (FileStream file2 = File.OpenWrite(Path.Combine(path, "file2.txt")))
{
  using (StreamWriter writer2 = new StreamWriter(file2))
  {
    ...
  } // automatically calls Dispose if the object is not null
} // automatically calls Dispose if the object is not null

and simplified variant:

using FileStream file2 = File.OpenWrite(Path.Combine(path, "file2.txt"));
using StreamWriter writer2 = new(file2);
...

They are actually not equal. The difference is in a life-scope of the variables. In the first case file2 and writer2 are available only within their using { ... } blocks. They are automatically closed and disposed there. It would be impossible to use them farther, and they would not cause further errors.

In the second case both variables live within entire outer code block even if not used anymore. So it might be necessary to manually perform some operations. E.g. if someone wants to read "file2.txt" after writing, they will need to explicitly close the streams:

writer2.Close();
file2.Close();
File.ReadAllText(Path.Combine(path, "file2.txt"));

I'm sure you're aware of this behaviour, but it took some time for me to realize why seemingly proper code causes exceptions.

Dreamoochy commented 2 years ago

One more thing about using statement. In section "Compressing streams" (pp. 386+) there are code snippets like

Stream decompressor = new GZipStream(file,CompressionMode.Decompress);
using (decompressor)
{
...
}

In the documentation MS writes:

You can instantiate the resource object and then pass the variable to the using statement, but this isn't a best practice.