Instance variables and local variables confusion - vb.net

Please take a look at sample1 below:
Public Class LocalVariable
Public Sub Run()
Dim TestVariable As Integer
TestVariable = Method1(TestVariable)
TestVariable = Method2(TestVariable)
TestVariable = Method3(TestVariable)
End Sub
Private Function Method1(ByVal x As Integer) As Integer
Return x + 1
End Function
Private Function Method2(ByVal x As Integer) As Integer
Return x + 2
End Function
Private Function Method3(ByVal x As Integer) As Integer
Return x + 3
End Function
End Class
and sample 2 below:
Public Class InstanceVariable
Dim TestVariable As Integer
Public Sub Run()
Method1()
Method2()
Method3()
End Sub
Private Sub Method1()
TestVariable = TestVariable + 1
End Sub
Private Sub Method2()
TestVariable = TestVariable + 2
End Sub
Private Sub Method3()
TestVariable = TestVariable + 3
End Sub
End Class
The outcome is obviously the same after each program runs i.e. TestVariable=6. Every example I find online and at work uses sample 1. Surely this is a misuse of instance variable as TestVariable should be shared across functions? Therefore an instance variable should be used.

The two samples don't mean the same thing.
The difference is what happens if you call Run() more than once over the life of the program. The Run() method in sample 2 never resets TestVariable, so it will continue to get larger and larger. In sample 1, the result will always be 6 because TestVariable is a new variable with each call to the function. Which is more correct depends entirely on what you're trying to do.
There is a third option
All else being equal, I also recommend the sample 1 approach from those two options. However, instance vs local variable is not the distinction. There's no reason sample 1 couldn't also use an instance variable with those method definitions. So our third option would look like this:
Public Class InstanceVariableWithSampleOneFunctions
Dim TestVariable As Integer
Public Sub Run()
TestVariable = Method1(TestVariable)
TestVariable = Method2(TestVariable)
TestVariable = Method3(TestVariable)
End Sub
Private Function Method1(ByVal x As Integer) As Integer
Return x + 1
End Function
Private Function Method2(ByVal x As Integer) As Integer
Return x + 2
End Function
Private Function Method3(ByVal x As Integer) As Integer
Return x + 3
End Function
End Class
This uses the instance variable from sample 2 with the methods from sample 1. I'll call it sample 3.
This cuts better to the heart of your question, because now sample 3 has the same behavior as sample 2. Whether you should choose 1 or 2 depends on which behavior you need. But whether you should choose 2 or 3 depends on the merits of the coding style. Both 2 and 3 rely on an instance variable in the Run() method, but 2 also uses an instance variable in the additional methods, while 3 uses a local variable.
I can say that at this point, comparing 2 and 3, I definitely prefer sample 3. The methods from sample 3 have more of a functional style: accept an input, return an output. This gives them a higher level of abstraction, which makes it easier to refactor sample 3 to do things like move those methods elsewhere... say, to a utility class where one set of methods can be shared with both samples 1 and 3. Since you mentioned threading, typically this style makes it easier, not harder, to do multi-threading correctly.
One concrete example how this method style is better is that it's composable. This attribute allows me to re-write sample 3's Run() method like this and be confident of getting the same results:
Public Sub Run()
TestVariable = Method3(Method2(Method1(TestVariable)))
End Sub

Related

Why do some classes have an "I" in front of their name?

I'm working with some legacy code in Visual Basic 98, and there are several classes with an "I" in front of their name. Most of the classes don't have this name, however.
Here's the contents of the IXMLSerializable.cls file.
' Serialization XML:
' Create and receive complete split data through XML node
Public Property Let SerializationXML(ByVal p_sXML As String)
End Property
Public Property Get SerializationXML() As String
End Property
Public Property Get SerializationXMLElement() As IXMLDOMElement
End Property
Note that VBA supports interfaces, just as C#/VB.NET do (almost). Interfaces are the only way to provide inheritance mechanisms in VBA.
By convention interfaces start their name with the capital letter I.
Here is an example interface declaration that states an object must define a name property
[File: IHasName.cls, Instancing: PublicNotCreatable]
Option Explicit
Public Property Get Name() As String
End Property
As you can see there is no implementation required.
Now to create an object that uses the interface to advertise that it contains a name property. Of course, the point is that there are multiple classes that use the one interface.
[File: Person.cls, Instancing: Private]
Option Explicit
Implements IHasName
Private m_name As String
Private Sub Class_Initialize()
m_name = "<Empty>"
End Sub
' Local property
Public Property Get Name() as String
Name = m_name
End Property
Public Property Let Name(ByVal x As String)
m_name = x
End Property
' This is the interface implementation that relies on local the property `Name`
Private Property Get IHasName_Name() As String
IHasName_Name = Name
End Property
As a convenience in the UI once you include the Implements statement you can choose the interface properties from the top
And to consume the above code use the following test, which calls a function that can take any object that implements IHasName.
[File: Module1.bas]
Option Explicit
Public Sub TestInterface()
Dim target As New Person
target.Name = "John"
GenReport target
' This prints the name "John".
End Sub
Public Function GenReport(ByVal obj As IHasName)
Debug.Print obj.Name
End Function
The I stands for Interface, like specified in the Microsoft Official Documentation:
IXMLDOMElement Members.
The following tables show the properties, methods, and events.
In C++, this interface inherits from IXMLDOMNode.
That was a pretty common convention and by doing so, you immediately know that it represent an Interface, without looking at the code.
Hope this helps.
I stands for interface. VBA and older Visual Basic dialects up to VB 6.0 are said to be object oriented but a have a very poor support for it. For example, there is no class inheritance. Nevertheless, you can declare and implement interfaces in VBA/VB6; however, there is no Interface keyword as there is a Class keyword. Instead, you just declare a class with empty Subs, Functions and Properties.
Example. In a Class named IComparable, declare a Function CompareTo:
Public Function CompareTo(ByVal other As Object) As Long
'Must return -1, 0 or +1, if current object is less than, equal to or greater than obj.
'Must be empty here.
End Function
Now you can declare classes that implement this interface. E.g. a Class named clsDocument:
Implements IComparer
public Name as String
Private Function IComparable_CompareTo(other As Variant) As Long
IComparable_CompareTo = StrComp(Name, other.Name, vbTextCompare)
End Function
Now, this lets you create search and sorting algorithms that you can apply to different class types that implement this method. Example of a class called Document
Option Explicit
Implements IComparable
Public Name As String
Public FileDate As Date
Public Function IComparable_CompareTo(ByVal other As Object) As Long
Dim doc As Document, comp As Long
Set doc = other
comp = StrComp(Me.Name, doc.Name, vbTextCompare)
If comp = 0 Then
If Me.FileDate < doc.FileDate Then
IComparable_CompareTo = -1
ElseIf Me.FileDate > doc.FileDate Then
IComparable_CompareTo = + 1
Else
IComparable_CompareTo = 0
End If
Else
IComparable_CompareTo = comp
End If
End Function
Here an example of a QuickSort for VBA. It assumes that you pass it an array of IComparables:
Public Sub QuickSort(ByRef a() As IComparable)
'Sorts a unidimensional array of IComparable's in ascending order very quickly.
Dim l As Long, u As Long
l = LBound(a)
u = UBound(a)
If u > l Then
QS a, l, u
End If
End Sub
Private Sub QS(ByRef a() As IComparable, ByVal Low As Long, ByVal HI As Long)
'Very fast sort: n Log n comparisons
Dim i As Long, j As Long, w As IComparable, x As IComparable
i = Low: j = HI
Set x = a((Low + HI) \ 2)
Do
While a(i).CompareTo(x) = -1: i = i + 1: Wend
While a(j).CompareTo(x) = 1: j = j - 1: Wend
If i <= j Then
Set w = a(i): Set a(i) = a(j): Set a(j) = w
i = i + 1: j = j - 1
End If
Loop Until i > j
If Low < j Then QS a, Low, j
If HI > i Then QS a, i, HI
End Sub

VBA call class property from the class

In a VBA class module (let's say in Excel or Access), I wrote a function SomeFunction() returning a value.
If I call this from another function/sub in the class, should I call it:
a) this way: myVar = SomeFunction or
b) this way: myVar = Me.SomeFunction ?
I think both work, so except for the writing style and clarifying SomeFunction is part of the class, does it make any difference?
Both are indeed valid, but way B should be preferred since it's more explicit what you're doing.
Consider the following (valid) class code:
Public Property Get SomeValue() As Integer
SomeValue = 5
End Property
Public Property Get AnotherValue() As Integer
Dim SomeValue As Integer
SomeValue = 3
AnotherValue = SomeValue 'Returns 3
Debug.Print Me.SomeValue 'Returns 5
End Property
Because you can (but shouldn't) do this in VBA, it's a good practice to use Me. to make it clear you're using a class property and not a variable.
As far as I know - It does not make any difference.
However, if you use Me. in the class, you can use the Intellisense to see the available subs, functions and properties, which could be a bit handy:
However, I prefer not to use the Me.
If you are having the following in a module:
Public Function Foo()
Foo = 5
End Function
Sub TestMe()
Dim cls As New Klasse1
cls.TestMe
End Sub
And then the following in Klasse1:
Sub TestMe()
Debug.Print Modul1.Foo
Debug.Print Me.Foo
Debug.Print Foo
End Sub
Function Foo()
Foo = 10
End Function
it is visible that the existense of Me. is just syntax sugar.

Class variable as the counter of a For loop in VBA

I have a class module called MyClass, with a public integer in it:
Public i as Integer
When I try to use this variable in a For loop like so:
Dim MyInstance as MyClass: Set MyInstance = New MyClass
For MyInstance.i = 1 To 10
Debug.Print "Hello"
Next
I get the error: Variable required. Can't assign to this expression
I have consulted the help page but cannot see how it applies to my case. The relevant fragment is: "You tried to use a nonvariable as a loop counter in a For...Next construction. Use a variable as the counter." But i is a variable after all, and not a Let Property function or any other expression.
What is wrong with the code?
EDIT: I should point out that the reason I want my iterator to be part of the class is that I have multiple instances of the class, serving different purposes in my project, and there are multiple nested For loops for each instance of the class. Therefore it is worth having the iterators belong to their respective objects, say:
For Client.i = 1 To Client.Count
For Order.i = 1 To Order.Count
For Item.i = 1 To Item.Count
etc.
I have settled for the following workaround but am still not entirely satisfied with it:
For ciii = 1 To Client.Count
Client.i = ciii ' Client.i is later used in the code
For oiii = 1 To Order.Count
Order.i = oiii
For iiii = 1 To Item.Count
Item.i = iiii
You cannot use MyInstance.i as the increment counter but you can use it as the terminator; e.g. For i = 1 To MyInstance.i.
MyClass class
Option Explicit
Public pi As Long
Public Property Get i() As Long
i = pi
End Property
Public Property Let i(Value As Long)
pi = Value
End Property
test sub procedure in Module1
Sub test()
Dim MyInstance As MyClass, i As Long
Set MyInstance = New MyClass
MyInstance.i = 10
For i = 1 To MyInstance.i
Debug.Print "Hello"
Next
End Sub
If you want a publicly accessible loop variable stick it at the top of a standard module i.e. declare the Public i at the top of a standard module.
Note that this would mean you need to re-write your standard module code as, as per point two, you are treating i as if it is a property/method of the class.
So, standard module code would be:
Public i As Long
Sub ........
For i = 1 To 10
Debug.Print "Hello"
Next i
End Sub ......
If you want it to somehow be a property/method then you need to define Getters and Setters (potentially) in the class. And then re-write your module code accordingly. Especially if you are planning on looping using i, you will need an incrementor method in the class.
And yes, I have changed i to Long as there are no advantages, in this case I believe, of having it declared as Integer. A Long is a safer bet for avoiding potential overflow.
If you need a workaround so that you iterate through a property of the instance, you could create a method to increment it, change your loop to a Do While ... Loop and call that method before the loop call.
'Class Module
Option Explicit
Public i As Integer
Public Sub increment_i()
i = i + 1
End Sub
Private Sub Class_Initialize()
i = 0
End Sub
'Module
Sub loop_myclass()
Dim instance As MyClass: Set instance = New MyClass
Do While instance.i <= 10
'Instance property dependent code here
Debug.Print instance.i
instance.increment_i
Loop
End Sub
OK, I found the answer. There is a Microsoft help page on For…Next loop regarding VB, but I think it pertains to VBA as well.
It says:
If the scope of counter isn't local to the procedure, a compile-time
warning occurs.
So there's not much to discuss here, it's just the way MS wants it to be. Though I'd think that if the scope is greater than the procedure it shouldn't cause any problems, but apparently it does.

Naming global variables in a structured way

My current project has global constants that define certain rows and columns in workbooks that this project will be searching through. I have defined them as such:
Public Const headRow As Integer = 1
Public Const descRow As Integer = 2
Public Const pnumCol As Integer = 1
Public Const teamCol As Integer = 2
Public Const dateCol As Integer = 3
Public Const hourCol As Integer = 4
Public Const typeCol As Integer = 5
Public Const taskCol As Integer = 6
Public Const noteCol As Integer = 7
I'm wondering if there is a cleaner way to define these that would allow me to write these in a way such as:
ColumnNums.team
ColumnNums.task
ColumnNums.note 'etc
I think something similar to this could be done by defining my own type, but that would probably not be worthwhile. I'm basically wanting this to be an easy way to remember the variable names as I write more code, as well as to be able to count how many items I have in each group. Would a Type or Collection be useful in this case?
For mixed variable types, you can put it in a class module, name the class module ColumnNumbers and put the following code in:
Public Property Get Team() As Long
Team = 1
End Property
Public Property Get TeamName() As String
TeamName = "Team One! :-)"
End Property
Then you can use it in any module like this:
Dim colNums As New ColumnNumbers
Sub foo()
MsgBox colNums.Team
End Sub
If you only want to return long values, put it in an enum:
Enum ColumnNumbers
Team = 1
Description = 2
End Enum
Sub foo()
MsgBox ColumnNumbers.Team
End Sub
Chip pearson has already done a fantastic job of describing enums here it's worth a read if you have yet to discover them.
You could use public arrays like this:
Public ColumnNum(0 To 2) As Long
Public RowNum(0 To 2) As Long
Used together with an enum:
Public Enum Category
team
task
note 'etc.
End Enum
Then things like ColumnNum(team) will function like a public variable:
Sub test1()
ColumnNum(team) = 5
End Sub
Sub test2()
Debug.Print ColumnNum(team)
End Sub
If these two subs are run in order than 5 is printed.

function that call itself in vba

this is the code that I got at the end with the help of Latch, this would be an easy example of a subrutine that changes values in global collection in a recursive way, and now is completly funtional
Public List_of_data As New Collection
Sub main()
ClearCollection List_of_data
List_of_data.Add 7.5
List_of_data.Add 1
Recursive_function List_of_data
End Sub
Public Sub ClearCollection(parmCol As Collection)
Set parmCol = New Collection
End Sub
Public Function Recursive_function(ByRef List_of_data As Collection)
Dim x1
x1 = List_of_data.Item(1)
Dim x2
x2 = List_of_data.Item(2)
If x1 > x2 Then
ClearCollection List_of_data
List_of_data.Add x1
List_of_data.Add x2 + 1
Call Recursive_function(List_of_data)
End If
End Function
You are passing an argument to a function that takes none. Changing your function to:
Public Function Calculo_de_Dientes_Epicicloidales(byref lista_de_datos)
will yeild the result you are looking for.
Also, instead of looping to empty your collection, you could simply do:
Public Sub ClearCollection(parmCol As Collection)
set parmCol = new collection
End Sub
You cannot "call" a function. A function provides a result; you need to set some variable equal to your function. If you just need the code to run, and don't need it to provide you with a specific variable result, then make it a sub, and not a function.
Also, do you have "End Function" below the last line you have copied for us?