Visual Studio 2012 Custom CompareTo Method slow when debugging - vb.net

I have a DataTable that I'm sorting ascending by 3 columns. Tricky part is that the column can contain numeric and non-numeric values. Because of that, we see if we can cast this field, if so, we do numeric compare, otherwise we do a string compare.
This works very good in compiled version, but in Visual Studio 2012 this goes extremly slow ... Some how the comparing takes more then 5 minutes in debugging mode, in compiled mode it takes less than a second.
I deleted all breakpoints, but still only this part of my code goes to slow in debugging mode.
The comparing code:
Public Function CompareValues(ByVal sThis As String, ByVal sOther As String) As Integer
Dim bFirstNumeric As Boolean = IsNumeric(sThis)
Dim bOtherNumeric As Boolean = IsNumeric(sOther)
If bFirstNumeric AndAlso bOtherNumeric Then
Return Integer.Parse(sThis).CompareTo(Integer.Parse(sOther))
ElseIf bFirstNumeric And Not bOtherNumeric Then
Return -1
ElseIf Not bFirstNumeric And bOtherNumeric Then
Return 1
Else
Return sThis.CompareTo(sOther)
End If
End Function

I would do the IsNumeric check like this and then proceed, from here. It is cleaner.
Dim Value1 As Object
Dim Value2 As Object
Dim V1 As Integer
Dim V2 As Integer
Public Function IsNumeric(ByVal Expression As Object) As Boolean
If IsNumeric(Value1) = True And IsNumeric(Value2) = True Then
V1 = Integer.Parse(Value1)
V2 = Integer.Parse(Value2)
End If
Return True
End Function
Let me know, if you need more help.

Related

How to convert DateTime to 64 bit representation?

In another question, I asked how to convert 64 bit _ComObject to native DateTime. I would like to also be able to do the reverse.
The code to convert 64 bit _ComObject to DateTime:
<DirectoryProperty("lastLogonTimestamp")>
Public Property LastLogonTimestamp() As Date?
Get
'Dim valueArray = GetProperty("whenChanged")
Dim valueArray = ExtensionGet("lastLogonTimestamp") 'ExtensionGet("LastLogon")
If valueArray Is Nothing OrElse valueArray.Length = 0 Then Return Nothing
Dim lastLogonDate = valueArray(0)
Dim lastLogonDateType = lastLogonDate.GetType()
Dim highPart = CType(lastLogonDateType.InvokeMember("HighPart", Reflection.BindingFlags.GetProperty, Nothing, lastLogonDate, Nothing), Int32)
Dim lowPart = CType(lastLogonDateType.InvokeMember("LowPart", Reflection.BindingFlags.GetProperty Or Reflection.BindingFlags.Public, Nothing, lastLogonDate, Nothing), Int32)
Dim longDate = CLng(highPart) << 32 Or CLng(lowPart)
Dim result = IIf(longDate > 0, CType(DateTime.FromFileTime(longDate), DateTime?), Nothing)
Return result
'Return DateTime.FromFileTimeUtc(valueArray(0))
End Get
Set(value As Date?)
ExtensionSet("lastLogonTimestamp", value)
End Set
End Property
Working with an example DateTime value of #2/19/2018 8:17:20 AM#, I need a conversion to come back with 64 bit representing both date and time parts. I believe that value should be 131635198405088370, and the reason I think so is because AD says so when I inspect the datetime value.
I have tried this..
Dim my64bitDate = DateTime.Parse("#2/19/2018 8:17:20 AM#").ToFileTime
... which returns 131635198400000000, which is only part of the value I expect.
UPDATE:
Based on HansPrassant's input, I tried more precise datetime value of 2/19/2018 8:17:21 0000001 AM (adding 1 second and 1 nanosecond?), and this resulted in value 131635198410000001 so I believe this is working and correct.
This works... Dim my64bitDate = DateTime.Parse("#2/19/2018 8:17:20 AM#").ToFileTime
Originally, I had been using a value from Active Directory property lastLogonTimestamp that contained more precise value. This precision is not required to use as a filter on the property. A good thing because it is lost during the above parse.
If the original question, and comments don't clear things up, ask a question in the comments below - and I will do my best to answer. Thanks.

VB.NET Boxing weirdness

I can't understand what is happening with the following code in VB.NET. When I run this code:
Public Function test() As Boolean
Dim a As Integer = 1
Dim b As Object = a
Dim c As Object = b
Return Object.ReferenceEquals(b, c)
End Function
Then the function returns True. However, if I run this:
Structure TTest
Dim i As Integer
Dim tid As Integer
Sub New(ByVal _i As Integer, ByVal _tid As Integer)
i = _i
tid = _tid
End Sub
End Structure
Public Function test_2() As Boolean
Dim a As New TTest(1, 1)
Dim b As Object = a
Dim c As Object = b
Return Object.ReferenceEquals(b, c)
End Function
Then it returns False. In both functions, I declare two value type variables, an Integer on the first and a custom Structure on the second one. Both should be boxed upon object assignment, but in the second example, it seems to get boxed into two different objects, so Object.ReferenceEquals returns False.
Why does it work this way?
For primitive types, .Net is able to re-use the same "box" for the same values, and thus improve performance by reducing allocations.
Same with strings, it's .NET way to optimize thing. But as soon as you use it, the reference will change.
Sub Main()
Dim a As String = "abc"
Dim b As String = "abc"
Console.WriteLine(Object.ReferenceEquals(a, b)) ' True
b = "123"
Console.WriteLine(Object.ReferenceEquals(a, b)) ' False
Console.ReadLine()
End Sub

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.

vb.net function branching based on optional parameters performance

So I was coding a string search function and ended up with 4 since they needed to go forwards or backwards or be inclusive or exclusive. Then I needed even more functionality like ignoring certain specific things and blah blah.. I figured it would be easier to make a slightly bigger function with optional boolean parameters than to maintain the 8+ functions that would otherwise be required.
Since this is the main workhorse function though, performance is important so I devised a simple test to get a sense of how much I would lose from doing this. The code is as follows:
main window:
Private Sub testbutton_Click(sender As Object, e As RoutedEventArgs) Handles testbutton.Click
Dim rand As New Random
Dim ret As Integer
Dim count As Integer = 100000000
Dim t As Integer = Environment.TickCount
For i = 0 To count
ret = superfunction(rand.Next, False)
Next
t = Environment.TickCount - t
Dim t2 As Integer = Environment.TickCount
For i = 0 To count
ret = simplefunctionNeg(rand.Next)
Next
t2 = Environment.TickCount - t2
MsgBox(t & " " & t2)
End Sub
The functions:
Public Module testoptionality
Public Function superfunction(a As Integer, Optional b As Boolean = False) As Integer
If b Then
Return a
Else
Return -a
End If
End Function
Public Function simpleFunctionPos(a As Integer)
Return a
End Function
Public Function simplefunctionNeg(a As Integer)
Return -a
End Function
End Module
So pretty much as simple as it gets. The weird part is that the superfunction is consistently twice faster than either of the simple functions (my test results are "1076 2122"). This makes no sense.. I tried looking for what i might have done wrong but I cant see it. Can anybody explain this?
You didn't set a return type for simple function. So they return Object type.
So when you using simpleFunctionNeg function application convert Integer to Object type when returning value, and then back from Object to Integer when assigning returning value to your variable
After setting return value to Integer simpleFunctionNeg was little bid faster then superfunction

Make An Integer Null

I have an update function that updates an sql server db table through a dataset. One of the fields in the table is an integer and accepts null values. So when I am populating the update function I need a way to enter a null in when the function wants an integer.
I tried to do it this way but _intDLocation = "" throws an exception
Dim _dLocation As String = udDefaultLocationTextEdit.Text
Dim _intDLocation As Integer
If _dLocation <> "" Then
_intDLocation = Integer.Parse(udDefaultLocationTextEdit.Text)
Else
'NEED HELP HERE
_intDLocation = ""
End If
Integers cannot be set to Null. You have to make the integer "nullable" by adding a question mark after the word Integer. Now _intDLocation is no longer a normal integer. It is an instance of Nullable(Of Integer).
Dim _dLocation As String = udDefaultLocationTextEdit.Text
Dim _intDLocation As Integer?
If _dLocation <> "" Then
_intDLocation = Integer.Parse(udDefaultLocationTextEdit.Text)
Else
_intDLocation = Nothing
End If
Later on, if you want to check for null you can use this handy, readable syntax:
If _intDLocation.HasValue Then
DoSomething()
End If
In some cases you will need to access the value as an actual integer, not a nullable integer. For those cases, you simply access
_intDLocation.Value
Read all about Nullable here.
Try this:
Dim _dLocation As String = udDefaultLocationTextEdit.Text
Dim _intDLocation As Nullable(Of Integer)
If Not String.IsNullOrEmpty(_dLocation) Then
_intDLocation = Integer.Parse(_dLocation)
End If
My application uses a lot of labels that start out blank (Text property), but need to be incremented as integers, so I made this handy function:
Public Shared Function Nullinator(ByVal CheckVal As String) As Integer
' Receives a string and returns an integer (zero if Null or Empty or original value)
If String.IsNullOrEmpty(CheckVal) Then
Return 0
Else
Return CheckVal
End If
End Function
This is typical example of how it would be used:
Dim Match_Innings As Integer = Nullinator(Me.TotalInnings.Text)
Enjoy!
_intDLocation = Nothing