Variable that automatically updates when another property is updated - vb.net

I'm sure this is a very simple thing and there's just something I fundamentally don't understand about properties/variables. I want the class to have a variable that is assigned/calculated after some property values are input. This isn't homework, I'm just using a simplified example.
Public Class person
Public Property name As String
Public Property address As String
Public phoneNumber As String
Public Sub getPhoneNumber()
phoneNumber = lookLookThemUpInThePhoneBook
End Sub
End Class
I might not know the person's name AND address when they are created, but as soon as they are both assigned, I want the phone number to be calculated.
I know I could just do
Dim Eddy As New person
Eddy.name = "Ed Wood"
Eddy.address = "123 Sample Road"
Eddy.getPhoneNumber()
Debug.Print(Eddy.phoneNumber)
But this will get very cumbersome having to manually run the procedure, so I figure there has to be a way to have that value get proactively assigned.
I'd like to be able to do this
Eddy.name = "Ed Wood"
Eddy.address = "123 Sample Road"
Debug.Print(Eddy.phoneNumber) ' His phone number is just automatically looked up.

You can delay the phone number lookup until the phoneNumber property is referenced by implementing that logic in the setter of that property (as mentioned in the comments). Here is an example of that:
Public ReadOnly Property phoneNumber As String
Get
'Leverage a class level private variable and wrap in an
'If Statement if you only want To look it up once.
Return lookLookThemUpInThePhoneBook()
End Get
End Property
Then when you do objYourPerson.phoneNumber it does the look up for you.

Both the Name and Address properties can try to set the phone number when they are modified
Public Class Person
Private _name As String = ""
Private _address As String = ""
Private _phoneNumber As String = "not set yet..."
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
_name = value
setPhoneNumber()
End Set
End Property
Public Property Address As String
Get
Return _address
End Get
Set(value As String)
_address = value
setPhoneNumber()
End Set
End Property
Public Property PhoneNumber As String
Get
Return _phoneNumber
End Get
Private Set(value As String)
_phoneNumber = value
End Set
End Property
Public Sub setPhoneNumber()
If Name <> "" AndAlso Address <> "" Then PhoneNumber = lookThemUpInThePhoneBook(Name, Address)
End Sub
Private Function lookThemUpInThePhoneBook(name As String, address As String) As String
Return "123456789" ' search by name and address here
End Function
End Class
usage:
Sub Main()
Dim Eddy As New Person()
Eddy.Name = "Ed Wood"
Console.WriteLine(Eddy.PhoneNumber)
Eddy.Address = "123 Sample Road"
Console.WriteLine(Eddy.PhoneNumber)
Console.ReadLine()
End Sub
output
not set yet...
123456789

something like this...
Public Class Person
Public Sub New(strname As String, straddress As String)
name = strname
address = straddress
_phoneNumber = LookThemUpInThePhoneBook(strname, straddress)
End Sub
Private Function LookThemUpInThePhoneBook(pName As String, pAddress As String) As String
Dim myPhone As String = ""
'Do work to look up phone number
Return myPhone
End Function
Private _phoneNumber As String
Public ReadOnly Property phoneNumber As String
Get
Return _phoneNumber
End Get
End Property
Private _name As String
Public Property name As String
Get
Return _name
End Get
Set(value As String)
_name = value
End Set
End Property
Private _address As String
Public Property address As String
Get
Return _address
End Get
Set(value As String)
_address = value
End Set
End Property
End Class
Then
Dim Eddy As New Person("Eddy wood", "123 Sample Road")
Debug.Print(Eddy.phoneNumber)

Related

How to create a class within a class in VBA

I am trying to create a class based off an existing set of data. The columns include name, street address, city, state, and zip. I would like to create a class with name and address. Within this class I would like to have another class for address containing street address, city, state and zip.
My code looks like this for each private variable (I do not have variables for city, state or zip but I am sure I need them):
Private pCustomerName As String
Public Property Let CustomerName(Value As String)
pCustomerName = Value
End Property
Public Property Get CustomerName() As String
CustomerName = pCustomerName
End Property
What would we be different about the address section to include a sub-class I guess is what I'm asking.
Currently looks like this:
Public Property Let Address(Value As String)
pAddress = Value
End Property
Public Property Get Address() As String
Address = pAddress
End Property
You are right there man! You can simply create another class for your address data and instantiate it inside your Customer Class. You just need to use VBA's Set keyword to assign the Address object back and forth. It is also usually good practice to instantiate the Address object in the Class_Initialize and set it to Nothing in the Class_Terminate as shown below:
Customer Class:
Option Explicit
Private pCustomerName As String
Private pAddress As AddressObj
Public Property Let CustomerName(value As String)
pCustomerName = value
End Property
Public Property Get CustomerName() As String
CustomerName = pCustomerName
End Property
Public Property Set CustomerAddress(value As AddressObj)
Set pAddress = value
End Property
Public Property Get CustomerAddress() As AddressObj
Set CustomerAddress = pAddress
End Property
Private Sub Class_Initialize()
Set pAddress = New AddressObj
End Sub
Private Sub Class_Terminate()
Set pAddress = Nothing
End Sub
Address Class (I called it AddressObj)
Option Explicit
Private pZipCode As Integer
Private pStreet As String
Private pState As String
Public Property Let ZipCode(value As Integer)
pZipCode = value
End Property
Public Property Get ZipCode() As Integer
ZipCode = pZipCode
End Property
Public Property Let Street(value As String)
pStreet = value
End Property
Public Property Get Street() As String
Street = pStreet
End Property
Public Property Let State(value As String)
pState = value
End Property
Public Property Get State() As String
State = pState
End Property
'... etc for other properties

Class with property that is a List Of items in a subclass

I want a class for a Customer, with a text property name.
Another property CustAddress will be a list of multiple addresses.
Each address will have two string properties.
Here is what I have.
I am not sure if I need something in the constructor of the class address.
And I'm even not sure what the code would look like to exploit this class.
Also, I can't get the F11 Step Into debug feature to step into the class code. If i put a break in the class code it does break and works fine. I have modified the option "Just My Code" to remove checkbox, but it does not help. I have a solution containing one class module and one Windows App together.
<ComClass(ComClass1.ClassId, ComClass1.InterfaceId, ComClass1.EventsId)>
Public Class ComClass1
#Region "COM GUIDs"
' These GUIDs provide the COM identity for this class
' and its COM interfaces. If you change them, existing
' clients will no longer be able to access the class.
Public Const ClassId As String = "c8e723b4-f229-4368-9737-97c4c71d490a"
Public Const InterfaceId As String = "16275ddb-5cfe-47c0-995f-84a5f868ad1b"
Public Const EventsId As String = "dad73a5c-8ac4-4384-a5f9-8e2c388b5514"
#End Region
' A creatable COM class must have a Public Sub New()
' with no parameters, otherwise, the class will not be
' registered in the COM registry and cannot be created
' via CreateObject.
'Fields
Private _name As String
Public _CustAddress As List(Of address)
'Constructor for class ComClass
Public Sub New()
_CustAddress = New List(Of address)
End Sub
Public Property CustName() As String
Get
Return _name
End Get
Set(ByVal Value As String)
_name = Value
End Set
End Property
Public Property CustAddress() As List(Of address)
Get
Return _CustAddress
End Get
Set(value As List(Of address))
_CustAddress = value
End Set
End Property
Public Class address
Private _address1 As String
Private _address2 As String
Public Sub New()
'??????
End Sub
Public Property Address1 As String
Get
Return _address1
End Get
Set(value As String)
_address1 = value
End Set
End Property
Public Property Address2 As String
Get
Return _address2
End Get
Set(value As String)
_address2 = value
End Set
End Property
End Class
End Class
I took out the com stuff just to shorten the answer. Since you had no extra code in the Property Procedures I shortened that to automatic Properties. I also moved the address Class out on its own. This class could be useful else where in the program so a nested class is not really necessary.
Public Class ComClass1
Public Property CustName As String
Public Property CustAddress As List(Of address)
Public Sub New(cName As String, cAddresses As List(Of address))
CustName = cName
CustAddress = cAddresses
End Sub
End Class
Public Class address
Public Property Address1 As String 'Street Address
Public Property Address2 As String 'City and State
Public Sub New(a1 As String, a2 As String)
Address1 = a1
Address2 = a2
End Sub
End Class
Private Sub DeclareAComClass1()
Dim addrList As New List(Of address) From {
New address("12 Main Street", "Los Angeles, CA"),
New address("13 Park Avenue", "New York, NY")
}
Dim cc As New ComClass1("Big Company, Inc.", addrList)
End Sub
Here is what I have ended up with. #Mary got me further ahead. But because I am using a COM class, I can't have any public constructors with parameters.
I added a method called AddAddress which gives me the functionality I need.
In my original post I somehow left out MyBase.New which is required for a COM class.
I encourage comments with insights on this approach.
<ComClass(ComClass1.ClassId, ComClass1.InterfaceId, ComClass1.EventsId)>
Public Class ComClass1
#Region "COM GUIDs"
' These GUIDs provide the COM identity for this class
' and its COM interfaces. If you change them, existing
' clients will no longer be able to access the class.
Public Const ClassId As String = "c8e723b4-f229-4368-9737-97c4c71d490a"
Public Const InterfaceId As String = "16275ddb-5cfe-47c0-995f-84a5f868ad1b"
Public Const EventsId As String = "dad73a5c-8ac4-4384-a5f9-8e2c388b5514"
#End Region
' A creatable COM class must have a Public Sub New()
' with no parameters, otherwise, the class will not be
' registered in the COM registry and cannot be created
' via CreateObject.
'Fields
Private _name As String
Private _CustAddress As List(Of address)
'Constructor for class ComClass
Public Sub New()
MyBase.New
_CustAddress = New List(Of address)
End Sub
Public Sub AddAddress(a1 As String, a2 As String)
Dim addr As New address(a1, a2)
_CustAddress.Add(addr)
End Sub
Public Property CustName() As String
Get
Return _name
End Get
Set(ByVal Value As String)
_name = Value
End Set
End Property
Public Property CustAddress() As List(Of address)
Get
Return _CustAddress
End Get
Set(value As List(Of address))
_CustAddress = value
End Set
End Property
Public Class address
Private _address1 As String
Private _address2 As String
Public Sub New(a1 As String, a2 As String)
_address1 = a1
_address2 = a2
End Sub
Public Property Address1 As String
Get
Return _address1
End Get
Set(value As String)
_address1 = value
End Set
End Property
Public Property Address2 As String
Get
Return _address2
End Get
Set(value As String)
_address2 = value
End Set
End Property
End Class
End Class
And the code to implement/test is as follows:
Dim TestClass As New ComClass1
Dim myint As Int32
TestClass.CustName = "John Smith"
TestClass.AddAddress("123 Main Street", "Los Angeles")
TestClass.AddAddress("13 Park Avenue", "New York")
Debug.Print(TestClass.CustAddress(0).Address1) '123 Main Stree'
Debug.Print(TestClass.CustAddress(1).Address1) '13 Park Avenue
TestClass.CustAddress.Remove(TestClass.CustAddress(0))
Debug.Print(TestClass.CustAddress(0).Address1) ' 13 Park Avenue

Comparing list of class by string length and text comparison

I have a List(Of Abbreviation).
The class "Abbreviation" contains the string members "Input", "Output" and "CaseSensitive".
The class is stated below.
I would like to sort this list so that the class with the "Input"
"ZZZ"
comes before
"zz"
The comparison should thus first compare by string length, then by alphetical order, and then by CaseSensitive.
How could I sort the list this way?
Public Class Abbreviation
Implements IComparable
Private _sIn As String = String.Empty
Private _sOut As String = String.Empty
Private _bCaseSensitive As Boolean = False
Public Property Input() As String
Get
Return _sIn
End Get
Set(value As String)
_sIn = value
End Set
End Property
Public Property Output() As String
Get
Return _sOut
End Get
Set(value As String)
_sOut = value
End Set
End Property
Public Property CaseSensitive() As Boolean
Get
Return _bCaseSensitive
End Get
Set(value As Boolean)
_bCaseSensitive = value
End Set
End Property
Public Sub New(ByVal uInput As String, ByVal uOutput As String, ByVal uCaseSensitive As Boolean)
_sIn = uInput
_sOut = uOutput
_bCaseSensitive = uCaseSensitive
End Sub
End Class
First, you can simplify your code by using automatic properties. The compiler writes the get, set, and backer fields (the private fields). This is the preferred way in recent versions of Visual Studio if there is no extra code in the getter, setter.
Another small detail with your Sub New. It violates encapsulation to set the private fields directly. Always go through the Public Properties. There could be code in the setter that needs to run before storing the data in the private fields. Classes like to keep their data close to the vest in their private fields.
Example of Auto Implemented Properties
Public Property Input As String
Public Property Output As String
Public Property CaseSensitive As Boolean
The sort can be done in one line of code using a Linq query.
Dim orderedList = From abrev In lstAbreviations Order By abrev.Input.Lenght Descending Select abrev
To check the output...
For Each abrev As Player In orderedList
Debug.Print(abrev.Input)
Next
I think I got it, except the case sensitivity. CaseSensitive should come before CaseSensitive = False.
Public Class Abbreviation
Implements IComparable(Of Abbreviation)
Private _sIn As String = String.Empty
Private _sOut As String = String.Empty
Private _bCaseSensitive As Boolean = False
Public Function CompareTo(uOther As Abbreviation) As Integer _
Implements IComparable(Of Abbreviation).CompareTo
If uOther.Input.Length > Me.Input.Length Then
Return 1
ElseIf uOther.Input.Length < Me.Input.Length Then
Return -1
Else
If uOther.Input > Me.Input Then
Return 1
Else
Return -1
End If
End If
End Function

InvalidOperationException on GetType

I try to serialize a Class in VB using XMLSerializer.
But when I call GetType for my Class I got a InvalidOperationException error.
Dim Playlist_serialize As New XmlSerializer(p.GetType)
Here is my class :
Public Class Playlist
Private p_name As String
Private p_elements As List(Of Playlist_element)
Sub New()
p_elements = New List(Of Playlist_element)
End Sub
Public Property Name() As String
Get
Name = p_name
End Get
Set(value As String)
p_name = value
End Set
End Property
Public Property Elements() As List(Of Playlist_element)
Get
Elements = p_elements
End Get
Set(value As List(Of Playlist_element))
p_elements = value
End Set
End Property
Here is my Playlist_element :
Public Class Playlist_element
Private p_Name As String
Private p_Type As String
Private p_Genre As String
Public Property Name() As String
Get
Name = p_Name
End Get
Set(value As String)
p_Name = value
End Set
End Property
Public Property Type() As String
Get
Type = p_Type
End Get
Set(value As String)
p_Type = value
End Set
End Property
Public Property Genre() As String
Get
Genre = p_Genre
End Get
Set(value As String)
p_Genre = value
End Set
End Property
Sub New(ByVal name As String, ByVal type As String, ByVal genre As String)
Me.Name = name
Me.Genre = genre
Me.Type = Type
End Sub
End Class
There are several issues with the way Playlist_element is coded. First your property getters are wrong. They need to return the backing field:
Public Property Name() As String
Get
' this does nothing:
'Name = p_Name
Return p_Name
End Get
Set(value As String)
p_Name = value
End Set
End Property
Next, I would not use Type as a property name even if you can. If you drill into the inner exception and view the message, it tells you that it cannot serialize PlayList_element because it does not have a simple constructor. All serializers require this because they do not know how to use:
Sub New(ByVal name As String, ByVal type As String, ByVal genre As String)
p_Name = name
p_Genre = genre
p_Type = type
End Sub
' add:
Public Sub New()
End Sub
It should work fine. I should note that as of VS2010, you can use auto-implemented properties and skip a lot of that code:
Public Class Element
Public Property Name() As String
Public Property Type() As String
Public Property Genre() As String
Sub New(name As String, type As String, genre As String)
_Name = name
_Genre = genre
_Type = type
End Sub
Public Sub New()
End Sub
End Class
VS provides a "hidden" backing field as with _Name, _Genre etc.

Deserializing nested json in vb.net

I'm new to json and am working on deserializing some nested json into an object. The outer object works fine, but I'm not getting any values for the inner object. I've tried several solutions including using list object, collections, the datacontractserializer, but nothing seems to work. I think I'm probably missing something obvious. Here is what I have now:
The json string looks like this:
{"type":"lookup","message":"Success","version":0.1,"user":{"loginName":"username","vendor":null}}
My code is as follows:
<Serializable()> Public Class LookupReturn
Private _Type As String = ""
Private _Message As String = ""
Private _Version As String = ""
Private _user As New jsonUser
Public Property Type() As String
Get
Return _Type
End Get
Set(ByVal value As String)
_Type = value
End Set
End Property
Public Property Message() As String
Get
Return _Message
End Get
Set(ByVal value As String)
_Message = value
End Set
End Property
Public Property Version() As String
Get
Return _Version
End Get
Set(ByVal value As String)
_Version = value
End Set
End Property
Public Property Userobj() As jsonUser
Get
Return _user
End Get
Set(ByVal value As jsonUser)
_user = value
End Set
End Property
End Class
<Serializable()> Public Class jsonUser
Private _loginName As String = ""
Private _vendor As String = ""
Public Property loginName() As String
Get
Return _loginName
End Get
Set(ByVal value As String)
_loginName = value
End Set
End Property
Public Property vendor() As String
Get
Return _vendor
End Get
Set(ByVal value As String)
_vendor = value
End Set
End Property
End Class
Dim _Json As New JavaScriptSerializer()
Dim _Message as string = "{"type":"lookup","message":"Success","version":0.1,"user"{"loginName":"username","vendor":null}}"
Dim returnData As LookupReturn = _Json.Deserialize(Of LookupReturn)(_Message)
I'm geting data in the type, message, version values for the LookupReturn object, and it's returning an object for the user item, but the value for loginName is an empty string.
Any help would be appreciated!
Thanks!
After working with this some more, I realized that the problem was that my nested object was not named as it should be in the Get/Set section of my code. Because I was calling it "Userobj" instead of "user" as it was called in the jSon, the deserializer wasn't able to work properly. I changed the name to "user" and all worked well!