2

I've been struggling with this for a few hours, and I post this question with reluctance.

I am assigned to make a program that reads a name, saves it to a text box and writes the name and number to a text file upon the forms closing. Upon the form's loading, it should read the name and number from 2 text files and display that info. The number is automatically generated and the next customer number is read from a text file.

However, I am having a hard time with the streamwriter: I am particularly getting an error when I try to write to outFile2: NullReferenceError for outFile2.

Basically outFile2 doesn't open the file or append the output file. How can I get this program to work without trying to figure out why every method of read/write buffer crashes on me?

Private inFile1, inFile2 As IO.StreamReader
Private outFile1, outFile2 As IO.StreamWriter

Private customerNum As Integer = 1
Private customerName As String

Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles Me.Load
    Dim tempStr As String

    If IO.File.Exists("CustomerNumbers.txt") = False Then
        outFile1 = IO.File.CreateText("CustomerNumbers.txt")
        inFile1 = IO.File.OpenText("CustomerNumbers.txt")
    Else
        inFile1 = IO.File.OpenText("CustomerNumbers.txt")
        outFile1 = IO.File.AppendText("CustomerNumbers.txt")
    End If

    If IO.File.Exists("CustomerNames.txt") = False Then
        outFile2 = IO.File.CreateText("CustomerNames.txt")
        inFile2 = IO.File.OpenText("CustomerNames.txt")
    Else
        inFile2 = IO.File.OpenText("CustomerNames.txt")
        outFile2 = IO.File.AppendText("CustomerNames.txt")
    End If


    If inFile1.Read = Nothing Then
        customerNum = 1
        txtList.Text = customerNum
    End If

    Do While inFile1.Peek() <> -1
        tempStr = inFile1.ReadLine
        customerNum = tempStr
    Loop

    Do While inFile2.Peek() <> -1
        customerName = inFile2.ReadLine().ToString
        txtList.Text += customerName & vbCrLf
    Loop

    lblNumber.Text = customerNum

    inFile1.Close()
    inFile2.Close()


End Sub

Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnExit.Click

    outFile1.WriteLine(customerNum)

    outFile1.Close()
    outFile2.Close()
    Me.Close()
End Sub


Private Sub btnSaveCustomer_Click(sender As Object, e As EventArgs) Handles btnSaveCustomer.Click

    If (txtCustomerName.Text <> Nothing) Then
        txtList.Text += customerNum & "  " & txtCustomerName.Text & vbCrLf
        //error thrown here usually
        outFile2.WriteLine(customerNum.ToString & " " & txtCustomerName.Text.ToString)
        customerNum = customerNum + 1
        lblNumber.Text = customerNum
        txtCustomerName.Clear()
        txtCustomerName.Focus()
    End If

End Sub

Paste from Comment:

    If IO.File.Exists("CustomerNames.txt") = False Then
        Dim outFile1, outFile2 As IO.StreamWriter
        outFile2 = IO.File.CreateText("CustomerNames.txt")
        Using outFile2 = IO.File.AppendText("CustomerNames.txt")
            outFile2.WriteLine(customerNum.ToString & " " & txtCustomerName.Text.ToString)
        End Using
    End I
user3697824
  • 518
  • 4
  • 14
user593301
  • 853
  • 1
  • 8
  • 12
  • This is the textbook problem for better understanding: http://imgur.com/Pc1OMnV – user593301 Apr 17 '15 at 20:09
  • 1
    you need to jigger those around. 1) `CreateText` opens a stream so that should be in a Using block. You *dont* want that when File.Exists is true. Put it in an `Else`. 2) Move the DIMs outside the IF since that creates a local scope 3) Using ..AppendText will work if/when you remove CreateText. You can use the same var to do 2 things - a Create stream and a Append stream – Ňɏssa Pøngjǣrdenlarp Apr 17 '15 at 21:08
  • `Using` is a type of built in `Dim` so you dont need Dim at all - the long form (Option Explicit off) for this would be `Using var As StreamWriter = File.AppendText(...)` – Ňɏssa Pøngjǣrdenlarp Apr 17 '15 at 21:18

2 Answers2

3

This is the problem:

outFile1 = IO.File.CreateText("CustomerNumbers.txt")
inFile1 = IO.File.OpenText("CustomerNumbers.txt")

You cannot open a Reader and Writer on the same file at the same time. When you try, OpenText would throw an IOException that the file is in use. However since that code is in Form_Load, you likely do not see it; instead NET gives up and shows the form without executing the rest of the code in the event. This leaves the second stream objects as Nothing.

Later when you go to try and use it, you get a NullReferenceException because it was never created.

Remedies

A. Close and Dispose of your reader and writer.
One of the issues that you encountered (see comments) when moving the Writer code to elsewhere was that the file was in use (the same exception thrown initially in Form_Load, you just could not see it). This is because your code leaves the reader open in Form_Load. Correct:

Dim infil As StreamReader = IO.File.OpenText("CustomerNames.txt")
customerName = infil.ReadLine()
infil.Close()         ' close the stream
infil.Dispose()       ' dispose of it

These can be shortened using Using blocks which will declare, close and dispose of things for you in a local block:

' declare outF and open it in one line
Using outFile As StreamWriter = File.CreateText("CustomerNames.txt")
    '... write
End Using          ' close and dispose of stream

Not closing and disposing of them - just letting them go out of scope - can lead to leaks.

B. Use as Small of Scope as Possible
Since you do not need the Writer until (or if!) they click the Save button, wait to create it there.

If you should declare them, create them, use them and dispose of them as needed, there is no need for them to have form level scope:

Public Class frmMain
    Private inFile1, inFile2 As IO.StreamReader
    Private outFile1, outFile2 As IO.StreamWriter

Where you declare a variable determines its scope. These are declared at the form level (versus locally - inside a Sub), so they are available everywhere in the form. This is probably what you intended, but it is a bad practice.

Both the above examples declare and create the stream vars locally, do their business, then close and dispose of them. The Using form is more succinct because it implements the Dim, Close() and Dispose() for you.

C. A Stream is a Stream
Closing and Disposing is needed for both Readers and Writers.

In short, if the object supports a Dispose method use it (look in Object Browser: View Menu -> Object Browser).

Community
  • 1
  • 1
Ňɏssa Pøngjǣrdenlarp
  • 37,255
  • 11
  • 50
  • 147
  • These two files are different though, one is input file and one is output. They are different text files. – user593301 Apr 17 '15 at 20:06
  • No, you are using the same file: `CustomerNumbers.txt` to open both a reader and a writer, You are just making the same mistake with 2 different files. – Ňɏssa Pøngjǣrdenlarp Apr 17 '15 at 20:07
  • So I would have to close the read files before opening for writing? – user593301 Apr 17 '15 at 20:13
  • I would read what I need, close them. then later *when it is time to write*, open them for writing. Opening the streamwriter leaves the files locked until you close them. Bad practice – Ňɏssa Pøngjǣrdenlarp Apr 17 '15 at 20:14
  • Ok, I'll try this since it makes the most sense, the textbook didn't mention this.. – user593301 Apr 17 '15 at 20:15
  • I suspect maybe that was part of what you were supposed to learn from this, but the quirk about Exceptions in FormLoad hides it – Ňɏssa Pøngjǣrdenlarp Apr 17 '15 at 20:15
  • I opened the output files after closing the input files and still getting the issue. Is the problem related perhaps when I declare the streamwriter/reader objects? http://imgur.com/Vw857nD – user593301 Apr 17 '15 at 20:24
  • As detailed in my answer, I would **not** create the streamwriter when the form loads - do it when you need it. Also a Using block/scope (also shown) will assure that the StreamReader is closed and disposed before you try to open a Writer (which is likely the case in the pic) – Ňɏssa Pøngjǣrdenlarp Apr 17 '15 at 20:28
  • I tried the method of using just when ready to write to file, but got something about the textfile being used by another process – user593301 Apr 17 '15 at 20:37
  • If you have the same Global scope for the stream vars, the last code was not closing or disposing of the StreamReaders. They could still be open when you go to write - they **too** should be in USING blocks to close and free resources when you are done with them. (In use almost certainly means your a stream object is open on it or perhaps you have it open in NotePad?) – Ňɏssa Pøngjǣrdenlarp Apr 17 '15 at 20:39
  • If IO.File.Exists("CustomerNames.txt") = False Then Dim outFile1, outFile2 As IO.StreamWriter outFile2 = IO.File.CreateText("CustomerNames.txt") Using outFile2 = IO.File.AppendText("CustomerNames.txt") outFile2.WriteLine(customerNum.ToString & " " & txtCustomerName.Text.ToString) End Using ' close and dispose of stream If I do something like this, it says that outfile2 hides a variable in an enclosed block – user593301 Apr 17 '15 at 20:55
  • Hand on, i renamed that variable and I think'its working... checking now. – user593301 Apr 17 '15 at 21:01
  • @user593301 - rather than posting a whole bunch of code in the comments, you should be editing your question with the additional information. Comments don't format code very well; especially a large block of it. If you have to post code in a comment you should be enclosing it in backticks like this: ` someCode() ` -> `someCode()` – вʀaᴎᴅᴏƞ вєнᴎєƞ Apr 17 '15 at 21:16
  • Ok, thanks for the code. I will have to take a step back and see which works best . – user593301 Apr 17 '15 at 21:21
  • They are the same - @Brandon just spelled it out and did your homework for you – Ňɏssa Pøngjǣrdenlarp Apr 17 '15 at 21:24
  • @user593301 - I would highly suggest that you do not turn in a straight copy/paste of the code I posted for your homework project. I used several things that would probably be above the scope of a programming 101 class. – вʀaᴎᴅᴏƞ вєнᴎєƞ Apr 17 '15 at 21:34
1

First off you are using the StreamReaders/Writers in a very wrong way. You should be implementing them with Using. Secondly the reason your getting the exception in the btnSaveCustomer.Click event handler is because you are trying to reference outFile2 without instantiating it; You only declared it as a global variable.

Even though you attempt to instantiate it in the Form.Load event with outFile2 = IO.File.CreateText("CustomerNames.txt") or outFile2 = IO.File.AppendText("CustomerNames.txt"), these may not actually be succeeding. There is a known issue where exceptions can get eaten in the Form.Load event.

Here's an example of how to use the streamreader/writers with the Using statement and it should do everything that the code you posted did. I would highly suggest that you do not turn in a straight copy/paste of the following code for your homework project. I used several things that may be above the scope of a programming 101 class.

Option Strict On

Public Class frmMain

    'Private inFile1, inFile2 As IO.StreamReader
    'Private outFile1, outFile2 As IO.StreamWriter
    Private CustomerNamesPath As String = "C:\StackOverFlow\CustomerNames.txt"
    Private CustomerNumbersPath As String = "C:\StackOverFlow\CustomerNumbers.txt"

    Private CustomerNamesContents As New List(Of String)
    Private CustomerNumbersContents As New List(Of String)


    Private customerNum As Integer = 1
    Private customerName As String

    Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles Me.Load
        Try
            Using numbersSR As New IO.StreamReader(IO.File.Open(CustomerNumbersPath, IO.FileMode.OpenOrCreate, IO.FileAccess.Read))
                Do While Not numbersSR.EndOfStream
                    CustomerNumbersContents.Add(numbersSR.ReadLine)
                Loop
            End Using

            Using namesSR As New IO.StreamReader(IO.File.Open(CustomerNamesPath, IO.FileMode.OpenOrCreate, IO.FileAccess.Read))
                Do While Not namesSR.EndOfStream
                    CustomerNamesContents.Add(namesSR.ReadLine)
                Loop
            End Using
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try


        'If inFile1.Read = Nothing Then
        '    customerNum = 1
        '    txtList.Text = customerNum
        'End If
        If CustomerNumbersContents.Count = 0 Then
            customerNum = 1
            txtList.Text = CStr(customerNum)
        End If

        'Do While inFile1.Peek() <> -1
        '    tempStr = inFile1.ReadLine
        '    customerNum = tempStr
        'Loop
        CustomerNumbersContents.ForEach(Sub(x) customerNum = CInt(x))

        'Do While inFile2.Peek() <> -1
        '    customerName = inFile2.ReadLine().ToString
        '    txtList.Text += customerName & vbCrLf
        'Loop
        CustomerNamesContents.ForEach(Sub(x) txtList.Text &= x & vbCrLf)



        lblNumber.Text = CStr(customerNum)

        'inFile1.Close()
        'inFile2.Close()


    End Sub

    Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnExit.Click
        Try
            Using numbersSR As New IO.StreamWriter(IO.File.Open(CustomerNumbersPath, IO.FileMode.Create, IO.FileAccess.Write))
                CustomerNumbersContents.ForEach(Sub(x) numbersSR.WriteLine(x))
            End Using

            Using namesSR As New IO.StreamWriter(IO.File.Open(CustomerNamesPath, IO.FileMode.Create, IO.FileAccess.Write))
                CustomerNamesContents.ForEach(Sub(x) namesSR.WriteLine(x))
            End Using
        Catch ex As Exception
            MsgBox(ex.Message)
        Finally
            Me.Close()
        End Try

    End Sub


    Private Sub btnSaveCustomer_Click(sender As Object, e As EventArgs) Handles btnSaveCustomer.Click

        If (txtCustomerName.Text <> Nothing) Then
            txtList.Text += customerNum & "  " & txtCustomerName.Text & vbCrLf
            CustomerNamesContents.Add(customerNum.ToString & " " & txtCustomerName.Text.ToString)
            CustomerNumbersContents.Add(CStr(customerNum))
            customerNum = customerNum + 1
            lblNumber.Text = CStr(customerNum)
            txtCustomerName.Clear()
            txtCustomerName.Focus()
        End If

    End Sub
End Class