creating a shared variable with inheritance - vb.net

My question is in relation to the below question:
In VB.net, How can I access to a function in a class from an other function in a nested class?
By setting the variable h shared, are you making that variable available to all instances of the class as a single or static variable thereby creating the possibility for problems in the asker's future endeavors? Or is my understanding of VB.net skewed?
If I'm right would that mean that the code would the need to be arranged like this:
Class N
Dim h
Class n
Implements iInterface
Sub f()
h = 5
End Sub
End Class
End Class
And instead create an instance of the object to use in consuming code?

A shared variable isn't part of the instantiated object. If you write
Dim o As New N
o.h = 1
Assuming h is shared, you will get a warning. You have to call it like this.
N.h = 1
When you have code in the class itself, you don't need to specify the class name. His code is actually
Class N
Shared h = 4
Class n
Implements iInterface
Sub f()
N.h = 5
End Sub
End Class
End Class
Maybe this will help you understand it a bit more. This clearly show that each instance of n will be sharing the same h variable. Let's add a new function
Class N
Shared h = 4
Class n
Implements iInterface
Sub f()
h = 5
End Sub
Sub ff()
h = 12
End Sub
Function GetH() As Integer
Return h
End Sub
End Class
End Class
Dim o1 As New n
Dim o2 As New n
o1.f()
o2.ff()
Console.WriteLine(o1.GetH()) ' This will print 12
Console.WriteLine(o2.GetH()) ' This will print 12
I think his question didn't have enough information to indicate if the shared variable will cause problem or not.

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

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.

In VB.net, How can I access to a function in a class from an other function in a nested class?

I'm developing in VB.net but I'm come from java and I have the idea to create a anonymous class who implements an interface like this:
int h = 4;
Object x = new iInterface({
#Override void f(){
h = 5;
}
});
I didn't know how to do it, so I think to create a nested class who implments the "iInterface" but...
Class N
Dim h = 4
Class n
Implements iInterface
Sub f()
h = 5
End Sub
End Class
End Class
... VisualStudio puts a fluffy blue mat under h and says to me: "Reference to a non-shared member requires an object reference"
What should I do? >___<
Something like this:
Class N
Shared h = 4
Class n
Implements iInterface
Sub f()
h = 5
End Sub
End Class
End Class
You may be looking for shared elements. Here is the documentation for that: https://msdn.microsoft.com/en-us/library/zc2b427x.aspx
Otherwise you have to explicitly create an instance of that class in order to
use it.

Instance variables and local variables confusion

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

Calling type variables from another sub

Hi I have a series of subroutines as follows:
DataCollection() : Collects data from the spreadsheet and writes it to custom type variables.
NewSub() : Does something else, but not relevant to the question.
I would like to keep the same variables previously declared, and having values assigned in the second sub. I think I have to make them global variables somehow, but could not work it out so far, whatever I do I get the variable not defined error. My code is as follows:
Option Explicit
Public Type Trucks
NumberOfAxles As Integer
AxleWeights(15) As Double
End Type
Public Sub DataCollection()
Dim NumberOfTrucks As Integer
Truck(10) As Trucks
Dim i, j, k As Integer
'Determine Number of Trucks
NumberOfTrucks = Cells(6, 8)
'Populate Truck Arrays (Trucks 1 to 5)
k = 0
For i = 1 To 5
Truck(i).NumberOfAxles = Cells(9, 4 + 4 * k)
k = k + 1
Next i
k = 0
For i = 1 To 5
For j = 1 To Truck(i).NumberOfAxles
Truck(i).AxleWeights(j) = Cells(31 + j, 3 + 4 * k)
Next j
k = k + 1
Next i
End Sub
Public Sub NewSub()
For i = 1 To Truck(10).NumberOfAxles
Cells(27 + i, 22) = Truck(10).AxleWeights(i)
Next i
End Sub
Any ideas would be most welcome! Thanks!
Keep your variables in as limited a scope as possible.
If you call NewSub from DataCollection, then make Trucks() local to DataCollection and pass it as an argument to NewSub.
If you don't call one from the other but they are in the same module declare Trucks() as a module-level variable. To do that use the Private keyword and make the declaration at the top of the module outside of any procedures.
Finally, if NewSub is in a different module, you need to declare a global variable. Use the Public keyword and declare it in it's own module called MGlobals. Why it's own module? It's good practice to limit your use of global variables and declare them all in the same place so you can manage them more effectively. (That means move your public Type to MGlobals too.)
OK, having said all that, stop using Types now. At some point in your project, you're going to want some function that is beyond what Type can do for you. I know you don't think so, but it will happen. So you'll create a function that does it and it will become an unmanageable mess. So make a Truck class and a Trucks class. The Truck class will contain the two properties. The Trucks class will contain a private collection object that holds all the Truck instances. The only global variable you'll need is gclsTrucks. As long as that is in scope, all of your Truck instances. All of your heavy lifting should be on in the Truck class. A little extra work right now will save you big.
You can use global variables like follows.
Dim global_var As Integer
'
Sub doA()
global_var = global_var + 1
Debug.Print global_var
End Sub
Sub doB()
global_var = global_var + 10
Debug.Print global_var
End Sub
Sub main()
doA
doB
doA
End Sub
You declare your variable in
Truck(10) As Trucks
and not on
Public Type Trucks
NumberOfAxles As Integer
AxleWeights(15) As Double
End Type
In other words, just move the "Dim" to outside the routine.