How do I replace On Error Resume Next with try-catch? - vb.net

I have migrated a few controls from VB6 to VB.Net and one of the read-only properties has "On Error Resume Next." Because of this, the property is not throwing any error and it will return a value always.
Now I have replaced this with try-catch and I want your guys' opinion whether the try-catch implementation holds good or need any changes.
Below, I have shown the original code as well as the try-catch implementation code.
Original wizard migrated code
Public ReadOnly Property TotalAmount() As String
Get
'On Error Resume Next
Dim intIndex As Short
Dim numFS As Short
Dim totalAmount As Double
With m_udtProperties_READ
numFS = CShort(UBound(m_udtProperties_READ.FundSource))
If numFS >= 0 Then
For intIndex = 0 To numFS
totalAmount = totalAmount +
CDbl(m_udtProperties_READ.FundSource(intIndex).FromSide.Amount)
Next
End If
TotalAmount= CStr(totalAmount)
End With
End Get
Try catch implementation code.
Public ReadOnly Property TotalAmount() As String
Get
Dim intIndex As Short
Dim numFS As Short
Dim totalAmount As Double
With m_udtProperties_READ
Try
numFS = CShort(UBound(m_udtProperties_READ.FundSource))
Catch ex As Exception
End Try
If numFS >= 0 Then
For intIndex = 0 To numFS
Try
totalAmount = totalAmount + CDbl(m_udtProperties_READ.FundSource(intIndex).FromSide.Amount)
Catch ex As Exception
End Try
Next
End If
TotalAmount = CStr(totalAmount)
End With
End Get
End Property
is there any better way than above?

on error resume next is nasty when you need to convert code from vb6 to .net.
Basically, it means to simply ignore any error that comes after it and continue with the code execution right from the code row after the one that threw the error.
In .Net, doing the equivalent thing would mean that each row after where the on error resume next was, should be wrapped in a try...catch block with an empty catch.
Obviously, this is not practical nor is it good practice (In fact, it's a very bad practice to swallow exceptions).
Lucky for everyone that translates code, not every row is likely to throw an exception.
You need to isolate the danger zones in your translated code and only wrap them in try...catch.
I would recommend not to swallow the exceptions but propagate them to where they can be handled - so my advise is to do refactoring instead of just translating.

Related

"Object reference not set to an instance of an object" message

I keep getting window that pops up when I run a VB.NET console program I made that simply says "Object reference not set to an instance of an object." The window doesn't even say "error" or anything--the title is simply the name of the project. However...I'm assuming this is something I don't want.
I searched around a bit and found posts about similar messages but couldn't figure out how they applied to my situation.
Here is some of my code. (This program is supposed to take some data from a preformatted text file that describes the geometry of a cross section of a river and systematically enters some new geometric data to represent the riverbed being cleaned/cleared out in a certain way, and then write the new data to a new file in a similar format.)
Imports System
Imports System.IO
Imports Microsoft.VisualBasic.FileIO
Module module1
Public Sub Main()
Using sr As New StreamReader("C:\inputfile.txt")
Using outfile As New StreamWriter("C:\outputfile.txt")
Dim line As String = ""
Dim styles As Globalization.NumberStyles
styles = Globalization.NumberStyles.AllowLeadingSign Or Globalization.NumberStyles.AllowDecimalPoint
Dim stations(-1) As Double
Dim elevations(-1) As Double
Dim i As Integer = 0
Do
Try
line = sr.ReadLine()
Dim stringarray() As String = line.Split()
ReDim Preserve stations(i)
ReDim Preserve elevations(i)
stations(i) = Double.Parse(stringarray(0), styles)
elevations(i) = Double.Parse(stringarray(1), styles)
Catch ex As Exception
MsgBox(ex.Message)
End Try
i = i + 1
Loop Until line Is Nothing
Dim min As Double = elevations(0)
(some more code.....)
End Using
End Using
End Sub
End Module
I only included the first part of my code because when I put a break at the "Loop Until line Is Nothing" statement, the message didn't come up until after I went through the break, but when I put the break at the "Dim min As Double = elevations(0)" statement, the message came up before the program got to the break.
I don't really get what's wrong with my code. Anyone have any ideas?
Thanks!
See if this works any better:
Public Sub Main()
Dim styles As Globalization.NumberStyles = Globalization.NumberStyles.AllowLeadingSign Or Globalization.NumberStyles.AllowDecimalPoint
Dim stations As New List(Of Double)
Dim elevations As New List(Of Double)
For Each line As String in File.ReadLines("C:\inputfile.txt")
Try
Dim stringarray() As String = line.Split()
stations.Add(Double.Parse(stringarray(0), styles))
elevations.Add(Double.Parse(stringarray(1), styles))
Catch ex As Exception
MsgBox(ex.Message)
End Try
Next Line
Dim min As Double = elevations(0)
Using outfile As New StreamWriter("C:\outputfile.txt")
End Using
End Sub
Note that I moved your output file to the end... you haven't written anything yet, and so it's a good idea to wait as long as possible to open it, to keep it open for as short a time as possible.
The code fails after the last line has been read because the exit condition (Loop Until) doesn't know that you have reached the end of file. So another loop is started and the code tries to read an inexistent line. The last ReadLine returns Nothing, but the code tries to split it.
You could add a check before trying to split the line and jump directly to the Loop Until statement if you don't have anymore lines to read. Of course I also suggest to remove the array redim at every loop and use a more flexible List(Of Double)
Dim stations = New List(Of Double)
Dim elevations = New List(Of Double)
Do
Try
' After the last line this command returns null (Nothing in VB)
line = sr.ReadLine()
if line IsNot Nothing Then
Dim stringarray() As String = line.Split()
stations.Add(Double.Parse(stringarray(0), styles))
elevations.Add(Double.Parse(stringarray(1), styles))
End If
Catch ex As Exception
MsgBox(ex.Message)
End Try
Loop Until line Is Nothing
OK...I feel really dumb now...after reading through all of your answers and looking at Steve's code, I realized my code was going through the loop one last time where "line" was set as nothing at the beginning of the loop and it was still trying to add "nothing" as an element to the arrays. The key to fixing it was adding an "If line IsNot Nothing" statement before the statements adding elements to the arrays, which Steve had.
Thank you all for your answers!

system.NullReferenceException: Object not set to an instance of an object

Dim Permission As String
Dim chk As String = "p"
Permission = (ds.Tables("privilege").Rows(0).Item(0)).ToString
MessageBox.Show(Permission)
Dim PermissionArray() As String = Split(Permission, ":")
For i As Integer = 0 To 36
If PermissionArray(i) = 1 Then
Try
Dim chkBox As CheckBox = CType(Me.Controls(chk & i), CheckBox)
chkBox.Checked = True
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try
End If
Next
This code gives me the following error in catch, i have googled but nothing is working
This is the error: System.NullReferenceException – Object reference not set to an instance of an object.
As you noted chkBox.Checked throws NullReferenceException, you should evaluate the following line for the error:
Dim chkBox As CheckBox = CType(Me.Controls(chk & i), CheckBox)
' This may throw NullReferenceException if there is no (chk & i) control available
chkBox.Checked = True
Although it turned out not to be your problem this time,
Permission = (ds.Tables("privilege").Rows(0).Item(0)).ToString
is a prime candidate for a system.NullReferenceException. This statement relies on everything in your data set being fully and correctly populated or errors can occur.
If the table "privilege" does not exist in the dataset, or the table is empty, or the first column of the first row is null, you can get exceptions and it will be very hard to tell what is wrong. You should test those conditions before relying on the assignment, so you don't get exceptions.
I'll bet you are missing one or more controls "p0" ... "p35" since blindly build the ID, ask the form for the control of that ID but never check if it was actually found. Try including the value of 'i' in your message when the exception is caught. That'll be the first control you're misisng.
And then, be sure to check the return values of functions you call before you USE those return values.

Why to set an object to Nothing in the Finally block?

In this VB.NET code:
Dim o as SomeClass
Try
o = new SomeClass
'call some method on o here
Catch(...)
...
Finally
o = Nothing
End Try
Why is there a need to set o to Nothing? What if i don't set it to Nothing in the Finally block? What i think is that it is OK if you don't set it to Nothing because the object will be marked for GC.
If the object is unsafe to use out of the try catch should this be done. If this was a stream for example you'd see the stream closed and then set to nothing. It is not always the right thing to do but this code is seen a lot.
consider this code
Sub Main()
Dim o As String
Try
o = "Hello"
Console.Out.WriteLine("hi {0}", o)
Catch ex As Exception
' do something here
Finally
o = Nothing
End Try
' unable to do something here
End Sub
Allthough it is a silly example it does mean you cannot reference o outside now because it is no longer set to an instance of an object. That is why it is done by a lot of people. If you are in a function and a function ends at that point there is no need to set to Nothing as the object falls out of scope however lots of people will set stuff to Nothing out of habit I'd consider that incorrect and bad code design
It's because the object is not safe to use outside the the try.. catch.. finally block. It's not guaranteed it's in a consistent state, so it's set to Nothing to make it obvious t's not supposed to be used.

Building A True Error Handler

I am trying to build an error handler for my desktop application. The code Is in the class ZipCM.ErrorManager listed below.
What I am finding is that the outputted file is not giving me the correct info for the StackTrace.
Here is how I am trying to use it:
Try
'... Some stuff here!
Catch ex As Exception
Dim objErr As New ZipCM.ErrorManager
objErr.Except = ex
objErr.Stack = New System.Diagnostics.StackTrace(True)
objErr.Location = "Form: SelectSite (btn_SelectSite_Click)"
objErr.ParseError()
objErr = Nothing
End Try
Here is the class:
Imports System.IO
Namespace ZipCM
Public Class ErrorManager
Public Except As Exception
Public Location As String
Public Stack As System.Diagnostics.StackTrace
Public Sub ParseError()
Dim objFile As New StreamWriter(Common.BasePath & "error_" & FormatDateTime(DateTime.Today, DateFormat.ShortDate).ToString().Replace("\", "").Replace("/", "") & ".log", True)
With objFile
.WriteLine("-------------------------------------------------")
.WriteLine("-------------------------------------------------")
.WriteLine("An Error Occured At: " & DateTime.Now)
.WriteLine("-------------------------------------------------")
.WriteLine("LOCATION:")
.WriteLine(Location)
.WriteLine("-------------------------------------------------")
.WriteLine("FILENAME:")
.WriteLine(Stack.GetFrame(0).GetFileName())
.WriteLine("-------------------------------------------------")
.WriteLine("LINE NUMBER:")
.WriteLine(Stack.GetFrame(0).GetFileLineNumber())
.WriteLine("-------------------------------------------------")
.WriteLine("SOURCE:")
.WriteLine(Except.Source)
.WriteLine("-------------------------------------------------")
.WriteLine("MESSAGE:")
.WriteLine(Except.Message)
.WriteLine("-------------------------------------------------")
.WriteLine("DATA:")
.WriteLine(Except.Data.ToString())
End With
objFile.Close()
objFile = Nothing
End Sub
End Class
End Namespace
What is happenning is the .GetFileLineNumber() is getting the line number from objErr.Stack = New System.Diagnostics.StackTrace(True) inside my Try..Catch block. In fact, it's the exact line number that is on.
Any thoughts of what is going on here, and how I can catch the real line number the error is occuring on?
Edit: Changed the code to account for the Exception.StackTrace being a string rather than a real StackTrace
You're creating a new StackTrace, so then it will be for the line you're declaring it on, if you want the line number of the original exception, use the stack trace in Exception.StackTrace.
I think you're being a little confused, I can't see why you create the new StackTrace at all?
Edit: Added more bits to the answer here since easier to see the syntax than in a comment
Currently you have the line
objErr.Stack = New System.Diagnostics.StackTrace(True)
Which means that you're creating a whole new stacktrace, starting when you're creating it.
Instead change that line to:
objErr.Stack = New System.Diagnostics.StackTrace(ex, True)
Which will have the stacktrace from when the error actually happened.
Edit: Added complete sample:
Private Sub a1()
Try
a2()
Catch ex As Exception
Dim st As New StackTrace(True)
Debug.WriteLine(String.Format("ST after exception, will give line number for where st was created. Line No: {0}", st.GetFrame(0).GetFileLineNumber()))
st = New StackTrace(ex, True)
Debug.WriteLine(String.Format("ST after exception using exception info, will give line number for where exception was created. Line No: {0}", st.GetFrame(0).GetFileLineNumber()))
End Try
End Sub
Private Sub a2()
Dim st As New StackTrace(True)
Debug.WriteLine(String.Format("ST before exception, will give line number for where st was created. Line No: {0}", st.GetFrame(0).GetFileLineNumber()))
Dim b As Integer = 0
Dim a As Integer = 1 / b
End Sub
SOLVED: You should change the wrong instruction, of the original code:
.WriteLine(Stack.GetFrame(0).GetFileLineNumber())
with this new one:
.WriteLine(Stack.GetFrame(Stack.FrameCount - 1).GetFileLineNumber)
and you will see, it will return the exact Line_Number of code
where the run time error occurs!!
You do not need to include a Stack property for your ErrorManager, because you have access to the stack trace through the exception.
From experience, I would create a Shared Sub Write(ex As Exception, location As String) method on the ErrorManager, and call in your Catch statement as:
ZipCM.ErrorManager.Write(ex, "Form: SelectSite (btn_SelectSite_Click)")
This change results in cleaner code, reduces the need to write lots of code in each Catch statement, and allows you to change the implementation of Write without having to revisit/rework/refactor each Catch statement.
For instance, you can change the Write method to also call Debug.WriteLine(ex) so you can see which exceptions you are handling during debugging without having to open the file. Moreover, you may want to include WriteNotify method that displays a message box of the exception then calls the Write method to log the exception.

Better way to retry a statement that threw an exception in vb.net

I usually do something like this:
Dim Attempts = 0
Try
Retry:
<Block>
Catch
If Attempts < 3 Then
Attempts += 1
Thread.Sleep(2000)
GoTo Retry
Else
Throw
End If
End Try
This is really bad looking for me, but i don't know of a better way of doing it.
You could also try the following:
Dim retryCount as Integer = 0
Dim wasSuccessful as Boolean = False
Do
Try
<statements>
'set wasSuccessful if everything was okay.'
wasSuccessful = True
Catch
retryCount +=1
End Try
Loop Until wasSuccessful = True OrElse retryCount >=5
'check if the statements were unsuccessful'
If Not wasSuccessful Then
<do something>
End If
It will retry up to five times if the statements were not successful but will immediately exit the loop if the statements' execution was successful.
I think that's a bad usage, I use this one, and it's much cleaner.
Dim maxAttempt As Integer = 2
For i As Integer = maxAttempt To 0 Step -1
Try
...
'Successful Quit
Exit For
Catch
Thread.Sleep(2000)
End Try
Next
Just use a For loop or a While loop rather than GoTo, breaking on success. But other than that, it's the right approach.
Conceptually it's the right approach, although I would not catch each and every exception, see answer by #0xA3.
You could make it a bit 'prettier' by separating the retry logic from the actual code, e.g.:
Sub TryExecute(Of T As Exception)(ByVal nofTries As Integer,
ByVal anAction As Action)
For i As Integer = 1 To nofTries - 1
Try
anAction()
Return
Catch ex As T
Thread.Sleep(2000)
End Try
Next
' try one more time, throw if it fails
anAction()
End Sub
Which could then be used like so:
TryExecute(Of SomeExceptionType)(3, Sub()
<Block>
End Sub())
This will only work in VB 10, if you're using .Net 3.5 / VB 9, you need to separate this in a separate function
In general, retrying something that failed should be considered very carefully. Usually it is much better to report the error and let the user decide.
Raymond Chen gives a nice example how automatic retries might lead to unwanted problems and gives the advice to avoid retrying:
Take it easy on the automatic retries