How to use comparison methods between class object modules in VBA in a similar manner as VB.NET? - vb.net

Due to new project in VBA I moved from VB.NET, to be honest don't really know how to deal between objects classes here. What I want to reach is comparing objects between different class object modules.
e.g
class Employee
properties: Name, Age
point is: compare Names between two Employees
classes: Employee and Manager
point is: compare Name from Employee with Name of Manager
I know how to in VB.NET, but how do I compare properties of different class module objects in VBA?

VBA doesn't support class polymorphism so I recommend to change the way you're thinking about the Employee and Manager classes.
You can't have an Employee class as a base class and then a separate Manager class that derives from Employee. They could be 2 separate classes implementing a common interface.
I will talk about it in detail in a bit. Let's now go through a few examples...
↓ Easy approach ↓
A base class (Person) and child classes which derive from the base class. (applies to C#, VB.NET, etc)
but in VBA you have to think of it like this:
A base class which exposes an enum property describing the position.
Something like
This is the easiest way to have a class exposing some properties. It allows you to add your Person objects into a collection and iterate over using the easy for each loop with Intellisense!
Properties comparison system would be very very easy
note: same applies to enum as its implicitly converted to a number
↓ More complex approach ↓
Two separate classes which both expose public properties. For instance you have an Employee and Manager classes which both implement a Person Interface and an additional Comparer class exposing a Compare() method
In your VBA Project you need 4 class modules and a standard module
Person (this is your Interface)
Public Property Get Name() As String
End Property
Public Property Get Age() As Long
End Property
this class is the interface which both the Employee and Manager both need to implement to share some common functions (getters for Names and Ages). Having the interface allows you to do the for each loop using the interface type variable as the enumerator. You will see in a minute.
Employee and Manager are identical. Obviously you may modify them to suit your real life solution.
Implements Person
Private name_ As String
Private age_ As Long
Public Property Get Name() As String
Name = name_
End Property
Public Property Let Name(ByVal Value As String)
name_ = Value
End Property
Public Property Get Age() As Long
Age = age_
End Property
Public Property Let Age(ByVal Value As Long)
age_ = Value
End Property
Private Property Get Person_Name() As String
Person_Name = Name
End Property
Private Property Get Person_Age() As Long
Person_Age = Age
End Property
ComparerCls you will use an instance of this class to compare two objects properties or references. You do not necessarily need to have a class for this but I prefer it that way.
Public Enum ComparisonMethod
Names = 0 ' default
Ages = 1
References = 2
End Enum
' makes names the default comparison method
Public Function Compare(ByRef obj1 As Person, _
ByRef obj2 As Person, _
Optional method As ComparisonMethod = 0) _
As Boolean
Select Case method
Case Ages
Compare = IIf(obj1.Age = obj2.Age, True, False)
Case References
Compare = IIf(obj1 Is obj2, True, False)
Case Else
Compare = IIf(obj1.Name = obj2.Name, True, False)
End Select
End Function
And your Module1 code
Option Explicit
Sub Main()
Dim emp As New Employee
emp.Name = "person"
emp.Age = 25
Dim man As New Manager
man.Name = "manager"
man.Age = 25
Dim People As New Collection
People.Add emp
People.Add man
Dim individual As Person
For Each individual In People
Debug.Print TypeName(individual), individual.Name, individual.Age
Next
End Sub
run the Main() sub and check out the results in the Immediate Window
The best part of the above code is the fact that you are creating a reference variable of the Person interface. It allows you to loop through all items in the collection that implement the interface. Also, you can use the Intellisense which is great if you have had many more properties and functions.
Comparison
Take a look again at the code in the ComparerCls class
I hope you see now why I have separated this to be a class. Its purpose is just to take care of the way the objects are being compared. You can specify the Enum order and modify the Compare() method itself to compare differently. Note the Optional parameter which allows you to call the Compare method without the method of comparison.
Now you can play around passing different parameters to the compare function. See what the results are like.
Try combinations:
emp.Name = "name"
man.Name = "name"
Comparer.Compare(emp, name, Names)
Comparer.Compare(emp, name, References)
Comparer.Compare(emp, emp, References)
If something is still unclear refer to this answer about the Implements keyword in VBA

Related

What is the difference between "Class program" and "module program" in VB.net [duplicate]

This question already has answers here:
Classes vs. Modules in VB.NET
(8 answers)
Closed 3 years ago.
I want convert a C# code to vb.net console.
This is first time I find this two type code structure.
1.
Namespace ConsoleApp4
Module Program
Public Sub Main(ByVal args As String())
test()
End Sub
sub test()
end sub
End Module
End Namespace
2.
Namespace ConsoleApp4
Class Program
Public Shared Sub Main(ByVal args As String())
test()
End Sub
shared sub test()
end sub
End Class
End Namespace
what is the difference of this two type?
Sub Main must be shared to work as entry point of the application. It is automatically shared (or static) in modules. In classes the Shared keyword is required.
A VB module corresponds to a C# static class. Static classes and modules have only static members that can be used without having to create an object. In contrast, a non-static class must be instantiated to access its non-static (C#) or non-shared (VB) members
Module M
Public Function F(ByVal x As integer) As Integer
Return x * x
End Function
End Module
Class C
Public Function T(ByVal x As Integer) AS Integer
Return x + 10
End Function
End Class
With these declarations, you can write
Dim r1 As Integer = M.F(5) ' Or simply F(5) '
Dim o As C = New C() ' Must instantiate class, i.e., create an object.'
Dim r2 As Integer = o.T(32)
If you have variables (or properties) in a module, those exist exactly once. You can, however, create many objects from the same class and each object will contain another copy of these variables
Public Class Person
Public Property FirstName As String
Public Property LastName As String
End Class
Using this class declaration you can write
Dim list As New List(Of Person)()
list.Add( New Person With { .FirstName = "Terry", .LastName = "Adams"} )
list.Add( New Person With { .FirstName = "Lisa", .LastName = "Jones"} )
For Each p As Person In list
Console.WriteLine($"Person = {p.FirstName} {p.LastName}")
Next
Now you have two Person objects in the list having different first and last names.
Classes belong to Object-oriented programming (OOP). I suggest you to read some introductions about it, as .NET is mainly based on OOP concepts.
See also:
Object-oriented programming (Visual Basic)
Explain Object Oriented Programming in VB.NET
The difference between a Module and a Class is subtle; there is only ever one instance of a Module in all your program's life, and you cannot make multiple instances of it using the New keyword. By contrast you have to make an instance of a Class before you can use it..
In order to run, the .net framework runtime has to be able to find an available Main method without having to create an instance of a Class. This is achieved either by having the Main be kept inside a Module (and thus the Main is available because the module is available without having to instantiate a Class) or having it declared as Shared and be inside a Class (in which case you can conceive that a special thing happens that makes the shared Subs available without a class instance)
It's a hard difference to explain if you're not really well introduced to the concepts of OO programming and what "instances" actually means:
Class Person
Public Name as String
Public Sub New(n as String)
Name = n
End Sub
End Class
This declares a class of type person. Nothing about it refers to a particular person and it doesn't cause any Person object to exist in the computer memory until you use New:
Dim cj as New Person("Caius Jard")
Dim g as New Person("gxmgxm")
g.Name = "gsmgxm" 'correct a typo! this edits the name in the g object. it leaves cj's name alone
Now two instances of a Person object are in your computer memory, one named for me and one for you. You cannot do this with a Module. If we'd declared Person as Module there would only be one of them in all the entire program, accessed by the reference "Person":
Person.Name = "Caius Jard"
Person.Name = "gsmgxm" 'this overwrites my name. The program cannot remember more than one Person
and we couldn't have multiples. Consider that, at the time you launch your program, the runtime finds everything that is declared to be a Module and creates just one of them. This is somewhat vital for all sorts of advanced reasons that I won't get into, and Modules definitely do have their place in the grand scheme of things but thy aren't always incredibly useful in OO programming because more of the time we want more than one instance of a thing so we can model multiple things simultaneously.
So that's a precis on Class vs Module and why they are. In order to call any Sub or Function, you have to be able to call it on something. You have to have a DVD player before you can put a DVD in it and press Play - equally in programming, you have to have something that you can put your Main sub on, so you (or the .net runtime) can refer to it with Program.Main() and execute the instructions of the Sub.. That's how Subs and Functions work - theyre either associated with the special single instance (if it's a Module or a Shared Sub/Function of a Class), or they're associated with some object instance in the computer memory, and calling the Sub/Function acts on the object instance that was referred to:
Class Person
Public Name as String
Public Sub SetNameBlank()
Name = ""
End Sub
End Class
cj.SetNameBlank() 'the name of the cj object we declared before, is now blank
g.SetNameBlank()
By specifying the object instance name cj then the Sub name, we establish context - the actions listed in SetNameBlank will be carried out against the cj object, not the g one. If we'd declared SetNameBlank as Shared then they would have taken place in the shared context, not related to either cj or g, and Shared SetNameBlank() could be invoked without cj or g even existing
Going back to the reason you're asking your question right now, what the difference between these two (in the context of a "Main" Sub), the answer is..
..is "not much" from the perspective of getting your app going. Either approach is fine. Your Main method will have to start kicking things off my making object instances of the other Classes you have in your program and getting them to do things. It probably wont make new instances of the class that your Main is in so right now it doesn't really matter whether you put your main in a module or a class - it achieves the same end result that there is a Sub the runtime can call into to start things moving, without needing to create an instance of a Class first
https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/program-structure/main-procedure

Icomparer for objects - or should I be using a SortedSet in the first place?

I want to create a sorted list of objects which are rather like KeyValue pairs, where the Value is itself an Object (quite a complex one, and may be one of a number of subclasses of a base type). The Keys can be duplicated, and I don't mind in which order the Values are sorted - it doesn't even have to be deterministic.
SortedList won't work because of the duplicate keys, so I thought of SortedSet. But to get this to work it seems that I need a comparator which returns a non-zero value when the entries are distinct, so I need to include the Value as part of the comparison.
The need to traverse the list in sorted order happens quite often in the program, so the suggestion that I've seen elsewhere to use a List and sort it when needed is probably not going to work too well.
So my question is: how can I write an Icomparer which compares objects and only produces a 0 result when the objects are in fact the same?
Some fairly minimal VB.NET code follows.
MustInherit Class Item
Friend Name As String
' I'm not bothering to add the constructor - just assume that "name" gets set somehow
End Class
Public Class ItemA
Inherits Item
End Class
Public Class ItemB
Inherits Item
End Class
Class ListEntry
Friend Key As Integer
Friend Entry As Item
End Class
Class EntryComparer
Implements IComparer(Of ListEntry)
Public Function Compare(x As ListEntry, y As ListEntry) As Integer Implements IComparer(Of ListEntry).Compare
If x.Key > y.Key Then
Return 1
ElseIf x.Key < y.Key Then
Return -1
Else
If x.Entry is y.Entry Then
Return 0
End If
'****************************
' what do I put in here to compare the Entry fields to ensure that it's only 0 when they're the same object?
'****************************
End If
End Function
End Class
I think this will work (not tested it yet, though):
Give the Item class a shared private field SerialNumber and a field SNo. When New is called, Copy SerialNumber into SNo and increment it. Use this as a secondary key for sorting.

When code is contained inside < > for Visual Basic

When using vb.net, if code is contained inside "< >" signs, like a namespace, what is it telling the compiler to do? Also, what would these be signs be called when used like this?
To give clarity to the question; I know that parentheses "( )" are generally used for arguments and that brackets "[ ]" are used to declare a new type, but I cannot find what the less than/greater than signs do when used in a similar capacity.
I've looked through my reference books and attempted to research this through the internet but I haven't come up with an answer. Most likely because I don't know what exactly these would be named. I always results that talk about the relational operators, which is not what I'm looking for.
Here is an example of what I'm looking at:
Imports System.ComponentModel.Design
'<CLSCompliant(True)>
<System.ComponentModel.DefaultEvent("DataReceived")> _
Public Class SerialDF1forSLCMicroCon
Inherits MfgControl.AdvancedHMI.Drivers.DF1ForSLCMicroPLC5
Implements System.ComponentModel.IComponent
Implements System.ComponentModel.ISupportInitialize
Private Shared ReadOnly EventDisposed As New Object()
Public Event Disposed As EventHandler Implements System.ComponentModel.IComponent.Disposed
Protected m_synchronizationContext As System.Threading.SynchronizationContext
Specifically I am looking at the line that contains
<System.ComponentModel.DefaultEvent("DataReceived")> _
That is an attribute. It is a way of attaching metadata (additional information) to your code that can be queried later using reflection.
For example, let's say you have a series of classes (e.g. Customer, Contact, Order, Product, etc.), each of which corresponds to a database table, and inherits from a DbTable base class that has a common DeleteAll() method.
Now, it might be that your database table names don't match your class names. In that case you can define an attribute that adds additional information to your class, providing the table name, as shown here:
<DbTableName("CUST01")>
Public Class Customer
Inherits DbTable
...
End Class
This indicates that your "Customer" objects are stored in the "CUST01" table in the database.
You might implement the attribute like this:
Public Class DbTableNameAttribute
Inherits System.Attribute
Public Property Name As String
Public Sub New(value As String)
Name = value
End Sub
End Class
Lastly, in your base DbTable class, you would implement DeleteAll() like this:
Public MustInherit Class DbTable
Public Sub DeleteAll()
' Use reflection to retrieve the attribute.
Dim attributes = Me.GetType().GetCustomAttributes()
Dim dbTableNameAttribute = attributes.FirstOrDefault(Function(x) x.GetType() = GetType(DbTableNameAttribute)
If dbTableNameAttribute IsNot Nothing Then
Dim tableName As String = CType(dbTableNameAttribute, DbTableNameAttribute).Name
' tableName will contain the value specified in the attribute (e.g. "CUST01")
Dim sql As String = "delete from " & tableName
' ... at this point you would send the delete command to your database ...
End If
End Sub
End Class
Now, in the specific example you cite: <System.ComponentModel.DefaultEvent("DataReceived")>
What is likely happening is that the SerialDF1forSLCMicroCon class probably has multiple events, and the attribute is providing a hint to the designer that the "DataReceived" event is the default one. You'll see a similar sort of thing with a Windows Forms Button. If you click the events for a Button, there are many, but the "Click" event is always highlighted by default, as it is the most commonly used one.

How to Save/Reload data in vb.net after .exe close?

I am new to vb.net, and this is my first project where I'm fairly certain there is an obvious answer that I just can't find.
Problem: I have a list of a structure I have defined with many properties. I want to be able to edit and load that list with the values I have saved to it before hand after closing the program and loading it backup. What is the best way to do this?
This isn't a simple string or bool, otherwise I would use the user settings that is commonly suggested, in the project's properties. I've seen others that save it into an xml and take it back up, but I'm not inclined to do so since this is going to be distributed to others in mass. Since it's a complex structure, what's the commonly held preferred method?
Example
Here's a structure:
Structure animal
Dim coloring as string
Dim vaccinesUpToDate as Boolean
Dim species as string
Dim age as integer
End structure
And there's a List(Of animal) that the user will add say 1 cat, 2 dogs, etc. I want it so that once the programs is closed after the user has added these, that structure will be saved to still have that 1 cat and 2 dogs with those settings so I can display them again. What's the best way to save the data in my program?
Thanks!
Consider serialization. For this, a class is more in order than an old fashioned Struct:
<Serializable>
Class Animal
Public Property Name As String
Public Property Coloring As String
Public Property VaccinesUpToDate As Boolean
Public Property Species As String
Public Property DateOfBirth As DateTime
Public ReadOnly Property Age As Integer
Get
If DateOfBirth <> DateTime.MinValue Then
Return (DateTime.Now.Year - DateOfBirth.Year)
Else
Return 0 ' unknown
End If
End Get
End Property
' many serializers require a simple CTor
Public Sub New()
End Sub
Public Overrides Function ToString() As String
Return String.Format("{0} ({1}, {2})", Name, Species, Age)
End Function
End Class
The ToString() override can be important. It is what will display if you add Animal objects to a ListBox e.g.: "Stripe (Gremlin, 27)"
Friend animalList As New List(of Animal) ' a place to store animals
' create an animal
a = New Animal
a.Coloring = "Orange"
a.Species = "Feline" ' should be an Enum maybe
a.Name = "Ziggy"
a.BirthDate = #2/11/2010#
animalList.Add(a)
' animalList(0) is now the Ziggy record. add as many as you like.
In more complex apps, you might write an Animals collection class. In that case, the List might be internal and the collection could save/load the list.
Friend Sub SaveData(fileName as String)
Using fs As New System.IO.FileStream(fileName,
IO.FileMode.OpenOrCreate)
Dim bf As New BinaryFormatter
bf.Serialize(fs, animalList)
End Using
End Sub
Friend Function LoadData(fileName as String) As List(Of Animal)
Dim a As List(of Animal)
Using fs As New FileStream(fileName, FileMode.Open, FileAccess.Read)
Dim bf As New BinaryFormatter
a = CType(bf.Deserialize(fs), List(Of Animal))
End Using
Return a
End Function
XMLSerialization, ProtoBuf and even json are much the same syntax. For a small amount of data, a serialized list is an easy alternative to a database (and have many, many other uses, like a better Settings approach).
Calculated Fields as Properties
Notice that I added a BirthDate property and changed Age to calculate the result. You should not save anything which can be easily calculated: in order to update the Age (or VaccinesUpToDate) you'd have to 'visit' each record, perform a calculation then save the result - which might be wrong in 24 hours.
The reason for exposing Age as a Property (rather than a function) is for data binding. It is very common to use a List<T> as the DataSource:
animalsDGV.DataSource = myAnimals
The result will be a row for each animal with each Property as a column. Fields as in the original Structure won't show up. Nor would an Age() function display, wrapping the result as a readonly property displays it. In a PropertyGrid, it will show disabled because it is RO.
Class versus Structure
So if a Structure using Properties will work, why use a Class instead? From Choosing Between Class and Struct on MSDN, avoid using a Structure unless the type meets all of the following:
It logically represents a single value, similar to primitive types (int, double, etc.)
It has an instance size under 16 bytes
It is immutable
It will not have to be boxed frequently
Animal fails the first 3 points (while it is a local item it is not a value for #1). It may also fail the last depending on how it is used.

Should I create individual properties in a class or just a method to set the values?

I am learning vb.net and I am having trouble wrapping my head around the following...
I can create several properties of a custom class and get/set values or I can create a method to set them all at once. If each property is going to allow read and write should I just make a method to assign values all at once? I assume that I am missing a very important piece here. Example:
I can create 2 properties:
Public Class Employee
Public Property LastName as string
Get
Return strLastName
End get
Set(ByVal value as string)
strLastName= value
End Set
End Property
Public Property FirstName as string
Get
Return strFirstName
End get
Set(ByVal value as string)
strFirstName= value
End Set
End Property
End Class
or I can create a method:
Public Class Employee
Public Sub AddEmployee(ByVal strLastName, ByVal strFirstName)
LastName = strLastName
FirstName = strFirstName
End Sub
End Class
I apologize for such a noob question, but any insight is greatly appreciated. thank you!
If you only have a single method, you will have to use it even if you only want to change the value of a single field.
Additionally, in such a method, if you need to validate the input, you will need to write quite a lot of code for validation that is not relevant to all of the fields.
If values must be updated together, use a method to update them together and do not provide setters.
The reality of things is that how to do this depends on what you are modelling in your class. There are no hard and fast rules that say that properties are better than methods or vice versa.
There is no reason not to support both properties and a method that sets multiple properties.
Commonly, a constructor is used to create an instance of the class and to set some properties. In VB, naming a class method "New" defines it as a constructor. In your example, if you rename your AddEmployeee method to New, you will have a perfectly fine constructor. Then you program can create new instances as such:
Dim emp1 as New Employee("Burdell", "George")