VB.NET: question about "using" block - vb.net

Consider the code:
On Error Goto ErrorHandler
Using sr As StreamReader = New StreamReader(OpenFile)
str = sr.ReadToEnd
sr.Close()
End Using
Exit Sub
ErrorHandler:
If there is an error inside the Using block how do you clean up the sr object?
The sr object is not in scope in ErrHandler so sr.Close() cannot be called. Does the Using block cleanup any resources automatically even if there is an error?

As codeka says, you don't need to call Close on sr. It'll called automatically, and that includes if there is an error. Using the using statement gives you the same functionality as try ... finally ... end try.
And as you see in the answers to your other question, you shouldn't be using On Error etc just do:
Try
Using sr as StreamReader ...
...
End Using
Catch ex as SomeException
...
End Try

Yes, the using block will automatically call IDisposable.Dispose (which, for a StreamReader is the same as calling Close) so there's nothing you need to do (that's the whole point of using blocks!)

This code:
Using sr As StreamReader = New StreamReader(OpenFile)
str = sr.ReadToEnd
sr.Close()
End Using
Is really equivalent to this:
Dim sr As StreamReader = Nothing
Try
sr = New StreamReader(OpenFile)
sr.Close() ' notice: unnecessary '
Finally
sr.Close()
End Try
Keep in mind that code within a Finally block will always execute before the method returns (if it throws an exception of its own, well, then you're in for a world of hurt). So the sr.Close line you have within your Using block is superfluous (notice it is unnecessary in the equivalent code using Try/Finally since sr.Close will be called in the Finally no matter what -- exception thrown or not).

Related

Log Writer not creating new line for each entry

I get the feeling this is something really simple, but I've tried I don't know how many permutations of vbNewLine, Environment.NewLine, sMessage & vbNewLine (or Environment.Newline) I've tried, or how many pages on this site, or through Google I've looked at but nothing has worked.
I even tried getting help from a VB.Net discord channel I'm a part of and they suggested to do the same things that I've done and the procedure is still writing each new log entry at the end of the previous one in a continuous string. My writer is below. Am I missing something simple?
Edit: The code that worked is below in case anyone else comes along with the same issue. If you want to see the original code it's in the edit log.
Option Explicit On
Imports System.IO
Public Class WriteroLog
Public Shared Sub LogPrint(sMessage As String)
Dim AppPath As String = My.Application.Info.DirectoryPath
If File.Exists($"{AppPath}\Log.txt") = True Then
Try
Using objWriter As StreamWriter = File.AppendText($"{AppPath}\Log.Txt")
objWriter.WriteLine($"{Format(Now, "dd-MMM-yyyy HH:mm:ss")} – {sMessage}")
objWriter.Close()
End Using
Catch ex As Exception
MsgBox(ex)
Return
End Try
Else
Try
Using objWriter As StreamWriter = File.CreateText($"{AppPath}\Log.Txt")
objWriter.WriteLine($"{Format(Now, "dd-MMM-yyyy HH:mm:ss")} – {sMessage}")
objWriter.Close()
End Using
Catch ex As Exception
MsgBox(ex)
Return
End Try
End If
End Sub
End Class
The File.AppendText() method creates a new StreamWriter that is then used to append Text to the specified File.
Note, reading the Docs about this method, that you don't need to verify whether the File already exists: if it doesn't, the File is automatically created.
As a side note, when creating a Path, it's a good thing to use the Path.Combine method: it can prevent errors in the path definition and handles platform-specific formats.
Your code could be simplified as follows:
Public Shared Sub LogPrint(sMessage As String)
Dim filePath As String = Path.Combine(Application.StartupPath, "Log.Txt")
Try
Using writer As StreamWriter = File.AppendText(filePath)
writer.WriteLine($"{Date.Now.ToString("dd-MMM-yyyy HH:mm:ss")} – {sMessage}")
End Using
Catch ex As IOException
MsgBox(ex)
End Try
End Sub
The File.CreateText does not assign result to "objWrite", should be:
objWriter = File.CreateText($"{AppPath}\Log.Txt")
Not really sure if this is the root of your problem, but it is an issue.
In essences, your logic is re-opening or creating the stream "objWriter" for every call to this method. I would recommend you initialize "objWriter" to Nothing and only define if it is Nothing.
Set to Nothing as below.
Shared objWriter As IO.StreamWriter = Nothing
Then add check for Nothing in logic.

How do I output to a text file without getting an IO exception?

I have this code.
Dim txtVern As String = String.Empty
Try
Using verSR As New StreamReader(appDataVersionLoc)
txtVern = verSR.ReadToEnd()
End Using
Catch ex As Exception
Dim verFile As System.IO.FileStream
verFile = System.IO.File.Create(appDataVersionLoc)
Dim wWFERes As DialogResult = MessageBox.Show("Version file missing/corrupt, created a new one.")
If My.Computer.FileSystem.FileExists(appDataVersionLoc) Then
My.Computer.FileSystem.WriteAllText(appDataVersionLoc, "0.0.0.0", True)
End If
End Try
But when it tries to execute My.Computer.FileSystem.WriteAllText(appDataVersionLoc, "0.0.0.0", True), I get this error:
The process cannot access the file 'C:\Users\Dubstaphone\AppData\Roaming\TheArena\version.txt' because it is being used by another process.
How to I avoid this? What I did was I made it create a text file called "version.txt" if it doesn't already exist. Now I'm trying to write "0.0.0.0" to it, but it's giving me that error. By the way, "appDataVersionLoc" equals GetFolderPath(SpecialFolder.ApplicationData) & "\TheArena\version.txt"
This variable works fine for everything else.
Thanks!
P.S.
I'm a complete noob.
System.IO.File.Create or My.Computer.FileSystem.WriteAllText may still hold a lock on the file. Use the System.IO.File WriteAllText and a Using statement.
If you use a Stream you should close/dispose of it. Better yet when dealing with files always use a Using statement.
Edit:
Example of creating a file
Using File.Create(path)
End Using
If File.Exists(appDataVersionLoc) Then
File.WriteAllText(appDataVersionLoc, "0.0.0.0")
End If
or
Dim appDataFile = File.Create(path)
appDataFile.Close

will everything automatically close in this code if there is an exception

three questions please
1) If there is an exception in the try below will the request stream automatically close as it is in a using
2)do I even need the requestStream.Close() and requestStream.Dispose() as it is in a using?
3) do I need to close the System.Net.FtpWebRequest?
Try
Dim rqst As System.Net.FtpWebRequest = DirectCast(System.Net.WebRequest.Create("ftp://1.com/text.txt"), System.Net.FtpWebRequest)
rqst.Credentials = New System.Net.NetworkCredential("useb", "pass")
rqst.Method = System.Net.WebRequestMethods.Ftp.UploadFile
'Throw New ApplicationException("Exception Occured")
Dim fle() As Byte = System.IO.File.ReadAllBytes("C:\test.txt")
Using requestStream As Stream = rqst.GetRequestStream()
requestStream.Write(fle, 0, fle.Length)
requestStream.Close() 'do I need this?
requestStream.Dispose() 'do I need this ?
End Using
Catch ex As Exception
MessageBox.Show(ex.Message.ToString())
End Try
No you dont need if you are using using. As using itself is used to dispose of. Also the reason for the "using" statement is to ensure that the object is always disposed correctly, and it doesn't require explicit code to ensure that this happens.
Dispose method is called immediately when control flow exits the using block.
Yes. If the exception occurs while executing code inside the using statement, the stream will be disposed before it goes to the Catch block.

StreamReader scoping in exception thrown scenario

In the following subroutine, will the StreamReader be closed properly when the exception is thrown? Or do I have do something myself to ensure this?
Sub mySub()
Dim sr As StreamReader = File.OpenText("someFilename")
Dim line As String = sr.ReadLine()
While Not (line Is Nothing)
' Some logic here
If someCondition Then
Throw New Exception("someExplanation")
End If
line = sr.ReadLine()
End While
End Sub
You should wrap the StreamReader variable in a using statement.
Using sr as StreamReader = File.OpenText("someFilename")
...
End Using
Stream won't be closed until the process is terminated. Even it does you should close it. Best way is to use "using" method as it mentioned before. But it doesn't cause data loss unlike while writing it does. In other translation, you should close the Stream, but it's not critic.

how do I clean up after a StreamWriter during an exception?

I'm trying to clean-up after an exception, and I'm not sure how to handle a StreamWriter.
Dim sw As StreamWriter
Try
''// stuff happens
somethingBad1() ''//Sometimes throws an exception
sw = New StreamWriter(File.Open("c:\tmp.txt", FileMode.Create))
''// stuff happens
somethingBad2() ''//Also sometimes throws an exception
sw.Write("Hello World")
sw.Flush() ''//Flush buffer
sw.Close() ''//Close Stream
Catch ex As Exception
sw = Nothing
Finally
sw = Nothing
end try
If somethingBad1 throws an exception, I don't need to do anything to sw; however, if somathignBad2 happens, sw has already been created and I need to close it. But How do I know if sw has been created or not?
''//stuff happens but you don't care because you didn't instantiate
''// StreamWriter yet
somethingBad1() ''//Sometimes throws an exception
Using sw As New StreamWriter("test.dat")
''// stuff happens
somethingBad2() ''//Also sometimes throws an exception
''//as you are in a using statement the sw.Dispose method would be called
''//which would free the file handle properly
sw.Write("Hello World")
End Using
Only do the try after assigning the sw variable (in your sample). Or use a using statement.
But as a general rule, you should close the StreamWriter (if not using it with using), and not just assign Nothing to it. Also, catching all exceptions should be avoided, only handle exception which you know how to handle gracefully.
Darin has it right, but just one stylistici point to expand on Pavel Minaev's comment: unlike VB6, in VB.Net setting your sw reference to Nothing has no real effect. You really don't need to do it. What you could do is have code like this in your finally block:
Finally
''# test
If sw IsNot Nothing Then sw.Dispose()
End
And that would take care of all the required cleanup (including what you've show of the catch block). You wouldn't even need to close the stream in your main code. But Using blocks are normally the better way to handle this.