Will a using clause close this stream?

48,654

Solution 1

Yes, StreamReader.Dispose closes the underlying stream (for all public ways of creating one). However, there's a nicer alternative:

using (TextReader reader = File.OpenText("file.txt"))
{
}

This has the added benefit that it opens the underlying stream with a hint to Windows that you'll be accessing it sequentially.

Here's a test app which shows the first version working for me. I'm not trying to say that's proof of anything in particular - but I'd love to know how well it works for you.

using System;
using System.IO;

class Program
{
    public static void Main(string[] args)
    {
        for (int i=0; i < 1000; i++)
        {
            using(StreamReader sr = new StreamReader
                  (File.Open("somefile.txt", FileMode.Open)))
            {
                Console.WriteLine(sr.ReadLine());
            }
            File.Move("somefile.txt", "somefile.bak");
            File.Move("somefile.bak", "somefile.txt");
        }
    }
}

If that works, it suggests that it's something to do with what you do while reading...

And now here's a shortened version of your edited question code - which again works fine for me, even on a network share. Note that I've changed FileMode.Create to FileMode.CreateNew - as otherwise there could still have been an app with a handle on the old file, potentially. Does this work for you?

using System;
using System.IO;

public class Test
{    
    static void Main()
    {
        StreamWriter creditsFile = new StreamWriter(File.Open("test.txt", 
                                          FileMode.CreateNew));

        creditsFile.WriteLine("code\\inc");

        creditsFile.Close();
        creditsFile.Dispose();

        File.Move("test.txt", "test2.txt");
    }
}

Solution 2

Note - your using blocks do not need to be nested in their own blocks - they can be sequential, as in:

using(FileStream fs = File.Open("somefile.txt", FileMode.Open))
using(StreamReader sr = new StreamReader(fs))
{
    //read file
}

The order of disposal in this case is still the same as the nested blocks (ie, the StreamReader will still dispose before the FileStream in this case).

Solution 3

I would try to use FileInfo.Open() and FileInfo.MoveTo() instead of File.Open() and File.Move(). You could also try to use FileInfo.OpenText(). But these are just suggestions.

Share:
48,654
scottm
Author by

scottm

Software engineer with skills in highly available systems and software solutions.

Updated on March 18, 2020

Comments

  • scottm
    scottm about 4 years

    I've apparently worked myself into a bad coding habit. Here is an example of the code I've been writing:

    using(StreamReader sr = new StreamReader(File.Open("somefile.txt", FileMode.Open)))
    {
        //read file
    }
    File.Move("somefile.txt", "somefile.bak"); //can't move, get exception that I the file is open
    

    I thought that because the using clause explicitly called Close() and Dispose() on the StreamReader that the FileStream would be closed as well.

    The only way I could fix the problem I was having was by changing the above block to this:

    using(FileStream fs = File.Open("somefile.txt", FileMode.Open))
    {
      using(StreamReader sr = new StreamReader(fs))
      {
        //read file
      }
    }
    
    File.Move("somefile.txt", "somefile.bak"); // can move file with no errors
    

    Should closing the StreamReader by disposing in the first block also close the underlying FileStream? Or, was I mistaken?

    Edit

    I decided to post the actual offending block of code, to see if we can get to the bottom of this. I am just curious now.

    I thought I had a problem in the using clause, so I expanded everything out, and it still can't copy, every time. I create the file in this method call, so I don't think anything else has a handle open on the file. I've also verified that the strings returned from the Path.Combine calls are correct.

    private static void GenerateFiles(List<Credit> credits)
    {
        Account i;
        string creditFile = Path.Combine(Settings.CreditLocalPath, DateTime.Now.ToString("MMddyy-hhmmss") + ".credits");
    
        StreamWriter creditsFile = new StreamWriter(File.Open(creditFile, FileMode.Create));
    
        creditsFile.WriteLine("code\inc");
    
        foreach (Credit c in credits)
        {
            if (DataAccessLayer.AccountExists(i))
            {
                string tpsAuth = DataAccessLayer.GetAuthCode(i.Pin);
                creditsFile.WriteLine(String.Format("{0}{1}\t{2:0.00}", i.AuthCode, i.Pin, c.CreditAmount));
            }
            else
            {
                c.Error = true;
                c.ErrorMessage = "NO ACCOUNT";
            }
    
            DataAccessLayer.AddCredit(c);
    
        }
    
        creditsFile.Close();
        creditsFile.Dispose();
    
        string dest =  Path.Combine(Settings.CreditArchivePath, Path.GetFileName(creditFile));
        File.Move(creditFile,dest);
        //File.Delete(errorFile);
    }