vb.net list class properties with values - vb.net

I have a class, and I want to do a custom 'toString' function;
Public Class Person
public property Name as string
public property Age as interger
public Overrides Function ToString() as string
dim BigStr as string = ""
for each Member as MemberInfo in Me.GetType.GetMembers
bigst += Member.Name & " " & [thevalue of this instance]
next
return BigStr
end function
end class
I want it to automatically display all properties with the value of the current instance. But I don't know how to get the value of property without specifically type it out. Is there a dynamic way?

Type.GetMembers returns a list of MemberInfo objects, one per member of the type. However, not all members have values. Fields and properties have values, so if you get the list of just the fields or just the properties, you can ask them for their values. But things like methods don't have a value. You may be able to invoke them and read their return values, but that's different from reading the value of a property or a field.
In other words, you have to work differently with each member, depending on what kind of member it is. Since MemberInfo is the lowest common-denominator, it doesn't have any of the functionality which only works on some of the members. If you want the additional functionality available to you, you'll need to use one of the more specific methods like GetProperties or GetFields.
Since your class contains properties, you probably want to get the list of properties:
Public Class Person
Public Property Name As String
Public Property Age As Integer
Public Overrides Function ToString() As String
Dim bigStr As String = ""
For Each p As PropertyInfo In Me.GetType().GetProperties()
bigStr &= p.Name & " " & p.GetValue(Me)?.ToString()
Next
Return bigStr
End Function
End Class

Related

Using Widening/Narrowing to Cast One Custom Class to Another

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?

Vb.Net setting properties of user control

I have a custom user control & I am looking to set some of its properties from the designer. The properties will be coming from a structure. Here is the current code
Private fooList As Foo_structure
Public Structure Foo_structure
Public Property a As Integer
Public Property b As Integer
Public Property c As Extras
End Structure
Public Structure Extras
Public Property precision As Integer
Public Property light As String
End Structure
Public Property foo As Foo_structure
Get
Return fooList
End Get
Set(ByVal value As Foo_structure)
fooList = value
End Set
End Property
I need to be able to set the properties of the Foo_structure from the designer properties panel like the eg shown in the image below.
You are going to need a TypeConverter to collapse foo into a string; and convert back from it. The nested Type means you need to write another one for Extras. You will probably need to use some attributes to handle designer persistence.
To start, I think you need to change at least Foo_structure to a Class, otherwise there is no way to add code to instance Extras (also no way to create a Foo instance). This should get you started (changed some names):
' Foo converted to Class:
<TypeConverter("FooItemConverter")>
Public Class FooBar
<DefaultValue(0)>
<DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)>
Public Property Foo As Integer
<DefaultValue(0)>
<DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)>
Public Property Bar As Integer
<EditorBrowsable(EditorBrowsableState.Always)>
<NotifyParentProperty(True)>
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
Public Property Ex As Extras
Public Sub New(a1 As Integer, b2 As Integer)
Foo = a1
Bar = b2
Ex = New Extras ' do not want NOTHING flying about
End Sub
End Class
DefaultValue does not do what you may think it does. It tells the IDE to serialize the value for a property when the current value does not equal the Default. DesignerSerializationVisibility tells VS to save the value for a property. Foo and Bar both need these.
Ex/Extra is different. NotifyParentProperty allows FooBar to be notified when a Extra property value has changed so the IDE window is updated, internal "DirtyFlag" set etc; DesignerSerializationVisibility.Content tells VS that we know we cant save Ex as a value, so save the contents.
Then comes the FooItemConverter. This will be the thing that displays the string you want in the Props window AND creates a Foo item from that string:
Friend Class FooItemConverter
Inherits ExpandableObjectConverter
' tells the IDE what conversions it can handle:
Public Overrides Function CanConvertTo(context As ITypeDescriptorContext,
destType As Type) As Boolean
If destType = GetType(String) Then
' Yes I Can
Return True
End If
' Probably have to also say YES to an InstanceDescriptor
Return MyBase.CanConvertTo(context, destType)
End Function
After that a ConvertTo function is used to convert foo to a string. Something like this:
Public Overrides Function ConvertTo(context As ITypeDescriptorContext,
culture As Globalization.CultureInfo,
value As Object, destType As Type) As Object
If destType = GetType(String) Then
Dim f As FooBar = CType(value, FooBar)
Return String.Format("{0}, {1}, {2}",
f.foo.ToString,
f.bar.ToString,
f.Ex.ToString)
' outputs: X, Y, <ex>
' where Ex is what we use in the ExtraItemConverter
End If
Return MyBase.ConvertTo(context, destType)
End Function
If ExtraItemConverter.ConvertTo use a format of "({0} / {1})" then the control contents will show as: F, B, (P / L) where F=Foo, B=Bar etc.
To make it work, you need 4 procedures: CanConvertTo, ConvertTo, CanConvertFrom, ConvertFrom all responding to string. You probably will be able to just use the <DefaultValue> attribute for persistence.
FooItemConverter.ConvertFrom will have to know how to create an object from that string. Normally, that is done like this:
' parse the string you made and create a Foo
Dim els As String() = str.Split(","c)
Return New myFoo(Convert.ToInt32(els(0)), Convert.ToInt32(els(1)))
Note that the 3rd element is ignored as that is actually for the ExtraItemConverter to handle. That converter would be very similar.
So, you will first have to decide whether to cling to the structure or use a class (another pro for a Class is that 99.999% of the examples you find will be Class based). These guys know a lot about TypeConverters.

.net - Using Class as one parameter

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

Let property of VBA class modules - is it possible to have multiple arguments? [duplicate]

This question already has answers here:
How do optional Parameters in Let/Get Properties work?
(3 answers)
Closed 7 years ago.
My understanding of using the Let property in a class module so far is that you set it up in the class modules like this:
Dim pName as String
Public Property Let Name(Value As String)
pName = Value
End Property
And then you after you've created an object of this class you can set this property like so:
MyObject.Name = "Larry"
Question: Is it possible to somehow enter multiple arguments into a class property? For instance:
Dim pFirstName as String, pLastName as String
Public Property Let Name(FirstName As String, LastName As String)
pFirstName = FirstName
pLastName = LastName
End Property
How would you then go about setting this property outside the class?
MyObject.Name = ??
Or is this just plain not possible to do?
I realise there already are 2 workarounds but I thought answering your original question was worth giving a shot due to the amount of views this question is receiving.
The answer to
Is it possible to have multiple arguments in the Let Property in VBA?
is
YES! It's possible.
First let's talk about the GET property. Consider this being the Class1
Private firstName as String
Private lastName as String
Public Property Get Name() As String
Name = firstName & " " & lastName
End Property
The Name property will return the full name, that's great but how to use the Let property to assign firstName & lastName in one go?
Side note: You could pass a single string separated with a special character and split it inside the body of the Let and assign the first and last names but forget that, let's get it done properly...
OK, in VBA the default Let property for the current setup would take 1 parameter and assign it to either first or last name...
Something like:
Public Property Let Name(value As String)
firstName = value
End Property
Rule: the Get takes no parameters and the Let takes one. That is very logical because the Get returns the underlying value but the Let needs to grab a value from somewhere in order to assign it to the data it's representing. Worth remembering at this point that the Let property is assigned via the = sign ie. myObject.Name = "IdOnKnuu"
we know that if we stay consistent with the above rule, theoretically we should be able to add one parameter to the Get and one more to the Let.
Let's forget the Let - comment it out for now - and add a parameter to the Get property.
Private firstName As String
Private lastName As String
Public Property Get Name(first As String) As String
Name = firstName & " " & lastName
End Property
'Public Property Let Name(value As String)
' firstName = value
'End Property
Going back to the Module1 create an instance of Class1 and type c.Name(
oh, intelli-sense expecting something? Awesome!
At this point it's worth understanding that our Get property returns first + last which are both empty at the moment, so it doesn't matter what you are going to pass to the c.Name() it will return an empty string.
Ok, let's uncomment and tweak the Let property now...
Side node: if you jump back to Module1 and type c. and don't get intelli-sense it pretty much means that something in Class1 is broken... We already know - it's the Let property that's causing it
We have added a parameter to the Get property, let's do the same with the Let property...
Public Property Let Name(first As String, value As String)
firstName = value
End Property
Let's go back to the Module1 and try to use the Let property:
Remember, how you have used the Let property before? You needed to assign it a value
c.Name = "John"
But now, your Let property is expecting an extra parameter first as String.
Why don't we try this:
c.Name("John") = "Smith"
Hey! that compiles and runs (a quick F5)
Great, let's check the results!
Debug.print c.Name("John")
Uhm... that only shows Smith in the Immediate Window (Ctrl+G)... Not exactly what we were looking for....
Going back to the Let property we notice that we are grabbing 2 values via arguments but we only make use of one of them? We have the 2 different values coming in to the function, right? Let's treat the first one as the firstName and the second one as lastName
Public Property Let Name(first As String, last As String)
firstName = first
lastName = last
End Property
Going back to Module1
Sub Main()
Dim c As New Class1
c.Name("John") = "Smith"
Debug.Print c.Name("John")
End Sub
and re-running out current code gives us what we need ... it prints John Smith but wait!!! why do we have to pass the first name in order to retrieve the full name?
Ha! The trick to this is to make the first parameters of both properties Optional
Summarising, the code:
Class1.cls
Private firstName As String
Private lastName As String
Public Property Get Name(Optional first As String) As String
Name = firstName & " " & lastName
End Property
Public Property Let Name(Optional first As String, last As String)
firstName = first
lastName = last
End Property
Module1.bas
Sub Main()
Dim c As New Class1
c.Name("John") = "Smith"
Debug.Print c.Name ' prints John Smith
End Sub
So basically the assignment of two (or more) values through the Let property is possible in VBA. The thing that may throw you off a bit is the Syntax for it but I hope my explanation in this answer has helped you understand where things come from and why.
The Get property takes an optional parameter - it's really just a dummy parameter... it's not used anywhere within the Get property but it's allowing us to get the desired Let signature and allows us to pass two parameters to it. It also grants the easy to remember c.Name syntax.
The call
Debug.Print c.Name("first")
is still possible, however the "first" parameter just acts like a dummy and has no effect on the actual underlying data. It's a dummy and has no effect on the actual data - dump it and use:
Debug.print c.Name
definitely more convenient :)
As per your comment if you would prefer to encapsulate this logic then you can use something similar to the below.
Below includes the sub and function. The function returns a Person object:
Public Sub testing()
Dim t As Person
Set t = PersonData("John", "Smith")
End Sub
Public Function PersonData(firstName As String, lastName As String) As Person
Dim p As New Person
p.firstName = firstName
p.lastName = lastName
Set PersonData = p
End Function
Person Class:
Dim pFirstName As String, pLastName As String
Public Property Let FirstName(FirstName As String)
pFirstName = FirstName
End Property
Public Property Get FirstName() As String
FirstName = pFirstName
End Property
Public Property Let LastName(LastName As String)
pLastName = LastName
End Property
Public Property Get LastName() As String
LastName = pLastName
End Property
Use as Public Sub procedure within the class to perform this:
Public Sub testing()
Dim myPerson As New Person
myPerson.SetName "KaciRee", "Software"
End Sub
Person Class:
Dim pFirstName As String, pLastName As String
Public Property Let FirstName(FirstName As String)
pFirstName = FirstName
End Property
Public Property Get FirstName() As String
FirstName = pFirstName
End Property
Public Property Let LastName(LastName As String)
pLastName = LastName
End Property
Public Property Get LastName() As String
LastName = pLastName
End Property
Public Sub SetName(FirstName As String, LastName As String)
pFirstName = FirstName
pLastName = LastName
End Sub

How can I get a property name for a type without the need to instantiate an object of that type?

I have a requirement where I need to have a "type safe" way of accessing property names, without actually instantiating an object to get to the property. To give an example, consider a method that takes as arguments a list of IMyObject and a string that represents a property name (a property that exists in IMyObject).
The methods implementation will take the list and access all the objects in the list using the property name passed... for some reason or another, we won't dwell on that!!
Now, I know that you can do this using an instantiated object, something like ...
Dim x as MyObject = nothing
Dim prop As PropertyInfo = PropHelper.GetProperty(Of MyObject)(Function() x.MyProperty)
Where my helper method uses reflection to get the name of the property as a string - there are numerous examples of this flying around on the web!
But I don't want to have to create this pointless object, I just want to do something like MyObject.MyProperty! Reflection allows you to iterate through a types properties and methods without declaring an object of that type... but I want to access a specific property and retrieve the string version of its name without iteration and without declaring an object of that type!
The main point here is that although I am trying to get the property name as a string... this is done at run time... at compile time, I want this to be type safe so if someone changes the property name, the compilation will break.
Can anyone help in this quest!?!
So here is a quick code-listing to demonstrate the answer that I was looking for:
Imports System.Linq.Expressions
Public Class A
Public Prop1 As String
Public Prop2 As Integer
End Class
Public Class Form1
Public Function GetPropertyNameB(Of TModel, TProperty)(ByVal [property] As Expression(Of Func(Of TModel, TProperty))) As String
Dim memberExpression As MemberExpression = DirectCast([property].Body, MemberExpression)
Return memberExpression.Member.Name
End Function
Public Sub New()
InitializeComponent()
Dim propertyName As String = GetPropertyNameB(Function(myObj As A) myObj.Prop1)
Dim propertyName2 As String = GetPropertyNameB(Function(myObj As A) myObj.Prop2)
MsgBox(propertyName & " | " & propertyName2)
End
End Sub
End Class
You may be able to pass the property in as a simple lamdba expression, and take it in the method as an expression tree. You should be able to analyze the expression tree to get the string name of the property, but it the lambda expression will fail to compile if the property name changes. Check out this page for more details:
http://msdn.microsoft.com/en-us/library/bb397951.aspx
You can make use of the NameOf function:
Dim fieldName = nameOf(MyClass.MyField)