VB.Net access the same member on different objects - vb.net

Often I have to write structures where logic is performed on each member variable for a class. For example, in this code I am finding anything that has changed between the object and another object, then returning the changes:
Public Class Foo
Public B1 As Boolean?
Public B2 As Boolean?
...
Public B1000 As Boolean?
Function GetChanges(F as Foo) As Foo
Dim Changes As New Foo()
If Not B1.Equals(F.B1) Then
B1 = F.B1
Changes.B1 = F.B1
End If
If Not B2.Equals(F.B2) Then
B2 = F.B2
Changes.B2 = F.B2
End If
...
If Not B1000.Equals(F.B1000) Then
B1000 = F.B100
Changes.B1000 = F.B1000
End If
Return Changes
End Function
End Class
As you can see, the same "If Not X.Equals(F.X) ..." has to be copy pasted many many times.
Here is a second example where I am taking the changes calculated before, and using them to update the value of the Foo object.
Sub Update(Changes as Foo)
If Changes.B1.HasValue Then
B1 = Changes.B1
End If
If Changes.B2.HasValue Then
B2 = Changes.B2
End If
...
If Changes.B1000.HasValue Then
B1000 = Changes.B1000
End If
End Sub
This kind of structure isn't something I'm happy with. It's fragile, it's ugly, it's repetitive. Is there something I can do about it?
One option is to pass the 3 members by ref, but the problem is you still have these 3 calls to the same variable, which is still repeditive:
Public Shared Sub CheckChange(Of T)(ByRef OldV As T, NewV As T, ByRef Change As T)
If Not OldV.Equals(NewV) Then
OldV = NewV
Change = NewV
End If
End Sub
Function GetChanges(F as Foo) As Foo
Dim Changes As New Foo()
CheckChange(B1, F.B1, Changes.B1)
CheckChange(B2, F.B2, Changes.B2)
...
CheckChange(B1000, F.B1000, Changes.B1000)
return Changes
End Function
It would be great if I could do something like:
Sub CheckChanges(F as Foo, ByRef Changes as Foo, Member as ???)
If Not Member.Equals(F.Member) Then
Member = F.Member
Changes.Member = F.Member
End If
End Sub
Function GetChanges(F as Foo) As Foo
Dim Changes As New Foo()
CheckChange(F, Changes, B1)
CheckChange(F, Changes, B2)
...
CheckChange(F, Changes, B1000)
return Changes
End Function
I'm not even sure what that kind of feature would be called. Relative member referencing? Anyway, is there any kind of feature (this, or otherwise) that would make the code more robust when having to run the same logic on multiple members?

On the subject of the question, code in situations like this can often be simplified by using a bit of Reflection. I am yet to test this but I think that this method should work:
Private Sub UpdateIfNotEqual(source As Foo, changes As Foo, fieldName As String)
Dim fooType = GetType(Foo)
Dim field = fooType.GetField(fieldName)
Dim currentValue = field.GetValue(Me)
Dim sourceValue = field.GetValue(source)
If Not currentValue.Equals(sourceValue) Then
field.SetValue(Me, sourceValue)
field.SetValue(changes, sourceValue)
End If
End Sub
You'd call it like so:
UpdateIfNotEqual(F, Changes, NameOf(B1))
UpdateIfNotEqual(F, Changes, NameOf(B2))
'...
UpdateIfNotEqual(F, Changes, NameOf(B1000))
If the fields really were numbered then you could even use a loop:
For i = 1 To 1000
UpdateIfNotEqual(F, Changes, $"B{i}")
Next
The drawback is that Reflection is very slow. It may not be a big deal for this much data but only testing would tell you for sure.

Reflection
Class Foo
Public B1 As Boolean
Public B2 As Boolean
Public B3 As Boolean
Public C1 As Integer = Rnd() * 100
Function GetChanges(F As Foo) As Foo
Dim Changes As New Foo()
For Each field As System.Reflection.FieldInfo In F.GetType.GetFields.Where(Function(x) x.Name.StartsWith("B"))
If field.GetValue(F) <> Me.GetType.GetField(field.Name).GetValue(F) Then
Changes.GetType.GetField(field.Name).SetValue(Changes, field.GetValue(F))
Else
'????????
End If
Next
Return Changes
End Function
End Class
PropertyDescriptor
Imports System.ComponentModel
Class Foo
Property B1 As Integer
Property B2 As Boolean
Property Text As String
Function GetChanges(F As Foo) As Foo
Dim Changes As New Foo()
Dim pdc As PropertyDescriptorCollection = TypeDescriptor.GetProperties(F)
For Each pd As PropertyDescriptor In pdc
If pd.IsReadOnly = False Then
If pd.IsBrowsable = True Then
'add more conditions if needed
Changes.GetType.GetProperty(pd.Name).SetValue(Changes, pd.GetValue(F), Nothing)
End If
End If
Next
Return Changes
End Function
End Class

Related

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?

VBA Object module must Implement ~?

I have created two classes, one being an interface for the other. Each time I try to instantiate Transition_Model I get:
Compile error: Object Module needs to implement '~' for interface'~'
To my understanding Implementing class is supposed to have a copy of all public subs, function, & properties. So I don't understant what is the problem here?
Have seen similar questions come up but either they refer to actual Sub or they include other complications making answer too complicated for me to understand.
Also note I tried changing Subs of Transition_Model to Private and add 'IModel_' in front of sub names(Just like top answer in second question I linked) but I still receive the same error.
IModel
Option Explicit
Public Enum Model_Types
Transition
Dummy
End Enum
Property Get M_Type() As Model_Types
End Property
Sub Run(Collat As Collateral)
End Sub
Sub Set_Params(key As String, value As Variant)
End Sub
Transition_Model
Option Explicit
Implements IModel
Private Transitions As Collection
Private Loan_States As Integer
Private Sub Class_Initialize()
Set Transitions = New Collection
End Sub
Public Property Get M_Type() As Model_Types
M_Type = Transition
End Property
Public Sub Run(Collat As Collateral)
Dim A_Transition As Transition
Dim New_Balance() As Double
Dim Row As Integer
For Row = 1 To UBound(Collat.Curr_Balance)
For Each A_Transition In Transitions
If A_Transition.Begining = i Then
New_Balance = New_Balance + Collat.Curr_Balance(Row) * A_Transition.Probability
End If
Next A_Transition
Next
End Sub
Public Sub Set_Params(key As String, value As Double)
Dim Split_key(1 To 2) As String
Dim New_Transition As Transition
Split_key = Split(key, "->")
Set New_Transition = New Transition
With New_Transition
.Begining = Split_key(1)
.Ending = Split_key(2)
.Probability = value
End With
Transitions.Add New_Transition, key
End Sub
Lastly the Sub I am using to test my class
Sub Transition_Model()
Dim Tested_Class As New Transition_Model
Dim Collat As New Collateral
'Test is the model type is correct
Debug.Assert Tested_Class.M_Type = Transition
'Test if Model without transition indeed does not affect balances of its collateral
Collat.Curr_Balance(1) = 0.5
Collat.Curr_Balance(2) = 0.5
Tested_Class.Run (Collat)
Debug.Assert ( _
Collat.Curr_Balance(1) = 0.5 And _
Collat.Curr_Balance(2) = 0.5)
End Sub
Actaully Per the second question I linked has the correct answer which I missed.
All subs need to start with 'IModel_' and rest ot the name has to match the name in IModel.
AND
This is the part i missed, you cannot use underscore in the Sub name.

VBA calling class let method, compile error

Beginner to excel class modules here. I am having trouble with the basics-
When I set (let) the property, I get "Compile error: Wrong number of arguments or invalid property assessment" with the .Name property:
Sub test()
Dim acc As account
Set acc = New account
MsgBox (acc.Name("First Account").rowNum())
End Sub
And this is the "account" class module:
Private strAccName As String
Private mlngRowNum As Long
Public Property Let Name(strN As String)
strAccName = strN
End Property
Public Property Get rowNum(exists As Boolean)
dim rowNum as Long
'...some logic here...
'...
getRowNum = rowNum
End Property
So supposedly I am going wrong in the Let method? Advice greatly appreciated
you can assign a value to a property LET (for normal dataTypes) or property SET (for Object) by the equal sign, not vith parenthesis (used for method instead), or read a property GET assigning the value to another variable, like this:
acc.Name = "xyz"
MsgBox acc.Name
This might help you:
Sub test_class()
Dim acc As account
Set acc = New account
acc.Name = "First Account"
MsgBox acc.rowNum(1)
End Sub
class (account):
Private strAccName As String
Private mlngRowNum As Long
Public Property Let Name(strN As String)
strAccName = strN
End Property
Public Property Get rowNum(exists As Boolean)
'Dim rowNum As Long
'...some logic here...
'...
If exists Then
'getRowNum = rowNum
rowNum = 5
Else
rowNum = 10
End If
End Property

instantiate object ONLY from other class in VB.NET

I have 2 classes A and B (in VB.NET).
I want the only way to create an object of B class was by using a mehtod of A.
Examples:
You could do:
Dim objectA as new A
Dim objectB as B = objectA.getAobject()
BUT you couldnĀ“t do:
Dim objectB as new B
Thanks!
Edit: in "You could do" section I wanna mean "Dim objectB as B = objectA.getAobject()"
You can make a private constructor in B:
Private Sub New()
End Sub
If there are no public constructors, this will block you from writing code like this anywhere but within B itself:
Dim objectB as new B
However, this requires you to write code like that somewhere in B, or you won't be able to ever create an instance of B anywhere. Typically a Shared method is the place to do this:
Friend Shared Function Create() As B
'...
Return New B
End Function
Note the Friend access modifier. Now, if you have an assembly (class library project) containing only A and B, only code inside that assembly (only A and B) will be able to use that function. Add to that a method in A that looks like this:
Public Shared Function getObject() As B
Return B.Create()
End Function
And we've met all your stated objectives. Code that references this assembly will be able to do this:
Dim objectB as B = A.getAobject()
But will not be able to do either of these:
Dim objectB as new B
Dim objectB As B = B.Create()
You could just check in the constructor of B, if your calling class is of type A
Class B
Sub New ()
' get parent frame and calling method
Dim frame As StackFrame = new StackFrame( 1, True )
Dim method As MethodBase = frame.GetMethod()
' for debug purposes
Dim methodName As String = method.DeclaringType.Name & "->" & method.Name
Console.WriteLine(methodName)
' throw exception
If (method.DeclaringType.Equals(typeof(A)) = False) {
throw new Exception("Not called from class A!!")
End If
End Sub
End Class
Create a new Project of type Class Library and add code similar to this for Class A and Class B
Public Class A
Public Function getObject() As B
Return New B()
End Function
End Class
Public Class B
Protected Friend Sub New()
Console.WriteLine("Constructor B called")
Console.ReadLine()
End Sub
End Class
Now the Constructor of Class B could only be called from inside code present in this project not from other assemblies
In your main project you need to add a reference to this new project and Imports its namespace.
Module Module1
Sub Main()
Dim a1 = New A()
' Dim b1 = New B() 'This will not compile at all'
Dim b2 = a1.getObject()
End Sub
End Module
Of course now you have to distribute two files instead of one....
The key for this to work is the different assembly and the keyword Protected Friend applied to the constructor of class B

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