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
Related
I've recently implemented reflection to replace the more tedious aspects of our data retrieval from a SQL database. The old code would look something like this:
_dr = _cmd.ExecuteReader (_dr is the SQLDataReader)
While _dr.Read (_row is a class object with public properties)
_row.Property1 = Convert.ToInt16(_dr("Prop1"))
_row.Property2 = Convert.ToInt16(_dr("Prop2"))
_row.Property3 = Convert.ToInt16(_dr("Prop3"))
If IsDBNull(_dr("Prop4")) = False Then _row.Prop4 = _dr("Prop4")
...
Since my code base has a lot of functionality like this, reflection seemed like a good bet to simplify it and make future coding easier. How to assign datareader data into generic List ( of T ) has a great answer that was practically like magic for my needs and easily translated into VB. For easy reference:
Public Shared Function GenericGet(Of T As {Class, New})(ByVal reader As SqlDataReader, ByVal typeString As String)
'Dim results As New List(Of T)()
Dim results As Object
If typeString = "List" Then
results = New List(Of T)()
End If
Dim type As Type = GetType(T)
Try
If reader.Read() Then
' at least one row: resolve the properties
Dim props As PropertyInfo() = New PropertyInfo(reader.FieldCount - 1) {}
For i As Integer = 0 To props.Length - 1
Dim prop = type.GetProperty(reader.GetName(i), BindingFlags.Instance Or BindingFlags.[Public])
If prop IsNot Nothing AndAlso prop.CanWrite Then
props(i) = prop
End If
Next
Do
Dim obj = New T()
For i As Integer = 0 To props.Length - 1
Dim prop = props(i)
If prop Is Nothing Then
Continue For
End If
' not mapped
Dim val As Object = If(reader.IsDBNull(i), Nothing, reader(i))
If val IsNot Nothing Then SetValue(obj, prop, val)
Next
If typeString = "List" Then
results.Add(obj)
Else
results = obj
End If
Loop While reader.Read()
End If
Catch ex As Exception
Helpers.LogMessage("Error: " + ex.Message + ". Stacktrace: " + ex.StackTrace)
End Try
Return results
End Function
The only caveat with this is that it is somewhat slower.
My question is how to optimize. Sample code I find online is all in C# and does not convert neatly into VB. Scenario 4 here seems like exactly what I want, but converting it to VB gives all kinds of errors (Using CodeFusion or converter.Telerik.com).
Has anyone done this in VB before? Or can anyone translate what's in that last link?
Any help's appreciated.
Couple ideas for you.
Don't use the DataReader when reading ALL records at once, it is slower than using a DataAdapter.
When you use the DataAdapter to fill a DataSet, you can iterate through the rows and columns which does NOT use reflection and will be much faster.
I have a program I created (and many other programmers do this too) that generate the code from a database for me. Each table and row is a class that is specifically named an generated in such a way that I can use intellisense and prevents many run-time errors by making them compile-time errors when data changes. This is very much like the EntityFramework but lighter because it fits MY specific needs.
I have a below line of code like this.
Private Sub SomeFunction(ByRef SomeShortVariable As Nullable(Of Short))
Dim SomeStringVariable As String = "" 'Let's assume it is "", that's how I am getting it in real time code
SomeShortVariable = IIf(SomeStringVariable = "", Nothing, SomeStringVariable) 'I want to set SomeShortVariable to Nothing but I am getting 0
End Sub
The variable SomeShortVariable is always sets to 0 even though I want it to be Nothing.
I know Short by default will set the variable to 0.
But how can I make it Nothing. I am using .NET 2.0.
Make SomeShortVariable a Nullable(Of Short) variable.
EDIT:
Also your statement should look like this:
SomeShortVariable = If(String.IsNullOrEmpty(SomeStringVariable), Nothing, New Nullable(Of Short)(Short.Parse(SomeStringVariable)))
SECOND EDIT:
If you're using Visual Studio 2005 the above won't work, because the If operator was only introduced in VS2008. So what you'll have to do is this:
If String.IsNullOrEmpty(SomeStringVariable) Then
SomeShortVariable = Nothing
Else
SomeShortVariable = Short.Parse(SomeStringVariable)
End If
Of course you will want to validate that SomeStringVariable is a numeric string first. :)
Regarding your update, well, that's cause it's Short, and not Nullable(Of Short) in the parameter. Make it nullable of Short and you are done. Although I would refactor to avoid ByRef, have a string parameter SomeStringVariable and return a Nullable(Of Short). Then things would start to make more sense.
Private Shared Function SomeFunction(SomeStringVariable As String) _
As Nullable(Of Short)
If String.IsNullOrEmpty(SomeStringVariable) Then
Return Nothing
Else
Return Convert.ToInt16(SomeStringVariable)
End If
End Function
EDIT: Actually shorthand syntax won't work in this case, for the reasons I outlined in my comment regarding change to If. Just don't use shorthand.
Watch out for the recommendations to use Convert or Parse on cases where the input string could be something other than an empty string but not a number (any user supplied input). It is typically better to use TryParse unless you are absolutely sure someone hasn't passed something in that you aren't expecting. Consider the following:
Dim someString = "a"
Dim someShort as new Nullable(Of Short)
Dim tempShort as Short
Console.WriteLine(someShort)
If Integer.TryParse(someString, tempShort) then
someShort = tempShort
end if
console.WriteLine(someShort)
if Not String.IsNullOrEmpty(someString) then
someShort = Short.Parse(someString) ' Throws FormatException
end if
Console.WriteLine(someShort)
I am trying to run through all the controls in a panel and find which properties the user has changed for each control.
So I have this code:
Private Sub WriteProperties(ByVal cntrl As Control)
Try
Dim oType As Type = cntrl.GetType
'Create a new control the same type as cntrl to use it as the default control
Dim newCnt As New Control
newCnt = Activator.CreateInstance(oType)
For Each prop As PropertyInfo In newCnt.GetType().GetProperties
Dim val = cntrl.GetType().GetProperty(prop.Name).GetValue(cntrl, Nothing)
Dim defVal = newCnt.GetType().GetProperty(prop.Name).GetValue(newCnt, Nothing)
If val.Equals(defVal) = False Then
'So if something is different....
End If
Next
Catch ex As Exception
MsgBox("WriteProperties : " & ex.Message)
End Try
End Sub
Now I face three problems:
When the property refers to image (BackGround Image) I have an error :
ImageObject reference not set to an instance of an object.
The second problem is that the code:
If val.Equals(defVal) = False Then
'So if something is different....
End If
is executes sometimes when the val and defVal are the same.
This is happening in cases that the property is a "parentProperty" like FlatAppearance (which has more child properties)
My loop doesn't look into basic properties like Size, or Location which I want
Re: Not set to an instance of an object, do something like ...
If val IsNot Nothing AndAlso defVal IsNot Nothing AndAlso Not val.Equals(defVal) Then
Which will only do the comparison if neither value is Nothing (aka Null).
Unfortunately, #2 is a fundamental problem - .Equals by default checks if the 2 object references point at the same object in memory - eg if You did
Dim A As New SomeClass
Dim B As New SomeClass
If A.Equals(B) Then
...
End If
Would return False unless SomeClass has an overridden equality comparer, which many classes do not.
You could check if the value in question is a type you know you can compare (Integer, String, Double, etc). If not, you could iterate through its properties and perform the same check again. This would allow you to compare the public properties of any type for equality but wouldn't guarantee the internal state of the classes is the same.
Something Like (Untested/Pseudo)...
Function Compare (PropA, PropB) As Boolean
Dim Match = True
If PropA.Value Is Nothing Or PropB.Value Is Nothing
Match = False
Else
If PropA.Value.GetType.IsAssignableFrom(GetType(String)) Or
PropA.Value.GetType.IsAssignableFrom(GetType(Integer)) Or ... Then
Match = PropB.Value.Equals(PropB.Value)
Else
For Each Prop In PropA.Value.GetType.GetProperties()
Match = Compare(Prop, PropB.Value.GetType.GetProperty(Prop.Name))
If Not Match Then Exit For
Next
End If
End If
Return Match
End Function
This is still not ideal as the internal states of the values may differ.
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
Having a bit of trouble using the List.Find with a custom predicate
i have a function that does this
private function test ()
Dim test As Integer = keys.Find(AddressOf FindByOldKeyAndName).NewKey
here's the function for the predicate
Private Shared Function FindByOldKeyAndName(ByVal k As KeyObj) As Boolean
If k.OldKey = currentKey.OldKey And k.KeyName = currentKey.KeyName Then
Return True
Else
Return False
End If
End Function
by doing it this way means i have to have a shared "currentKey" object in the class, and i know there has to be a way to pass in the values i'm interested in of CurrentKey (namely, keyname, and oldkey)
ideally i'd like to call it by something like
keys.Find(AddressOf FindByOldKeyAndName(Name,OldVal))
however when i do this i get compiler errors.
How do i call this method and pass in the values?
You can cleanly solve this with a lambda expression, available in VS2008 and up. A silly example:
Sub Main()
Dim lst As New List(Of Integer)
lst.Add(1)
lst.Add(2)
Dim toFind = 2
Dim found = lst.Find(Function(value As Integer) value = toFind)
Console.WriteLine(found)
Console.ReadLine()
End Sub
For earlier versions you'll have to make "currentKey" a private field of your class. Check my code in this thread for a cleaner solution.
I have an object that manages a list of Unique Property Types.
Example:
obj.AddProperty(new PropertyClass(PropertyTypeEnum.Location,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
//throws exception because property of type CallingCard already exists
Here is some code to check if properties already exist
Public Sub AddProperty(ByVal prop As PropertyClass)
If Properties.Count < 50 Then
'Lets verify this property does not exist
Dim existingProperty As PropertyClass = _
Properties.Find(Function(value As PropertyClass)
Return value.PropertyType = prop.PropertyType
End Function)
'if it does not exist, add it otherwise throw exception
If existingProperty Is Nothing Then
Properties.Add(prop)
Else
Throw New DuplicatePropertyException("Duplicate Property: " + _
prop.PropertyType.ToString())
End If
End If
End Sub
I haven't needed to try this in newer versions of VB.Net which might have a nicer way, but in older versions the only way that I know of would be to have a shared member in your class to set with the value before the call.
There's various samples on the net of people creating small utility classes to wrap this up to make it a little nicer.
I've found a blog with a better "real world" context example, with good variable names.
The key bit of code to Find the object in the list is this:
' Instantiate a List(Of Invoice).
Dim invoiceList As New List(Of Invoice)
' Add some invoices to List(Of Invoice).
invoiceList.Add(New Invoice(1, DateTime.Now, 22))
invoiceList.Add(New Invoice(2, DateTime.Now.AddDays(10), 24))
invoiceList.Add(New Invoice(3, DateTime.Now.AddDays(30), 22))
invoiceList.Add(New Invoice(4, DateTime.Now.AddDays(60), 36))
' Use a Predicate(Of T) to find an invoice by its invoice number.
Dim invoiceNumber As Integer = 1
Dim foundInvoice = invoiceList.Find(Function(invoice) invoice.InvoiceNumber = invoiceNumber)
For more examples, including a date search, refer to Mike McIntyre's Blog Post