This is probally more of an elegance question than functionality. I am looking for the absolutely safest way to check for an integer from a string and an object,
Using most of the built in functions for this in .net seem to generate a first chance exception, displayed in the Immediate window, and over time they just build up. what are the implications of these exceptions as they don't seem to affect the operation of the system.
Here are my two attempts, both feel clunky and I know there has to be a better way than using VB.IsDBNull and Integer.TryParse... Or am I just being anal.
(to integer from object)
Dim nInteger As Integer = 0
If oData Is Nothing OrElse VB.IsDBNull(oData) Then
Else
If bThrowErrorIfInvalid Then
Else
On Error Resume Next
End If
nInteger = CType(oData, Integer)
End If
Return nInteger
(to integer from string)
Dim nInteger As Integer = 0
If sText Is Nothing Then
Else
If bThrowErrorIfInvalid Then
Else
On Error Resume Next
End If
Integer.TryParse(sText, nInteger)
End If
Return nInteger
Whats wrong with using the Integer.TryParse? Thats what it was made for...
int i = 0;
string toTest = "not number";
if(int.TryParse(toTest, out i))
{
// it worked
}
How is that clunky? (C# not VB i know, but same diff)
EDIT: Added if you want to check from an object as well (since TryParse relies on a string) and im not too sure on how you actually plan to use it. Does that cover your concerns a little since this one method will check for both of your cases?
static bool TryParseInt(object o, out int i)
{
i = 0;
if (o.GetType() == typeof(int))
{
i = (int)o;
return true;
}
else if (o.GetType() == typeof(string))
{
return int.TryParse(o as string, out i);
}
return false;
}
You can try this:
Dim i as Integer
Try
i = Convert.ToInt32(obj)
Catch
' This ain't an int
End Try
Convert is in the System namespace.
EDIT: Note: If you are going to put any other code in the Try block, make sure to specify that the only exception the Catch should catch is the exception thrown by Convert.ToInt32 if/when it fails - otherwise you could end up with a nasty problem if something else in that try/catch should fail.
Integer.TryParse is designed to be the safe way to do this: that's why it was put into the framework in the first place. For an Object, you could always just call ToString() before using TryParse.
I'd also avoid using On Error Resume Next in favor of a Try-Catch block if you need to swallow an error for some reason, as it's much less likely to cause an unwanted side effect.
Since it's VB you can also use the IsNumeric function
it seems in your second example, you have a unnecessary check for bThrowErrorIfInvalid because Integer.TryParse never throws an error. Something Like
If bThrowErrorIfInvalid And Not Integer.TryParse(sText, nInteger) Then
Throw New ArgumentException()
EndIf
Dim d As Double = 2.0
Dim i As Integer = CInt(d)
If d - i = 0 Then
Debug.WriteLine("is integer")
Else
Debug.WriteLine("not a integer")
End If
Related
While CommitReader.Read()
Yield New Commit() With {
.FirstValue = CommitReader.GetInt32(CommitReader.GetOrdinal("FirstValue")),
.SecondValue = CommitReader.GetString(CommitReader.GetOrdinal("SecondValue")).Trim(),
'Lots of values
End While
I know I can do something like this; however there are 24 properties and I would like to make this part as clean as possible
While CommitReader.Read()
new Commit (){
Dim index As Integer = reader.GetOrdinal("FirstValue")
If reader.IsDBNull(index) Then
FirstValue = String.Empty
Else
FirstValue = reader(index)
End If
index = reader.GetOrdinal("SecondValue")
If reader.IsDBNull(index) Then
SecondValue = String.Empty
Else
SecondValue = reader(index)
End If
}
End While
Is there a better way to handle this type of thing? I am mainly a C# developer so if the syntax is off a little sorry, I am winging it in VB.
It's a shame that SqlDataReader doesn't have the generic Field extension method like DataRow does, but you could define your own extension method (has to be in a module in VB.NET) to help with the null checks, perhaps something like this:
<Extension>
Function GetValue(Of T)(rdr As SqlDataReader, i As Integer) As T
If rdr.IsDBNull(i) Then
Return Nothing
End If
Return DirectCast(rdr.GetValue(i), T)
End Function
And use it something like this:
While CommitReader.Read()
Yield New Commit() With {
.FirstValue = CommitReader.GetValue(Of Integer?)(CommitReader.GetOrdinal("FirstValue")),
.SecondValue = CommitReader.GetValue(Of String)(CommitReader.GetOrdinal("SecondValue")),
'Lots of values
End While
I haven't tested this fully to make sure it handles all data types appropriately (may be worth looking at DataRowExtensions.Field to see how it does it).
Note that you are using String.Empty as the "null" value for strings, while this will use Nothing/null (I also had to remove the .Trim call to avoid NREs). If you want empty string instead, you could use (adding the Trim back in):
.SecondValue = If(CommitReader.GetValue(Of String)(CommitReader.GetOrdinal("SecondValue")), String.Empty).Trim()
You may also want to move the GetOrdinal calls out of the loop to improve performance.
Obviously you have repetition in your code if ... else ... condition.
So you can extract it in another method.
For your case generic extension method seems good candidate.
Public Module Extensions
<Extension>
Public Function GetValueOrDefault(Of T)(originalValue As object,
defaultValue As T) As T
If originalValue = DbNull.Value Then
Return defaultValue
End If
return DirectCast(originalValue, T)
End Function
End Module
Then use it:
While CommitReader.Read() = True
Dim temp = new Commit With
{
Dim index As Integer = reader.GetOrdinal("FirstValue")
FirstValue = reader(index).GetValueOrDefault(String.Empty)
Dim index As Integer = reader.GetOrdinal("SecondValue")
FirstValue = reader(index).GetValueOrDefault(String.Empty)
}
End While
You can create another overload which return "default" value for given type if it is DbNull
<Extension>
Public Function GetValueOrDefault(Of T)(originalValue As object) As T
Return originalValue.GetValueOrDefault(Nothing)
End Function
Nothing in vb.net is default value, for reference types it is null for Integer it is 0 for example.
For using this overload you need provide type parameter explicitly
While CommitReader.Read() = True
Dim temp = new Commit With
{
Dim index As Integer = reader.GetOrdinal("FirstValue")
FirstValue = reader(index).GetValueOrDefault(Of String)()
Dim index As Integer = reader.GetOrdinal("SecondValue")
FirstValue = reader(index).GetValueOrDefault(Of String)()
}
End While
Notice that your solution executing reader twice, for checking is it null and for reading value. This can cause "tiny" performance issue.
So in extension method above we read value only once and then check value for DbNull.
If you concatenate a string with a Null you get the string:
FirstValue = reader(index) & ""
Kind of "unprofessional" but saves a lot of coding time if all you are doing is converting a possible Null to an empty string. Easy to forget however, so later data dependent errors may pop up.
I am a relative VB.Net noob, and I'm learning by doing. I'm sure what I'm about to ask has been asked 10^19 times before, but whatever code word it's under, I can't figure out how to Google it. Here goes...
We have an object model with one or more Project objects that consists of several Tables, which contain Rows which have Fields. This leads to code all over our apps that looks something like this...
Dim theColor As String = Projects(1).Tables(5).Rows(22).Fields(3)
In our application, if any of the objects in this "call chain" does not exist, the correct value for theColor should be Nothing*. This is just like Excel - the value of an empty cell in an empty row is vbnull, not "Excel has crashed".
This is not how VB.Net works, however. If, for instance, Rows(22) does not exist, the Fields(3) is called on Nothing and an exception is thrown. My question is how to best deal with this...
1) I could check each value to see it it's not Nothing, but that leads to horrible amounts of code...
If Projects(1) IsNot Nothing AndAlso Projects(1).Tables(5) AndAlso...
We have thousands of these, the amount of code this would require would be enormous.
2) I could wrap all accessors in try/catch, but that's really just a different sort of (1)
3) I could have a special instance of each object that has empty values. So, for instance, Tables(5) returns NullTable and Row(22) returns NullRow. But this means I have to always use accessor methods, I can't just look in the underlying arrays. You're probably saying good, but sadly a lot of our older code does just that (yes, duh).
4) Something else entirely? Am I missing some magic that everyone other than me knows?
You could have a function called GetField
Instead of
Dim theColor As String = Projects(1).Tables(5).Rows(22).Fields(3)
You would have
Dim theColor As String = GetField(1, 5, 22, 3)
Function GetField(ByVal projectIndex As Integer, ByVal tableIndex As Integer, ByVal rowIndex As Integer, byVal fieldIndex As Integer) As Object
If Projects(projectIndex) Is Nothing Then
Return Nothing
End If
If Projects(projectIndex).Tables(tableIndex) Is Nothing Then
Return Nothing
End If
If Projects(projectIndex).Tables(tableIndex).Rows(rowIndex) Is Nothing Then
Return Nothing
End If
If Projects(projectIndex).Tables(tableIndex).Rows(rowIndex).fields(fieldIndex) Is Nothing Then
Return Nothing
End If
Return Projects(projectIndex).Tables(tableIndex).Rows(rowIndex).fields(fieldIndex)
End Function
But I got to say... what you are doing looks sloppy. You should think of using classes with properties.
You could concoct a "project manager" of sorts. It is hard to know how viable this is from the post, but something like:
Class ProjectManager
Public Property CurrentProject As ProjectThing
Public Property CurrentTable As Integer
Get
Return tblIndex
End Get
Set
If CurrentProject IsNot Nothing Then
If CurrentProject.Tables(value) Is Nothing Then
Throw exception
Else
tblIndex = value
End If
End If
End Set
End Property
' etc
Then use it to store the current reference to the project and/or table. All the Is Not Nothings can be embedded there.
Private myPrj As New ProjectManager
...
myPrj.CurrentProject = Project(1)
myPrj.CurrentTable = 5
With the "manager" doing all the checking for everyone, you dont have to (much):
Dim theColor As String = myPrj.Rows(22).Fields(3)
or
Dim theColor As String = myPrj.GetRowValue(22, 3)
What it would really be doing is storing a validated object references for you, and testing those not worth storing. It could go as deep as you needed. Even if all it really did was encapsulate those Is Nothing/Is Not Nothing tests, it might add some value.
Public Function GetRowValue(r As Integer, f as Integer) As Something
If r < CurrentProject.Tables(tblIndex).Rows.Count AndAlso
f < CurrentProject.Tables(tblIndex).Rows(r).Fields.Count Then
Return ...
'or
Public Function GetRowValue(Of T)(r As Integer, f as Integer) As T
Once a project is specified, it could expose helpful properties like TableCount. It is possible that the data represented by some of the most used, most important Const definitions, could be exposed as properties:
' swap a property interface for "3"
Dim theColor As String = myPrj.FooColor(22)
You can handle the exception:
Dim theColor As String = Nothing
Try
theColor = Projects(1).Tables(5).Rows(22).Fields(3)
Catch
End Try
If you want to do it 'properly' you should specify the exceptions you are guarding against:
Dim theColor As String = Nothing
Try
theColor = Projects(1).Tables(5).Rows(22).Fields(3)
Catch ex As IndexOutOfRangeException
Catch ex As NullReferenceException
End Try
Should a function that originally doesn't return a value return Nothing anyway (as Visual Studio puts a green squiggle in the End Function statement asking if you'd forget to return a value), or I can ignore VS' tip? What is the impact in returning Nothing, or anything at all?
tl;dr: Should this example code return Nothing or it is not needed? Elucidations are really welcome!
Public Shared sCaminhoGravacaoXML As String = "C:\XMLData\"
Public Shared Function VerificaPastasXML()
If Not IsNothing(sCaminhoGravacaoXML) And sCaminhoGravacaoXML <> "" Then
Dim sRaiz As String = sCaminhoGravacaoXML
If Not FileIO.FileSystem.DirectoryExists(sRaiz) Then
FileIO.FileSystem.CreateDirectory(sRaiz)
End If
If Not Directory.Exists(sRaiz & "tempXML") Then
Dim diInfo As DirectoryInfo = Directory.CreateDirectory(sRaiz & "tempXML")
diInfo.Attributes = FileAttributes.Directory Or FileAttributes.Hidden
End If
Else
sErroBaixaXML = "Não foi possível montar a estrutura de pastas para os arquivos" & vbCrLf & "XML de NFe, favor consultar os parâmetros da filial!"
End If
End Function
Should this example code return Nothing or it is not needed?
Since it never returns a value, it should be a Sub. Function implies to the reader that a value is returned, so your function looks buggy as it is now, confusing everyone who has to maintain it.
Technically, a Function automatically returns the default value of the return data type if there is no explicit return statement. Thus, your function (which is implicitly declared with a return type of Object)¹ would return Nothing; a function with a return type of Integer would return 0, etc.
Since forgetting to return a value is a common cause of errors, the warning is here to help you. If you want to return the default value, it is good practice to use an explicit return statement to make that clear.
¹ Please consider activating Option Strict to avoid such implicit declarations.
A function in vb.net must return something, compared with subs, that doesn't have a return value. If you don't want to return a value, just change it from Function to Sub.
If you are not returning a value, and this is in fact VB.NET - consider using Sub instead of Function, which implies you are not returning a value.
However, in most other languages, it's ok to not return anything as the function will return null / Nothing on it's own.
Further, some code analysis tools will as you to always have a return statement one way or the other, though, so some developers will prefer you to explicitly state that for readability.
Public Shared Function VerificaPastasXML() As Boolean
and
If String.IsNullOrEmpty(sCaminhoGravacaoXML) Then Return False
Try
' ... doSomething (technically you should handle exceptions separately, or check for return values instead.
Return True
Catch ex As Exception
sErroBaixaXML = "Não foi possível montar a estrutura de pastas para os arquivos" & vbCrLf & "XML de NFe, favor consultar os parâmetros da filial!"
Return False
End Try
you should return something to notify the calling procedure if the operation succeeded or failed.
tbh though, the logic to create the folder/file needs some work.
ie: If Not VerificaPastasXML() Then : MsgBox("the operation failed") : Else : doSomething() : End If.
Here is the code I have so far:
Module Module1
Sub Main()
Dim A, B, C, D As Integer
Do
Try
System.Console.WriteLine("How high is the cube?")
A = Int32.Parse(Console.ReadLine())
Catch leg As System.FormatException
System.Console.WriteLine(leg.Message)
Finally
End Try
Loop Until A =
System.Console.WriteLine("How wide is the cube?")
B = Int32.Parse(Console.ReadLine())
System.Console.WriteLine("How long is the cube?")
C = Int32.Parse(Console.ReadLine())
D = A * B * C
Console.WriteLine(D)
System.Console.ReadKey()
End Sub
End Module
I want the first try block to loop till A is an Integer in case the user inputs a letter instead of a number. Does anyone know how I might do that?
Use TryParse instead of your try/catch block in conjunction with a do loop. It returns True when it succeeds and puts the int32 value into the variable a.
Dim a As Integer, tmp As String
Do
System.Console.WriteLine("How high is the cube?")
tmp = Console.ReadLine()
Loop While Int32.TryParse(tmp, a) = False
After the loop, the value stored in a will be an integer. Repeat this for each of the other values.
You can also do like Dai does in his C# sample, and not bother storing into a temporary variable:
Dim a As Integer
Do
System.Console.WriteLine("How high is the cube?")
Loop While Int32.TryParse(Console.ReadLine(), a) = False
If you MUST do it with Try/Catch for an assignment, you can try this though for practical purposes I would do it as above.
Dim a As Integer, valid As Boolean = True
Do
Try
System.Console.WriteLine("How high is the cube?")
a = Int32.Parse(Console.ReadLine())
Catch ex As Exception
valid = False
End Try
Loop While Not valid
In C# (conversion to VB is an exercise for the reader):
public static Int32 GetInt32() {
Int32 value;
while( !Int32.TryParse( Console.ReadLine(), out value ) ) {
Console.WriteLine("Please enter an integer");
}
Console.WriteLine("You entered {0}", value);
return value;
}
Here's a real world example to further expand on one reason why using Try/Catch to control program flow is bad. At least in .NET, throwing an error and handling it in a catch takes a lot more execution time than properly handling the error using proper control flow structures.
I picked up a project from a coworker who was using a function to clean input coming in that was supposed to be numeric. He was using something like the following code to do it.
Public Shared Function SafeDouble(value As Object, Optional DefaultValue As Double = 0.0) As Double
Dim CleanDouble As Double
Try
CleanDouble = CDbl(value) 'try to convert to a double
Catch ex As Exception
CleanDouble = DefaultValue 'if it fails use the default value
End Try
Return CleanDouble
End Function
Which worked fine for the one place he was using it originally to process one row of data. The problem came about when I started using this to process 1000s of rows of data. The execution bogged down big time when it hit this section of the import.
Using some timers, I determined that it was taking between 50 and 100 milliseconds for each value that it couldn't convert and threw an exception for.
I changed the code to use a proper control structure and the time dropped to 2 to 5 milliseconds per value it couldn't convert. The current code is below. I know that it's possible that some value might come in that isn't numeric. If it isn't numeric, I just assign the default value right away.
Public Shared Function SafeDouble(value As Object, Optional DefaultValue As Double = 0.0) As Double
Dim CleanDouble As Double
Try
If IsNumeric(value) Then 'test to see if the data is numeric before converting it
CleanDouble = CDbl(value)
Else
CleanDouble = DefaultValue 'if it isn't numeric, use default value
End If
Catch ex As Exception
CleanDouble = DefaultValue 'if something unexpected happens, use default value
End Try
Return CleanDouble
End Function
As you can see, I have handled the expected error of it not converting because it isn't numeric. This should account for the majority of issues that are likely to occur. I still leave the try/catch in there in case some other, unexpected error occurs.
I have one more concept to add. Methods fire exceptions, to let you handle inlikely problems and prevent application crash. Readlin and writeline can throw exceptions, and if you dont catch them and they happen, your program will crash. Use the try block to do what its meant for: handle unlikely crashes to increae robustness. To do a thorough job you should research the exceptions and write code to prevent the crash.
I am coding in vb.net.
At times the data is empty/null this is due to user's input into the db.
i will like to bypass it, however i get no luck.
here is snippet of my code:
If hct.mydbvalue.name IsNot Nothing Then
dr("mydbvalue") = hct.mydbvalue.name
End If
I still get an error:
System.NullReferenceException: Object reference not set to an instance of an object.
is there a way if it is a null value to not do anything?
Both #FishBasketGordo and #Yuck are correct, you need to check the full object path for nullability:
If (hct IsNot Nothing) AndAlso (hct.mydbvalue IsNot Nothing) AndAlso (hct.mydbvalue.name IsNot Nothing) Then
dr("mydbvalue") = hct.mydbvalue.name
End If
You won't get a NullReferenceException from data in the database that's null when using ADO.NET; ADO.NET uses DBNull.Value to represent null, so your null reference is coming from somewhere else. In the code above, your exception could occur if any of the following were null:
hct
hct.mydbvalue
dr
Make sure that the whole chain is not null. If hct or mydbvalue is null, you'll get the exception.
To me this looks like hct.mydbvalue is null, and therefore you can't call "name" on it.
Private Function NullCheck(ByVal inObject As Object, Optional ByVal defaultValue As Object = "") As Object
Dim out As Object
If Not IsDBNull(inObject) Then
out = inObject ' This returns the value that was passed in when it is not null
Else
out = defaultValue ' This ensures that out is something and defaults to ""
End If
Return out
End Function
You should be checking whether hct is Nothing, as well as mydbvalue. If you look at the exception message property, it will tell you which is causing the error.
I'm also solving this problem, but in C#.
On my project we've complex object paths like "RootObject.childObject.LitleObject.TinyObject.StringName"
when any of these objects in the path is null, you'll get a null reference when you try something easy like
if(RootObject.childObject.LitleObject.TinyObject.StringName == "a")
I would be okay if it just works as whole rest of the path will be null.
eg. when childObject = null, then I want also RootObject.childObject.LitleObject.TinyObject.StringName to be null, not null reference exception.
However I've found no solution yet, but there is one new operator which can slightly help you in some null tasks.
a = object.object ?? defaultValue;
operator ?? is something like ISNULL in SQL server. If object on left is null, it returns the object from right.
It also replaces whole function NullCheck posted by Michael above.
Hope this will help a bit.
more info on operators
http://msdn.microsoft.com/en-us/library/ms173224(v=vs.80).aspx
http://msdn.microsoft.com/en-us/library/6a71f45d(v=vs.80).aspx
you're talking about diferent things.
It doesn't matter if you use ISDBNull(x.y), String.IsNullOrEmpty(x.y) or (x.y=null)
The problem is far sooner than your selected function is called.
when X is null, it cannot have a property Y. so when you call
AnyOfYourPrefferedFunctions(x.y);
the error raises during evaluation of x.y (null. doesn't exist), so it stops before the machine actually knows what is the function you want to call.
Probably only way to check this, would be using reflection. But you would need to send string with path and reference to root. so something like:
var v = GetValueThroughReflection(rootObject, "rootObject.path.to.the.last.object");
Then you'll be able to write a function which will go through the object path and find which one is null and handle it accordingly. e.g. returns null.
But when you'll heavy use that function, it can make your application run slower. as you'll use reflection for such simple task as is getting value out of variable.
from VS14+ you can use
If hct?.mydbvalue?.name IsNot Nothing Then
dr("mydbvalue") = hct.mydbvalue.name
End If
Try inserting a IF NOT isdbnull(hct.mydbvalue.name)
The following code checks all values.
If hct IsNot Nothing AndAlso
hct.mydbvalue IsNot Nothing AndAlso
Not String.IsNullOrWhitespace(hct.mydbvalue.name) Then
dr("mydbvalue") = hct.mydbvalue.name
End If
Note that the last test used String.IsNullOrWhitespace(). I'm assuming name is a string and you don't want to save empty strings.
Update 1
The following code is a simple console application to prove that using IsDbNull() or Micheal's NullCheck() will throw NullReferenceException when hct.mydbvalue is Nothing.
Module Module1
Sub Main()
Dim hct = New hct
Dim dr = New Dictionary(Of String, String)
Dim errorCount = 0
Try
Dim thisCallWillFail = IsDBNull(hct.mydbvalue.name)
Catch ex As NullReferenceException
Console.WriteLine(
"Using IsDBNull() threw NullReferenceException as expected."
)
errorCount += 1
End Try
Try
Dim thisCallWillFail = NullCheck(hct.mydbvalue.name)
Catch ex As NullReferenceException
Console.WriteLine(
"Using NullCheck() threw NullReferenceException as expected."
)
errorCount += 1
End Try
Console.WriteLine("errorCount = 2? {0}", errorCount = 2)
End Sub
Private Function NullCheck(ByVal inObject As Object,
Optional ByVal defaultValue As Object = "") As Object
Dim out As Object
If Not IsDBNull(inObject) Then
' This returns the value that was passed in when it is not null
out = inObject
Else
' This ensures that out is something and defaults to ""
out = defaultValue
End If
Return out
End Function
End Module
Public Class hct
Property mydbvalue As mydbvalue
End Class
Public Class mydbvalue
Property name As String
End Class