I have several properties, for example
Public Property FIRSTNAME As New SQLString("FirstName", 50)
Public Property FULLNAME As New SQLString("Name", 50)
The SQLString object is defined as:
Public Class SQLString
Property SQL_Column As String
Property Limit As Integer
Property Value As String
Public Sub New(SQLcolumn As String, limit_ As Integer)
SQL_Column = SQLcolumn
Limit = limit_
End Sub
Public ReadOnly Property SQL_value() As String
Get
Return "'" & clean(Value, Limit) & "'"
End Get
End Property
End Class
Notice that through this method, each of my properties (e.g. FIRSTNAME) is able to have several sub properties, which is necessary.
To access them, it's simply for example FIRSTNAME.SQL_Column.
This works, however what I would like is to also be able to store a value (e.g. string data type) on the FIRSTNAME property itself, which would make accessing it like:
Dim MyFirstName As String = FIRSTNAME
Rather than:
Dim MyFirstName As String = FIRSTNAME.Value
Which is what I currently have to do.
The only way I can see to do this is to have the SQLString object be set to string (or another data type) by default, like:
Public Class SQLString As String
Obviously the above code does not work, but I'm wondering if there is an equivalent that does?
The default access modifier to a property (ie: Public, Private, etc) is the most restrictive when no access modifier is provided. In SQLString class, since there is not a Public access modifier in front of the properties in the class, they are essentially Private and not accessible from outside of the class.
Adding the access modifier to the properties should fix the issue you see:
Public Property SQL_Column As String
Public Property Limit As Integer
Public Property Value As String
Please tell me the problem for the vote downs - here is a working .NET fiddle of the proposed code changes above (https://dotnetfiddle.net/96o8qm).
Imports System
Dim p as Person = new Person()
p.FIRSTNAME = new SQLString("Test", 1)
p.FIRSTNAME.Value = "Test Value"
Console.WriteLine("Person Value: {0}", p.FIRSTNAME.Value)
Public Class Person
Public Property FIRSTNAME AS SQLString
End Class
Public Class SQLString
Public Property SQL_Column As String
Public Property Limit As Integer
Public Property Value As String
Public Sub New(SQLcolumn As String, limit_ As Integer)
SQL_Column = SQLcolumn
Limit = limit_
End Sub
Public ReadOnly Property SQL_value() As String
Get
Return ""
End Get
End Property
End Class
This yields the output:
Person Value: Test Value
The answer to your question is quite simple; add a CType widening operator.
Example:
Public Class SQLString
Public Shared Widening Operator CType(ByVal s As SQLString) As String
Return If((s Is Nothing), Nothing, s.Value)
End Operator
Public Property Value As String
End Class
Test:
Dim firstName As New SQLString() With {.Value = "Bjørn"}
Dim myName As String = firstName
Debug.WriteLine(myName)
Output (immediate window):
Bjørn
Related
I've been trying to read up on possible methods of casting one of my class objects to another to "simplify" some of my programming going forward. Specifically, I have a class I've built for some new development to store and retrieve information to/from our database, but I have a "legacy" class that I still need to use to retrieve data from an older iteration of the database until all of the data has been migrated and none of the applications/systems are looking for information in that older database. Here's an example:
Public Class Customer
Public Property customerID As Integer
Public Property firstName As String
Public Property middleName As String
Public Property lastName As String
Public Property businessName As String
Public Property mailingAddress As Address
Public Property homePhone As String
Public Property workPhone As String
End Class
Public Class Address
Public Property address1 As String
Public Property address2 As String
Public Property address3 As String
Public Property city As String
Public Property state As String
Public Property zipCode As String
Public Property zip4 As String
End Class
And the old class objects:
Public Class LegacyCustomer
Public Property id As Int64 = -1
Public Property type As String = String.Empty
Public Property name As String = String.Empty
Public Property subtype As Integer? = Nothing
End Class
Public Class LegacyAddress
Public Property id As Int64
Public Property type As String = String.Empty
Public Property addr1 As String = String.Empty
Public Property addr2 As String = String.Empty
Public Property city As String = String.Empty
Public Property state As String = String.Empty
Public Property county As String = String.Empty
Public Property zip As Integer? = Nothing
Public Property zip4 As Integer? = Nothing
End Class
As you can see, there are several similar properties between the old and new objects in just the above example code, but not all of them are able to "match up" exactly with their counterparts. A couple of examples:
The zipCode property in the "new" Address class is of type String while the zip property in the "old" LegacyAddress class is of type Integer?.
Where the Customer class has a name broken into component parts (firstName, middleName, lastName, businessName), the LegacyCustomer class has all the parts combined into a single name property.
The LegacyCustomer class has a type and subtype property while these properties do not exist in the Customer class.
The customerID property of the Customer class will almost certainly be different than the id property of the LegacyCustomer class.
So, in my research, I've been looking into building a custom type conversion routine a la this answer by Anthony Pegram to the similar question, "cast class into another class or convert class to another" I don't believe it would be too incredibly challenging to use this method, but I wonder if I need to use the Narrowing or a Widening version of this method. I would assume the former, but I'm not certain and would appreciate some guidance.
Also, because the mailingAddress property of the Customer class is of another class type and there is no matching object in the LegacyCustomer class, I'm guessing I would need to possibly create a matching "placeholder" address property (of type LegacyAddress) into the LegacyCustomer object in order for this to work as intended/expected. Here's kinda what I envision (this is all untested "pseudo-code" for now):
Add a property to the LegacyCustomer object
Public Class LegacyCustomer
'properties as defined above, plus
Public Property address As LegacyAddress
End Class
Public Class Customer
'properties as defined above
Public Shared Narrowing Operator CType(ByVal cust As Customer) As LegacyCustomer
Dim result As New LegacyCustomer
With result
If Not cust.businessName Is Nothing AndAlso Not String.IsNullOrEmpty(cust.businessName) Then
.name = cust.businessName
Else
If Not cust.lastName Is Nothing AndAlso Not String.IsNullOrEmpty(cust.lastName) Then
.name = cust.lastName & "," & cust.firstName & " " & cust.middleName
Else
.name = cust.firstName & " " & cust.middleName
End If
End If
.name = .name.Trim()
.type = "F"
With .address
.address1 = cust.mailingAddress.address1
.address2 = cust.mailingAddress.address2
If Not cust.mailingAddress.address3 Is Nothing AndAlso Not String.IsNullOrEmpty(cust.mailingAddress.address3) Then
.address2 = cust.mailingAddress.address2 & " " & cust.mailingAddress.address3
End If
.city = cust.mailingAddress.city
.state = cust.mailingAddress.state
If IsNumeric(cust.mailingAddress.zipCode) Then
.zip = TryCast(cust.mailingAddress.zipCode, Integer)
End If
If IsNumeric(cust.mailingAddress.zip4) Then
.zip4 = TryCast(cust.mailingAddress.zip4, Integer)
End If
.type = "A"
End With
End With
Return result
End Operator
End Class
Then, I'd probably have something similar in the LegacyCustomer class to convert it back the other way, although that may be a bit trickier.
I guess my actual question is in two parts:
Is overriding the CType operation in this way the "most effective" method of converting these objects from one to another? and
Am I correct in assuming that I'll need to use the Narrowing operator and not the Widening?
I have called clientdetails which I wish to return as a whole to the JSONConvert Method to Serialize for JSON.
I have created a Class that has the Property Types I require (TextA,TextB) etc.
I can refer to both TransactionCount and TransactionType because they are part of ClientDetails, however when I try to refer to TextA of Transactions it states that TextA is not a member of ClientDetails - this I know which is why I explicitly state .Transactions.TextA.
If I declare Transactions separate under a new variable then I am able to refer to them however I need them to all be declared under ClientDetails to pass to the Serializer.
Can anyone point me out what I'm doing wrong here? Still learning.
Public Class JSON
Public Shared Function SerializeObject()
Dim clientdetails As New ClientDetails() With {.TransactionCount = "1", .TransactionType = "Q", .Transactions.TextA} 'Unable to Refer to any Property of Transactions.
'Dim Trans As New Transactions()
'Trans.TextA= "Test"
Dim output As String = JsonConvert.SerializeObject(clientdetails, Newtonsoft.Json.Formatting.Indented)
Return output
End Function
End Class
Public Class ClientDetails
Public Property Transactions As New Transactions()
Public Property [TransactionType] As String
Public Property [TransactionCount] As Integer
End Class
Public Class Transactions
Public Property [RecordID] As String
Public Property No As String
Public Property TextA As String
Public Property TextB As String
Public Property Initial As String
Public Property Flag As String
Public Property Sex As String
Public Property Area As String
Public Property Type As String
Public Property IDNO As String
End Class
You can use this syntax:
Dim clientdetails As New ClientDetails() With {.TransactionCount = "1", .TransactionType = "Q", .Transactions = New Transactions() With {.TextA = "Test"}}
Or a more readable code:
Dim trans As New Transactions
trans.TextA = "Test"
Dim clientDetails As New ClientDetails()
With clientDetails
.TransactionCount = "1"
.TransactionType = "Q"
.Transactions = trans
End With
Is there a way in VBA to query a collection of custom classes named People. Lets say I have a custom class that has First Name, Last Name, and Title.
‘private attributes
Private pFirstName as String
Private pLastName as String
Private pTitle as String
‘Get/Let Methods
Public Property Get FirstName() as String
FirstName = pFirstName
End Property
Public Property Let FirstName (Value as String)
pFirstName = Value
End Property
Public Property Get LastName() as String
LastName = pLastName
End Property
Public Property Let LastName(Value as String)
pLastName = Value
End Property
Public Property Get Title() as String
Title = pTitle
End Property
Public Property Title Let (Value as String)
pTitle = Value
End Property
I then, in my main SUB, create a collection of people. Is there a way to query that collection, Ie, return me all People with first name == Jack.
Thanks
You could go out of your way to implement something like this so you could do crazy stuff like:
Dim items As LinqEnumerable
Set items = LinqEnumerable.FromCollection(myCollection) _
.Where("x => x.FirstName = ""Jack""")
Dim p As Person '"People" is plural, you don't want a pluralized class name here.
For Each p In items
Debug.Print p.FirstName
Next
But that is very very much overkill, and inefficient. All you need is one loop, and a condition:
For Each p In myCollection
If p.FirstName = "Jack" Then
'we have a winner
End If
Next
I am trying to reference the name of a variable as a string. I have a list of global variables
Public gvHeight As String = Blank
Public gvWeight As String = Blank
Public gvAge As String = Blank
I need to reference the name of the variables for an external API call. I am trying to avoid specific code per variable, instead allow me to add a new variable and everything reference correctly. I already have the rest of the code to deal with the name as a string.
example:
public Height as string
public weight as string
public age as string
[elsewhere in code]
for each var as string in {public variables}
CallToAPI(var.name) 'needs to send "height" "weight" or "age" but there are a lot so hardcoding is not a good solution
edited for example
You need to find the public fields through Reflection.
Having an example dll compiled from this source-code:
Public Class Class1
Public Field1 As String = "value 1"
Public Field2 As String = "value 2"
Public Field3 As Integer
End Class
Then you could do this:
' The library path.
Dim libpath As String = "...\ClassLibrary1.dll"
' The assembly.
Dim ass As Assembly = Assembly.LoadFile(libpath)
' The Class1 type. (full namespace is required)
Dim t As Type = ass.GetType("ClassLibrary1.Class1", throwOnError:=True)
' The public String fields in Class1.
Dim strFields As FieldInfo() =
(From f As FieldInfo In t.GetFields(BindingFlags.Instance Or BindingFlags.Public)
Where f.FieldType Is GetType(String)
).ToArray
' A simple iteration over the fields to print their names.
For Each field As FieldInfo In strFields
Console.WriteLine(field.Name)
Next strField
If all your variables are of the same type (here strings), you can use a Dictionary...
Public MyModule
Private myVars As Dictionary(Of String, String)
Public Function CallToAPI(VarName As String) As String
If myVars.ContainsKey(VarName) Then
Return myVars(VarName)
End If
Return ""
End Function
End Module
And somewhere else in your external code
Module TestModule
Public Sub Test()
Dim myVar = MyModule.CallToAPI("test")
End Sub
End Module
Now if your variables aren't the same, then you must use Reflection... and that's where the fun begins...
I have a class with several properties.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
End Class
In my VB.net code, I am assigning a value to each property.
I want to send the whole test class as one parameter, and use all the values inside it.
So that if I add extra parameters later on, I want them to be used dynamically, instead of writing this everytime:
Textbox1.text= test.a & test.b & test.c .......
Any way to do this?
Im not really writing the values in a textbox, but this is just an simplified example.
I think what you want is a property. You'll need to add a property to your class like:
Public Property Combination() As String
Get
Return a & b & c & d & e ...
End Get
End Property
Then to get the value you'd use
Textbox1.text = test.combination
(for more details you can see http://www.dotnetperls.com/property-vbnet)
I recommend you override the built-in ToString function. Also, to further simplify this, add a CType operator.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
Public Shared Widening Operator CType(obj As test) As String
Return If((obj Is Nothing), Nothing, obj.ToString())
End Operator
Public Overrides Function ToString() As String
Return String.Concat(Me.a, Me.b, Me.c, Me.d, Me.e, Me.f, Me.g)
End Function
End Class
The you could just do:
Textbox1.text = test
There is a way to dynamically get and set the value of properties on any object. Such functionality in .NET is collectively referred to as Reflection. For instance, to loop through all of the properties in an object, you could do something like this:
Public Function GetPropertyValues(o As Object) As String
Dim builder As New StringBuilder()
For Each i As PropertyInfo In o.GetType().GetProperties
Dim value As Object = Nothing
If i.CanRead Then
value = i.GetValue(o)
End If
If value IsNot Nothing Then
builder.Append(value.ToString())
End If
Next
Return builder.ToString()
End Function
In the above example, it calls i.GetValue to get the value of the property, but you can also call i.SetValue to set the value of the property. However, reflection is inefficient and, if used inappropriately, it can lead to brittle code. As such, as a general rule, you should avoid using reflection as long as there is any other better way to do the same thing. In other words, you should typically save reflection as a last resort.
Without more details, it's difficult to say for sure what other options would work well in your particular situation, but I strongly suspect that a better solution would be to use a List or Dictionary, for instance:
Dim myList As New List(Of String)()
myList.Add("first")
myList.Add("second")
myList.Add("third")
' ...
For Each i As String In myList
Textbox1.Text &= i
Next
Or:
Dim myDictionary As New Dictionary(Of String, String)()
myDictionary("a") = "first"
myDictionary("b") = "first"
myDictionary("c") = "first"
' ...
For Each i As KeyValuePair(Of String, String) In myDictionary
Textbox1.Text &= i.Value
Next