System.StackOverflowException intersection error - vb.net

I want my program to take a variable, and find letters A-Z. I have made this section of my program in a module to be shared between 2 different forms.
variables are passed from form1 and are processed by the module and then sent back again to form1. the problem is I think some sort of bug in the code but I cant identify it.
Public Function UPCASES(ByRef password1, points) As Boolean
Dim intersection As IEnumerable(Of Char)
intersection = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Intersect(password1)
'System.StackOverflowException error ^^^^^^^^^^^^^^^^^^^^^^^^^
If intersection.Count() = 1 Then
points = 5
Else
points = 0
End If
Return UPCASES(password1, points)
End Function

You are calling the method itself at the method end, that causes the StackOverflowException:
Return UPCASES(password1, points)
I guess this method should check if the password contains uppercase letters, then use:
Dim containsUpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Intersect(password1).Any()
So no need to create a method just for this one-liner, if you need a method:
Public Function ContainsUpperCaseLetter(password1 As String) As Boolean
Return "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Intersect(password1).Any()
End Function
Side-note: you should change your default project settings to use Option Strict ON(Off is default unfortunately). Then you will be able to write much more safe, robust and performant code after you have learned a lot about .NET types because you have to fix the compiler errors.

Related

Circular Dependency Error when using Enumerations

I want to assign certain time weightage values to the variables of a data set. So I am trying to use Enumerations/Constants so to improve my code quality and make it easy to maintain. But on compiling my project, it throws in a Circular Dependencies Between Modules error.
What I have understood is, one is allowed to use only constants [Eg: 2,3.14,56....], in the truest sense of the word. Bottom line, if I can't use Either Enumerations or Constants. Then how can I achieve my objective of keeping my weightages code-block, in a way that any changes in them is reflected everywhere in my project than me having to do a find and update manually every instance.
What I am getting at, is to have a global variable that can be accessed throughout the project and is dynamic.
Private Const Wtage As Double = ConvTohr(34) 'Error happens here
Enum Weightage
Var1_Weightage = ConvTohr(3) 'Error happens here
Var2_Weightage = ConvTohr(11)
Var3_Weightage = ConvTohr(2)
var4_Weightage = ConvTohr(9)
var5_Weightage = ConvTohr(0)
End Enum
Private Function ConvTohr(val As Integer) As Double
If val = 0 Then
ConvTohr = 0
Exit Function
End If
ConvTohr = Round((val / 60), 2)
End Function
The error message is incorrect: your code does not have any circular references.
This is more of a bug in the VBA interpreter: your code is still incorrect and invalid, but VBA is showing the wrong error message.
Given that VBA remains effectively frozen-in-time since 2002 (excepting 64-bit support in 2007), so don't expect any fixes, let alone any enhancements, ever (Though MS Office's COM automation tooling is slowly shifting to JavaScript (maybe Python? please?).
The actual problem with your code is threefold:
You cannot use a Function to initialize a Const value in VBA.
You cannot use a Function to define values for a VBA Enum either.
You cannot have Enum members typed as Double: VBA only supports Integer and Long values for Enum members.
Curiously, VBA does allow Const values to be typed as Double - but most other languages don't because Double is an IEEE-754 floating point type that does not have a machine-portable representation, but as VBA is only for Win32 on x86/x64 I guess that means Microsoft made it work given the very narrow gamut of hardware that VBA programs will run on.
Anyway, if you want "named values" typed as Double that you can use anywhere, then try this:
Create a new Module (not a Class Module).
Rename the Module from Module1 to Weightage.
Put this code in Weightage:
Private Function ConvTohr(val As Integer) As Double
ConvTohr = Round((val / 60), 2)
End Function
Public Property Get Var1_Weightage() As Double
Var1_Weightage = ConvTohr(3)
End Property
Public Property Get Var2_Weightage() As Double
Var2_Weightage = ConvTohr(11)
End Property
Public Property Get Var3_Weightage() As Double
Var3_Weightage = ConvTohr(2)
End Property
Public Property Get Var4_Weightage() As Double
Var4_Weightage = ConvTohr(9)
End Property
Public Property Get Var5_Weightage() As Double
Var5_Weightage = ConvTohr(0)
End Property
Screenshot proof:
(See output in the Immediate pane):

String Function with No Paramaters Invoked with Int Should Fail Build

We have a VB.NET project which I am doing some refactoring in. A lot of current functions require a userId to be passed in as a parameter. For example:
Private Function Foo(userId As Integer) As String
Return "Foo"
End Function
But now, I have made this userId paramater no longer needed in much of our code.
I was thinking I would just remove all the userId parameters from all of the functions that don't need them anymore, and try re-building the project to see what calling code needs to change. To my surprise though, the code built successfully.
I learned that you can call a function which returns a string and doesn't take any parameters, but still pass in an Integer value. VB.NET builds/executes this code as if you were trying to invoke the function and then get the character from the String at the specified index. For example:
Imports System
Public Module MyModule
Private Function Foo() As String
Return "Foo"
End Function
Public Sub Main()
Console.WriteLine(Foo(0)) ' prints 'F'
Console.WriteLine(Foo(1)) ' prints 'o'
End Sub
End Module
(.NET Fiddle)
I want my build to fail in this case. I have tried using Option Strict On and Option Explicit On but neither seem to change this behaviour.
Is there anyway to make this kind of function invocation invalid in my VB.NET project?
Create an overload with no parameters which is a copy of the original function in every other respect.
Public Function Foo() As String
Return "Foo"
End Function
Now change the type of userId to something else (not Object or anything numeric etc.)
Public Function Foo(userId As DateTime) As String
Return "Foo"
End Function
Since there is an overload with a parameter, the compiler thinks you mean to call that, instead of indexing the chararray. So you have compile errors. Fix the errors
Then delete the original function.

Sorting a SortedDictionary by key length in Visual Basic?

I'm writing a script that anonymizes participant data from a file.
Basically, I have:
A folder of plaintext participant data (sometimes CSV, sometimes XML, sometimes TXT)
A file of known usernames and accompanying anonymous IDs (e.g. jsmith1 as a known username, User123 as an anonymous ID)
I want to replace every instance of the known username with the corresponding anonymous ID.
Generally speaking, what I have works just fine -- it loads in the usernames and anonymous IDs into a dictionary and one by one runs a find-and-replace on the document text for each.
However, this script also strips out names, and it runs into some difficulty when it encounters names contained in other names. So, for example, I have two pairs:
John,User123
Johnny,User456
Now, when I run the find-and-replace, it may first encounter John, and as a result it replaces Johnny with User123ny, and then doesn't trigger Johnny.
The simplest solution I can think of is just to run the find-and-replace from longest key to shortest. To do that, it looks like I need a SortedDictionary.
However, I can't seem to convince Visual Basic to take my custom Comparer for this. How do you specify this? What I have is:
Sub Main()
Dim nameDict As New SortedDictionary(Of String, String)(AddressOf SortKeyByLength)
End Sub
Public Function SortKeyByLength(key1 As String, key2 As String) As Integer
If key1.Length > key2.Length Then
Return 1
ElseIf key1.Length < key2.Length Then
Return -1
Else
Return 0
End If
End Function
(The full details above are in case anyone has any better ideas for how to resolve this problem in general.)
I think it takes a class that implements the IComparer interface, so you'd want something like:
Public Class ByLengthComparer
Implements IComparer(Of String)
Public Function Compare(key1 As String, key2 As String) As Integer Implements IComparer(Of String).Compare
If key1.Length > key2.Length Then
Return 1
ElseIf key1.Length < key2.Length Then
Return -1
Else
'[edit: in response to comments below]
'Return 0
Return key1.Compare(key2)
End If
End Function
End Class
Then, inside your main method, you'd call it like this:
Dim nameDict As New SortedDictionary(Of String, String)(New ByLengthComparer())
You might want to take a look (or a relook) at the documentation for the SortedDictionary constructor, and how to make a class that implements IComparer.

Integer in VB.net turns into String in VBA

I have a list of objects that are "keys" into a table - the keys are simply the items in the first column. They can be Integers or Strings, depending on what DB table we read it from. Since we use them a lot, we cache that column in an ArrayList called "Keys".
We wrote cover methods to return Row, one that uses strings and the other integers. If you call the integer version it simply returns that row by index. If you call the string, it looks down Keys for a match, and uses that index to pull out the row.
Ok, so now I pass Keys to Excel, pull out one of them in a loop, and ask it what it is...
TypeName(DB.Keys(i))
And the object returns...
Long
Great, the keys must have been integers! So now I'll try to get the row for that key, by calling the accessor method, Row...
DB.Row(DB.Keys(i))
And it calls into the version that takes a String.
Whoa!
Can anyone think of a reason that VBA calling back into our VB.net DLL ends up calling the wrong accessor?
ADDED CODE: I can't figure out how to put the code in as a reply, so I'm editing this. Here is the code in the VB.net class:
Public Function Row(ByVal K As String) As DataRow
Dim R As DtaRow = DB.Tables(0).Rows(K)
Return New DataRow(R)
End Function
Public Function Accounts(ByVal K As Integer) As DataRow
Dim R As DtaRow = DB.Tables(0).Rows(K)
Return New DataRow(R)
End Function
If you're wondering, there's two versions of Rows, which take strings or ints.
This code works perfectly from VB.net. You can ask for a row by key string or by the row number, that invokes the proper Row, which calls the proper Rows, and out comes the proper answer.
But in VBA, it always calls the method with the string input. If I rename it to RowIHATEYOUALL then I can call the Integer version just fine. But when there are two of them, differing only in signature, no such luck.
And the A and i (see comments) was my typo.
The interop layer does not support overloaded methods. Whenever you call Row, the first declared method with that name is used. The .NET overload resolution algorithm does not apply here.
Other overloads are exposed to VBA as Row_2, Row_3, etc. Thus, the following code should do what you expect:
DB.Row_2(DB.Keys(i))
This implicit dependency on the order of declaration has a high potential for error. Thus, I would suggest to either
give the methods unique names if they are called from VBA,
or, if you want to retain the "nice" overloaded version for .NET, add compatibility methods for VBA:
<Obsolete("Compatibility method for VBA, use Row instead.")>
Public Function RowByKeyVBA(ByVal K As String) As DataRow
Return Row(K)
End Function
<Obsolete("Compatibility method for VBA, use Row instead.")>
Public Function RowByNumberVBA(ByVal K As Integer) As DataRow
Return Row(K)
End Function
Further information can be found in the following question:
COM->.NET - can't access overloaded method
Following Heinzi's notes (above) I fixed this by making three method signatures for each call, one takes an Object and then attempts to figure out what it is, the others take the String and Integers. Within VB/C#/etc the proper String or Integer methods get called as expected, from VBA the Object version is called, as Heinzi noted
This causes the very minor issue that a user may have a "number like value" that is actually a String. For instance, the array keys might be "User3232" or "3232", both of which are String objects in the table. So you have to be careful, simply asking if the Object can be converted to an Int32 is not enough. This is unlikely to be something that effects most users.

Why is it legal to pass "Me" ByRef in VB.NET?

I was shocked just a moment ago to discover that the following is legal (the C# equivalent is definitely not):
Class Assigner
''// Ignore this for now.
Public Field As Integer
''// This part is not so weird... take another instance ByRef,
''// assign it to a different instance -- stupid but whatever. '
Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
''// But... what's this?!?
Sub AssignNew()
''// Passing "Me" ByRef???
Assign(Me, New Assigner)
End Sub
''// This is just for testing.
Function GetField() As Integer
Return Me.Field
End Function
End Class
But what's even stranger just as strange to me is that it doesn't seem to do what I expect:
Dim a As New Assigner With {.Field = 10}
a.AssignNew()
Console.WriteLine(a.GetField())
The above outputs "10," not "0" like I thought it would (though naturally, this expectation was itself infused with a certain kind of horror). So it seems that you can pass Me ByRef, but the behavior is somehow overridden (?) by the compiler to be as if you had passed Me ByVal.
Why is it legal to pass Me ByRef? (Is there some backwards-compatibility explanation?)
Am I correct in saying that the behavior of doing this is overridden by the compiler? If not, what am I missing?
This behavior actually follows pretty directly from the Visual Basic specification.
11.4.3 Instance Expressions
An instance expression is the keyword Me, MyClass, or MyBase. An instance expression, which may only be used within the body of a non-shared method, constructor, or property accessor, is classified as a value.
9.2.5.2 Reference Parameters
If the type of the variable being passed to a reference parameter is not compatible with the reference parameter's type, or if a non-variable is passed as an argument to a reference parameter, a temporary variable may be allocated and passed to the reference parameter. The value being passed in will be copied into this temporary variable before the method is invoked and will be copied back to the original variable (if there is one) when the method returns.
(All emphasis mine)
So, the compiler will create a temporary variable assigned to the value of Me to be passed as the ByRef parameter. Upon return, no copy of the resulting value will take place since Me is not a variable.
It appears the compiler transforms "Me" into a variable which is then passed ByRef. If you compile your code, then open it with Reflector, you can see what's happening:
Class Assigner
''// Methods
Public Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
Public Sub AssignNew()
Dim VB$t_ref$S0 As Assigner = Me
Me.Assign((VB$t_ref$S0), New Assigner)
End Sub
Public Function GetField() As Integer
Return Me.Field
End Function
''// Fields
Public Field As Integer
End Class
So it looks like when you call AssignNew(), you are assigning the new instance to the internally generated variable. The "a" variable doesn't get touched because it's not even a part of the function.
This is just one of the thousands of possible 'almost errors' a programmer can make. MS caught most of them, in fact, sometimes I'm suprised at how many warnings do come up.
they missed this one.
As far as why it doesn't change 'me', it's a darn good thing! When you use 'me', it just passes a copy of the real class you are working with, for safety purposes. If this worked they way you were hoping, we would be talking GIANT side-effect. You're innocently working away with in your class' methods, and them BAM all of a sudden you are in an ENTIRELY different object! That would be awful! If you're going to do that, you might as well just write a piece of spagetti MS-Basic line-numbered code with all globals that get randomly set, and no subs/functions.
The way this works is the same way if you pass arguments in parenthesis. For example this works as expected:
Assign(Reference_I_Want_To_Set, New Assigner)
But this doesn't change anything:
Assign((Reference_I_Want_To_Set), New Assigner)
If you reflect the above type of code as adam101 suggests you will see similar results. While that is huge frustration with the parenthesis, it is a very good thing with Me !!!
what you need to do to make this code work is this:
Class Assigner
''// Ignore this for now.
Private newPropertyValue As Integer
Public Property NewProperty() As Integer
Get
Return newPropertyValue
End Get
Set(ByVal value As Integer)
newPropertyValue = value
End Set
End Property
''// This part is not so weird... take another instance ByRef,
''// assign it to a different instance -- stupid but whatever. '
Shared Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
''// But... what's this?!?
Shared Sub AssignNew(ByRef x As Assigner)
''// Passing "Me" ByRef???
Assign(x, New Assigner)
End Sub
End Class
then use it like
Dim a As New Assigner With {.NewProperty = 10}
Assigner.AssignNew(a)
my understanding is you cannot change the reference of the object while using it, so you need to change it in a shared sub
since Me cannot be the target of an assignment, the code seem to create a copy of it and from that point on, your not using the real object, but a copy of it