Null Reference Exception on List of Strings - vb.net

I've been looking at this thread as a way to create a "smart" method for concatenating strings. I have a set of properties where some of them might be null, in which case, I'd like to remove them from the filtered list before using the String.Join method. So, I have something like this:
Dim filteredList = (New List(Of String) From {
a.ToString(),
b.ToString(),
c.ToString()
}).Where(Function(x) Not String.IsNullOrWhiteSpace(x))
Dim result As String = String.Join(" | ", filteredList)
There are instances where a, b, and/or c could be null. When I run this code, I get a null reference exception saying one of the properties .get returned nothing and the code bails. Is there a way to fix this?
Edit
I guess I could fix this by checking if a, b, or c were null before adding them to the list like this:
Dim fullList = New List(Of String)
If a IsNot Nothing Then fullList.Add(a.ToString())
If b IsNot Nothing Then fullList.Add(b.ToString())
If c IsNot Nothing Then fullList.Add(c.ToString())
Dim filteredList = fullList.Where(Function(x) Not String.IsNullOrWhiteSpace(x))
Dim result As String = String.Join(" | ", filteredList)
Is this the best way to handle this situation? Or is there a more elegant way?

Calling the ToString() method on a null object will result in a NullReferenceException.
Instead, you will need to:
Filter to return just the values that are not null
Select the value of ToString on the filtered set
Then join
Also, there really is no need to convert the array to a List.
E.g.
Dim filteredList = { a, b, c }
.Where(Function(x) x IsNot Nothing)
.Select(Function(x) x.ToString())
Dim result As String = String.Join(" | ", filteredList)
Example: https://dotnetfiddle.net/Epwk3Q

Related

Is there a neat and clean way to handle nulls with Yields?

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.

Evaluate expression

So, I have an object with some properties, like this: Dim.d1, Dim.d2,...,Dim.d50
that return strings. For example: Dim.d1="Description A", Dim.d2="Description B",etc.
What I want to do is to attribute these descriptions to the headers of a Gridview and for that I was thinking using indexes, like this pseudocode:
for i=0 until 49
e.Row.Cells[i].Text = Evaluate(Dim.d(i+1))
So, basically, I need a way to change the call to my object properties depending on the index, but I don't know if it is possible. When index i=0, call Dim.d1, when index i=1 call Dim.d2, and so on until 50.
Any ideas?
This is what Arrays or Lists are for!
var dim = new string[50];
dim[0] = "Description A";
dim[1] = "Description B";
..// etc
for(var i=0;i<49;i++)
{
e.Row.Cells[i].Text = dim[i];
}
You can use methods in the System.Reflection namespace to do this. However, the answer is presented in order to answer the question - you should look at using some of the options suggested by other answerers e.g. use a List(Of String) or something similar.
Anyway, let's say you have a class:
Public Class Class1
Public Property d1 As String
Public Property d2 As String
Public Property d3 As String
End Class
And then, let's say you create an instance of that class and set its properties:
Dim obj As New Class1
obj.d1 = "Foo"
obj.d2 = "Bar"
obj.d3 = "Test"
If you then want to have a loop from 1 to 3, and access e.g. d1, d2, d2 etc then this is where you use Reflection:
For i As Integer = 1 To 3
Dim info As System.Reflection.PropertyInfo = obj.GetType().GetProperty("d" & i)
Dim val As String = info.GetValue(obj, Reflection.BindingFlags.GetProperty, Nothing, Nothing, Nothing)
Debug.Print(val.ToString)
Next
Will give you the output:
Foo
Bar
Test
Like Jamiec already posted, use an Array or List.
Where do you description labels come from?
If you have your descriptions in a comma separated string, here is the vb.net code:
dim descriptions as String = "Description A,Description B,Description C"
dim myArray as String() = descriptions.Split(cchar(","))
for i as Integer = 1 To myArray.Length
e.Row.Cells(i-1).Text = myArray(i)
Next

expression.call value cannot be null

I'm trying to code this LINQ query with expression trees:
Result = Result.Where(Function(Row) Convert.ToInt32(Row(2)) <= 10)
Result is declared as Dim Result As IEnumerable(Of Object()).
I have this code so far:
Dim queryiabledata As IQueryable(Of Object()) = Result.AsQueryable
Dim pe As ParameterExpression = Expression.Parameter(GetType(String), "Row(2)")
Dim left As expression = Expression.Call(pe, GetType(String).GetMethod("Convert.ToInt32", System.Type.EmptyTypes))
Dim right As Expression = Expression.Constant(10)
Dim e1 As Expression = Expression.LessThanOrEqual(left, right)
Dim predicatebody As Expression = e1
Dim wherecallexpression As MethodCallExpression = Expression.Call(
GetType(Queryable), "Where", New Type() {queryiabledata.ElementType}, queryiabledata.Expression,
Expression.Lambda(Of Func(Of Object(), Boolean))(predicatebody, New ParameterExpression() {pe}))
Result = queryiabledata.Provider.CreateQuery(Of Object())(wherecallexpression)
But if I run the query, I get an ArgumentNullException (Value cannot be null. Parameter name: method) at Expression.Call.
I tried to change "Convert.ToInt32" to "Value", but I got the same error.
How can I fix it?
Are the another code lines right to get the desired result?
I'm more used to C#, though I do VB.NET occasionally. Reflection in VB.NET is downright ugly. Getting the Where method is a bit of a hack. Here's the code:
shortForm and longForm should be identical.
Dim result As IEnumerable(Of Object()) = New List(Of Object())()
Dim queryiabledata As IQueryable(Of Object()) = result.AsQueryable()
Dim shortForm As Expression = queryiabledata.Where(Function(Row) Convert.ToInt32(Row(2)) <= 10).Expression
Dim whereMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).
First(Function(m) m.Name = "Where").
MakeGenericMethod(GetType(Object()))
Dim convertMethod = GetType(System.Convert).GetMethod("ToInt32", New Type() {GetType(Object)})
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
Dim longform As Expression =
Expression.Call(
whereMethod,
queryiabledata.Expression,
Expression.Lambda(
Expression.LessThanOrEqual(
Expression.Call(
convertMethod,
Expression.ArrayAccess(
rowParameter,
Expression.Constant(2)
)
),
Expression.Constant(10)
),
rowParameter
)
)
result = queryiabledata.Provider.CreateQuery(longform)
This line seems suspect to me:
GetType(String).GetMethod("Convert.ToInt32", System.Type.EmptyTypes)
GetType(String) returns the runtime type String. You then try to get a method called "Convert.ToInt32" which doesn't exist on the string type. I suspect that is returning null which is the source of your exception.
Perhaps you need to use something like this:
GetType(Convert).GetMethod("ToInt32", new Type() {GetType(Object)})
Since there are multiple overloads of the ToInt32 method of the Convert class, you need to specify which of the overloads you want by providing an array of Type as the second parameter. In other words, you a saying "give me the overload that takes an Object type as it's parameter".

How to merge two list to have a distinct list without duplicate values in vb.net

I have this problem in vb.net. Lets say I got 2 Lists ListA and ListB both holds objects of same type.
Eg., one of the property of the object is ID. (ID is written in brackets)
ListA ListB
---------------------------
A(3818) A(3818)
B(3819) B(3819)
C(3820) C(3820)
D(3821) D(3821)
E(3823) F(0)
H(3824) G(0)
I(3825)
How do I merge these two Lists to have a new distinct list which holds objects only once whose ID matches and all other objects(whose ID dont match) are simply added to the new list.
Sample output be,
New List
--------
A(3818)
B(3819)
C(3820)
D(3821)
E(3823)
F(0)
G(0)
H(3824)
I(3825)
When I searched I found that AddRange() and Union are some of the methods to do the merge. But i am not able to find if this works for non standard objects(apart from Integer, String)
Use addRange() and then linq with distinct to filter out the duplicates.
Dim b = YourCollection.Distinct().ToList()
Could use a collection bucket
Dim oCol As New Collection
AddTitems(oCol, oListA)
AddTitems(oCol, olistB)
Public Function AddTitems(oSummaryList As Collection, oList As List(Of thing)) As Collection
For Each oThing As thing In oList
If Not oSummaryList.Contains(CStr(oThing.ID)) Then oSummaryList.Add(oList, CStr(oThing.ID))
Next
Return oSummaryList
End Function
Here are a couple simple functions that should do that for you. I'm not sure how efficient they are though. I don't think there is anything built in.
Private Function nameOfFunction(list1 as list(of type), list2 as list(of type)) as list(of type)
Dim result as new list(of type)
for a as integer = 0 to math.max(list1.count, list2.count) - 1 step 1
If a < list1.count AndAlso resultHasID(result, list1(a).ID) = False Then
result.add(list1(a))
end if
If a < list2.count AndAlso resultHasID(result, list2(a).ID) = False Then
result.add(list2(a))
end if
next
End Function
Private Function resultHasID(testList as list(of type), s as string) as boolean
Dim result as Boolean = False
for a as integer = 0 to testlist.count - 1 step 1
if(testlist(a).ID = s) then
result = true
exit for
End if
Next
Return result
End function
For each item as String in ListA
If Not ListB.Contains(item) Then
ListB.Add(item)
End If
Next

Dynamic query Linq to xml VB.NET

Hey,
I want to write a query that the "where" in the query is a string something like"
Dim query as string= "Name =xxxx and Date > 10 "
Dim t = from book in doc.Descendants("books") Select _
[Name] = book..value, [Date] = book..value....
Where (query)
I build the query string on run time
Thanks...
I'm not saying this is your case but I see this a lot from people that came from ASP classic where we used to build dynamic SQL strings all of the time. We tend to hope that LINQ will give us some more power in part of the code but let us use strings elsewhere. Unfortunately this isn't true. Where takes a boolean argument and there's no way around that. You can write your own parser that uses reflection and eventually returns a boolean but you'd be writing a lot of code that could be error prone. Here's how you really should do it:
Assuming this is our data class:
Public Class TestObject
Public Property Name As String
Public Property Job As String
End Class
And here's our test data:
Dim Objects As New List(Of TestObject)
Objects.Add(New TestObject() With {.Name = "A", .Job = "Baker"})
Objects.Add(New TestObject() With {.Name = "B", .Job = "President"})
Objects.Add(New TestObject() With {.Name = "C", .Job = "Bus Driver"})
Objects.Add(New TestObject() With {.Name = "D", .Job = "Trainer"})
What you want to do is create a variable that represents the data to search for:
''//This variable simulates our choice. Normally we would be parsing the querystring, form data, XML values, etc
Dim RandNum = New Random().Next(0, 3)
Dim LookForName As String = Nothing
Select Case RandNum
Case 0 : LookForName = "A"
Case 1 : LookForName = "B"
Case 2 : LookForName = "C"
End Select
''//Query based on our name
Dim Subset = (From O In Objects Select O Where (O.Name = LookForName)).ToList()
If sometimes you need to search on Job and sometimes and sometimes you don't you just might have to write a couple of queries:
Dim Subset As List(Of TestObject)
Select Case RandNum
Case 0
Subset = (From O In Objects Select O Where (O.Name = "A" And O.Job = "Baker")).ToList()
Case Else
Select Case RandNum
Case 1 : LookForName = "B"
Case 2 : LookForName = "C"
End Select
Subset = (From O In Objects Select O Where (O.Name = LookForName)).ToList()
End Select
And just to explain writing your own query parser (which is a path that I recommend you DO NOT go down), here is a very, very, very rough start. It only supports = and only strings and can break at multiple points.
Public Shared Function QueryParser(ByVal obj As Object, ByVal ParamArray queries() As String) As Boolean
''//Sanity check
If obj Is Nothing Then Throw New ArgumentNullException("obj")
If (queries Is Nothing) OrElse (queries.Count = 0) Then Throw New ArgumentNullException("queries")
''//Array of property/value
Dim NameValue() As String
''//Loop through each query
For Each Q In queries
''//Remove whitespace around equals sign
Q = System.Text.RegularExpressions.Regex.Replace(Q, "\s+=\s+", "=")
''//Break the query into two parts.
''//NOTE: this only supports the equal sign right now
NameValue = Q.Split("="c)
''//NOTE: if either part of the query also contains an equal sign then this exception will be thrown
If NameValue.Length <> 2 Then Throw New ArgumentException("Queries must be in the format X=Y")
''//Grab the property by name
Dim P = obj.GetType().GetProperty(NameValue(0))
''//Make sure it exists
If P Is Nothing Then Throw New ApplicationException(String.Format("Cannot find property {0}", NameValue(0)))
''//We only support strings right now
If Not P.PropertyType Is GetType(String) Then Throw New ApplicationException("Only string property types are support")
''//Get the value of the property for the supplied object
Dim V = P.GetValue(obj, Nothing)
''//Assumming null never equals null return false for a null value
If V Is Nothing Then Return False
''//Compare the two strings, return false if something doesn't match.
''//You could use String.Compare here, too, but this will use the current Option Compare rules
If V.ToString() <> NameValue(1) Then Return False
Next
''//The above didn't fail so return true
Return True
End Function
This code would allow you to write:
Dim Subset = (From O In Objects Select O Where (QueryParser(O, "Name = A", "Job = Baker"))).ToList()
No, there is nothing directly like what you're looking for where you can pass in a string. As they say, when all you have is a hammer, everything looks like a nail...The real problem is that you need to learn what LINQ is good at and apply it to your code (if it is a good fit), rather than try and make it do what you could with a dynamically built SQL query string.
What you should probably be doing is making those "Where" clauses strongly typed anyway. Your current code has a lot of potential to blow up and be hard to debug.
What you could do instead is something like this (sorry, using C#, been a while since I've touched VB.NET):
var query = from book in doc.Descendants("books")
select book;
if(needsNameComparison)
{
query = query.where(book.Name == nameToCompare);
}
if(needsDateComparison)
{
query = query.Where(book.Date > 10);
}
List<book> bookList = query.ToList();
With LINQ, "query" isn't actually run until the "ToList()" call. Since it uses late execution, the query is dynamic in that it's being built on until it actually needs to run. This is similar to the code you were looking to use before since you were building a query string ahead of time, then executing it at a specific point.