Reputation: 13
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
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
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
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