user3076406
user3076406

Reputation: 13

Need to close filestreams that were opened in another context of the same app

I have a main window on which a user clicks a button to process a selected input file. The block of code triggered by the button opens the input file, an output file, and a log file. That same block of code reads lines of the input file one at a time, does some operations on the fields in each line, then writes a corresponding line to the output file. Error messages are written to the log file. Processing a single file can take minutes.

I want to be able to gracefully close the 3 files if the user gets impatient and closes the main window using the red "X" button. I have an event handler that captures the clicking of the "X", but I am unable to reference the 3 filestreams I want to close, because they were created outside of the event handler's context. How do I pass to the handler the 3 filestreams?

namespace less
{
   public partial class MainWindow : Window
   {
        ...

        private static string filename;

        ...

      public MainWindow()
      {

            InitializeComponent();

            // string filename is chosen in this block of code

      }

    private void convertData_Click(object sender, RoutedEventArgs e)
        {
            // This is invoked when user clicks the "Process Data" button

            System.IO.StreamReader inputFile = new System.IO.StreamReader(filename);
            System.IO.StreamWriter outputFile = new System.IO.StreamWriter(filename + ".less.csv");
            System.IO.StreamWriter logFile = new System.IO.StreamWriter(filename + ".log");

                     ....

               <process all the data>

                     ....

           inputFile.Close();  // <- this works
           outputFile.Close();  // <- this works
           logFile.Close();  // <- this works

           this.Close();
         }

    private void MainWindow_Closed(object sender, EventArgs e)
        {

           // This is the event handler invoked when the red "X" is clicked.

           inputFile.Close(); // <- this (and following) do not work
           outputFile.Close();
           logFile.Close();
        }

   } // closes public partial class MainWindow : Window

} // closes namespace

Upvotes: 1

Views: 1672

Answers (3)

Chris Williams
Chris Williams

Reputation: 170

You are creating the FileStream objects within the Event Handler, so they only exist for the scope of that event handler, then they are marked for collection by the Garbage Collector. You've closed the streams at the end of the handler, so even if you tried to access them outside the method, they would be closed, so this comes down to a couple of key points that are both related to the IDisposable pattern in C#.

Filestreams implement the IDisposable interface. This means they are guaranteed to have a Dispose method. Dispose is a method which clears up any resources that the object needs to. For example, in the case of FileStreams, the Dispose method closes the stream. The using keyword can execute on anything that implements IDisposable. The keyword is used as:

using(StreamReader reader = new StreamReader(filename))
{
    // Reading the stream
}

Which you can replace in your head with:

try
{
    StreamReader reader = new StreamReader(filename);
   // Reading the stream
}
finally
{
    if(reader != null)
    {
        reader.Dispose();
    }
}

What this means in effect is that you no longer need to worry about closing the streams, because the Dispose method will be called, even if an exception is thrown during the read. Handy, no?

If you're adamant about closing the streams outside of the event handler, you need to move the stream definitions out to the class scope. This doesn't save you from the fun of IDisposable though, because whenever you have a class that contains IDisposable objects, that class then needs to implement IDisposable. And the Dispose method of that class should call Dispose on the Disposable member objects. IDisposable is infectious and spreads upwards through the stack until you guarantee disposing. If you only use the objects in the scope of that method, you'll save yourself a lot of Fun.

Upvotes: 0

Steve
Steve

Reputation: 216323

Of course it doesnt' work. The three variables are local to the method convertData_Click and thus cannot be referenced outside that method. If you really want to do that you need to move the three variables at the same level of the variable filename. They become global class level variables and can be referenced in any method of that class.

Said that, I really suggest to avoid that. FileStream should be declared, used, closed and disposed when they are needed and not kept open for the lifetime of your form

namespace less
{
   public partial class MainWindow : Window
   {
        ...

       // Now these are global class level variables...
       // But again, don't do that.....
       private StreamReader inputFile;
       private StreamWriter outputFile;
       private StreamWriter logFile;

       private static string filename;

        ...
   }

A better approach is

private void convertData_Click(object sender, RoutedEventArgs e)
{
   using(StreamReader inputFile = new System.IO.StreamReader(filename))
   using(StreamWriter outputFile = new System.IO.StreamWriter(filename + ".less.csv"))
   using(StreamWriter logFile = new System.IO.StreamWriter(filename + ".log"))
   {
        ....
        <process all the data>
        ....
   }
}

The using statement is preferred because it closes and destroys the variables used in the defining lines also in case of exceptions while you process the data

Upvotes: 3

Eugen Rieck
Eugen Rieck

Reputation: 65314

Its just a scoping mixup:

First step: put this into the class scope

System.IO.StreamReader inputFile;
System.IO.StreamWriter outputFile;
System.IO.StreamWriter logFile;

Second step: replace

System.IO.StreamReader inputFile = new System.IO.StreamReader(filename);
System.IO.StreamWriter outputFile = new System.IO.StreamWriter(filename + ".less.csv");
System.IO.StreamWriter logFile = new System.IO.StreamWriter(filename + ".log");

with

inputFile = new System.IO.StreamReader(filename);
outputFile = new System.IO.StreamWriter(filename + ".less.csv");
logFile = new System.IO.StreamWriter(filename + ".log");

Third step: make sure, the files are really open! Either use a try/catch construct or use

if (inputFile != null) inputFile.Dispose();

and friends

Upvotes: 0

Related Questions