I have a set of classes, whose properties have data annotations on them. Some of these class properties are of primitive types (and by primitive, I also mean types such as string, double, datetime etc), while others are properties of a custom type.
I would like to be able to iterate through the properties of a class and the properties of the nested objects and pull out the attributes of each property. I’ve played around with reflection and my code works fine, if the class under consideration has only one property of a custom type.
However when a class has multiple properties of a custom type and each of those properties have other custom types, I am completely lost on how I’d keep track of the objects/properties that have already been visited.
This is where I have got so far. I have seen a lot of examples on the forum, but they all have a simple nested class, where there is a maximum of one custom type per class.
Below is a sample of what I am trying to get done:
Public Class Claim
<Required()>
<StringLength(5)>
Public Property ClaimNumber As String
<Required()>
Public Property Patient As Patient
<Required()>
Public Property Invoice As Invoice
End Class
Public Class Patient
<Required()>
<StringLength(5)>
Public Property MedicareNumber As String
<Required()>
Public Property Name As String
<Required()>
Public Property Address As Address
End Class
Public Class Address
Public Property Suburb As String
Public Property City As String
End Class
Public Class Invoice
<Required()>
Public Property InvoiceNumber As String
<Required()>
Public Property Procedure As String
End Class
Public Shared Function Validate(ByVal ObjectToValidate As Object) As List(Of String)
Dim ErrorList As New List(Of String)
If ObjectToValidate IsNot Nothing Then
Dim Properties() As PropertyInfo = ObjectToValidate.GetType().GetProperties()
For Each ClassProperty As PropertyInfo In Properties
Select Case ClassProperty.PropertyType.FullName.Split(".")(0)
Case "System"
Dim attributes() As ValidationAttribute = ClassProperty.GetCustomAttributes(GetType(ValidationAttribute), False)
For Each Attribute As ValidationAttribute In attributes
If Not Attribute.IsValid(ClassProperty.GetValue(ObjectToValidate, Nothing)) Then
ErrorList.Add("Attribute Error Message")
End If
Next
Case Else
Validate(ClassProperty.GetValue(ObjectToValidate, Nothing))
**** ‘At this point I need a mechanism to keep track of the parent of ClassProperty and also mark ClassProperty as visited, so that I am able to iterate through the other properties of the parent (ObjectToValidate), without revisiting ClassProperty again.**
End Select
Next
End If
Return Nothing
End Function
The most straightforward (and probably easiest) way to approach this is to keep a Dictionary of class property attributes keyed by class name.
If I were approaching this, I would probably create a class to hold the property attributes:
Public Class PropertyAttribute
Public PropertyName As String
Public PropertyTypeName As String
Public Required As Boolean
Public StringLength As Integer
End Class
Then create a class to hold information about each class' properties:
Public Class ClassAttributes
Public ClassName As String
' You could also use a dictionary here to key properties by name
Public PropertyAttributes As New List(Of PropertyAttribute)
End Class
Finally, create a dictionary of ClassAttributes to keep track of which custom classes you have already processed:
Public ProcessedClasses As New Dictonary(Of String, ClassAttributes)
The key for the dictionary is the classname.
When you are processing the attributes through reflection, if the property type is custom, check the dictionary for the existence of the class. If it is there, you don't have to process it.
If it is not there, add a new instance to the dictionary immediately (so that nested objects of the same type are safely handled) and then process the attributes of the class.
Related
Let's take a very short example of a class like this:
Public Class The_Class1
Public Property ID As Integer
Public Property Property1_Integer As Integer
Public Property Property2_Single As Single
End Class
Somewhere else, I have a dictionary containing instances of The_Class1, like this:
Public Dictionary_Class1 As New Dictionary(Of Integer, The_Class1)
I want to perform an operation over Property1_Integer on all of the members inside Dictionary_Class1. Also, I want to perform the very same operation over Property2_Single, so I would like to create a function to perform such operation, and somehow instruct VB to use a given property on every call.
Can you think of an elegant way to do that?
Edit: Let's say, for example, that the operation that I want to perform is the sum of every Property1_Integer or Property2_Single of the members inside the dictionary. What I really really want to do is to determine if all of the values are the same, or if there is at least one that is different.
You can use Reflection, but it's not as clean as you may imagine. Here's some skeleton code you can adapt to your needs:
Public Class The_Class1
Public Property ID As Integer
Public Property Property1_Integer As Integer
Public Property Property2_Single As Single
End Class
Private Sub SetProperty1_Integer()
Dim myClassInstance As New The_Class1
Dim myType As Type = GetType(The_Class1)
Dim propertyInfo As System.Reflection.PropertyInfo = myType.GetProperty("Property1_Integer")
propertyInfo.SetValue(myClassInstance, 1)
MessageBox.Show(myClassInstance.Property1_Integer.ToString)
End Sub
Have fun!
I want to save a communication inside a class. After that I plan to serialize the class o a XML file, where all datapoints are decoded between a tag.
Therefore I want to explain my communication protocol first.
The message Frame Looks like the following
LIE01
LIE02
When the communication ends, I have around 3000 of this telegrams inside a raw variable.
Here I describe the Messages:
LIE01: Header + 1 data word
LIE02: Header + 2 data words
My idea was to decode the frame and save it in a list (or array) of structures that are public properties of my class.
Public Class Com
Public Structure sLIE01
Public Property Header As Int16
Public Property data1 As Int16
End Structure
Public Structure sLIE02
Public Property Header As Int16
Public Property data1 As Int16
Public Property data2 As Int16
End Structure
Public Property LIE01 As List(Of sLIE01)
Get
?
End Get
Set(ByVal value As List(Of sLIE01))
?
End Set
End Property
Public Property LIE02 As List(Of sLIE02)
Get
?
End Get
Set(ByVal value As List(Of sLIE02))
?
End Set
End Property
End Class
Unfortunatelly I am more a beginner than an expert, so that I have no idea, how to write the code to Set or Get a specific LIE message.
Even I'm not sure, whether my way is a common way for this purpose or not.
You could use auto implemented properties in your code and skip getters and setters altogether (https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/procedures/auto-implemented-properties). You'll then be able to assign lists to them like:
Dim newList as new List(of sLIE01)()
ComInstance.Lie01 = newList
You can also operate on those list properties directly (just make sure you initialize them in class constructor to avoid NullReferenceException):
Dim lie as sLie01
ComInstance.Lie01.Add(lie)
Also consider replacing structures with classes: https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/structures-and-classes
If you still want to use Get Set it would look like this...
Private _LIE01 As List(Of sLIE01)
Public Property LIE01 As List(Of sLIE01)
Get
Return _LIE01
End Get
Set(value as List(Of sLIE01))
_LIE01 = value
End Set
End Property
Been working a lot with custom classes lately and I love the power you can have with them but I have come across something that I'm not able to solve and/or find anything helpful online.
I have a list of a class with properties I'm looking to only store information pulled from a database into.
Public Class CustomClass
Public _Values As String
Public _Variables As String
Public ReadOnly Property Values() As String
Get
Return _Values
End Get
End Property
Public ReadOnly Property Variables() As String
Get
Return _Variables
End Get
End Property
Sub New(ByVal values As String, ByVal variables As String)
_Values = values
_Variables = variables
End Sub
End Class
I will be iterating through some database entries, and I'm looking to store them into the appropriate property when I hit them (since I won't have them all available immediately, which is part of my problem). I want to just be able to add either the value or the variable at a time and not both of them, but since I have the sub procedure 'New' passing two arguments, it will always require passing them both. I've found the only way around this is by making them optional fields which I don't feel is the right way to solve this. Is what I'm looking to do possible with a class or would it be simpler by using a structure?
You can overload the constructor:
Friend Class Foo
' using auto-implement props:
Public Property Name As String ' creates a _Name backing field
Public Property Value as Integer
Public Sub New(newN as String, newV as Integer)
' access "hidden" backing fields if you want:
_Name = newN
_Value = newV
End Sub
Public Sub New() ' simple ctor
End Sub
Public Sub New(justName As String)
' via the prop
Name = justName
End Sub
End Class
You now have 3 ways to create the object: with full initialization, partial (name only) or as a blank object. You will often need a "simple constructor" - one with no params - for other purposes: serializers, Collection editors and the like will have no idea how to use the parameterized constructors and will require a simple one.
If rules in the App were that there was no reason for a MyFoo to ever exist unless both Name and Value being defined, implementing only the New(String, Integer) ctor enforces that rule. That is, it is first about the app rules, then about coding convenience.
Dim myFoo As New Foo ' empty one
myFoo.Name = "ziggy" ' we only know part of it
Since the default of string is nothing, you could pass nothing for the value you don't have. IE
Collection.Add(New CustomClass("My Value",Nothing))
Every type has a default, so this works with more than just strings.
Can I please have some help to perform a deep copy of an object.
Here is my code:
Option Explicit On
Option Strict On
<Serializable> Public Class [Class]
Private _Name As String
Private _ListOfFields As New List(Of Field)
Public Property Name As String
Get
Return _Name
End Get
Set(value As String)
_Name = value
End Set
End Property
Public Property ListOfFields As List(Of Field)
Get
Return _ListOfFields
End Get
Set(value As List(Of Field))
_ListOfFields = value
End Set
End Property
Public Function Clone() As [Class]
Return DirectCast(Me.MemberwiseClone, [Class])
End Function
End Class
Field is a Class that I have written myself as well.
What do I need to modify for the Clone() Function to return a deep copy?
You can create a clone of any class by calling this helper function:
Function DeepClone(Of T)(ByRef orig As T) As T
' Don't serialize a null object, simply return the default for that object
If (Object.ReferenceEquals(orig, Nothing)) Then Return Nothing
Dim formatter As New BinaryFormatter()
Dim stream As New MemoryStream()
formatter.Serialize(stream, orig)
stream.Seek(0, SeekOrigin.Begin)
Return CType(formatter.Deserialize(stream), T)
End Function
This works by serializing all the information from your class into a portable object and then rewriting it in order to sever any reference pointers.
Note: The passed in class and any other classes it exposes as properties must be marked <Serializable()> in order to use BinaryFormatter.Serialize
If you want to make your own class expose the clonable method itself, you can add the method and implement the ICloneable interface like this:
<Serializable()>
Public Class MyClass : Implements ICloneable
'NOTE - The Account class must also be Serializable
Public Property PersonAccount as Account
Public Property FirstName As String
Function Clone(ByRef orig As MyClass) As MyClass Implements ICloneable.Clone
' Don't serialize a null object, simply return the default for that object
If (Object.ReferenceEquals(orig, Nothing)) Then Return Nothing
Dim formatter As New BinaryFormatter()
Dim stream As New MemoryStream()
formatter.Serialize(stream, orig)
stream.Seek(0, SeekOrigin.Begin)
Return CType(formatter.Deserialize(stream), T)
End Function
End Class
Note: Be aware ICloneable comes with it's share of controversies as it does not indicate to the caller if it is performing a deep or shallow clone. In reality, you don't need the interface to be able to add the method to your class.
(As an aside, I probably would name your class something other than "Class").
If you wanted to do it all by hand you would need to follow steps like:
Ensure that your Field class also implements a deep copy Clone() method. If you haven't done this already, then this would likely involve its Clone() method creating a new object of type Field and then populating each of its properties based on the current object. If your Field class has properties which are other classes/complex types (e.g. classes you have created yourself) then they should also implement Clone() and you should call Clone() on them to create new deep copies
In your Clone() method for the class you would create a new object of type [Class], e.g. by calling its constructor
Set the Name property of the new object to the Name property of your current object
Create a new List(Of Field), let's call it listA for the sake of example
Iterate over your current list and assign a clone of each list item to listA. For example:
For Each item in _ListOfFields
listA.Add(item.Clone())
End
After that you can assign your new list (listA) to the object you have created in the Clone() method
There is an alternative (probably better) by-hand approach that is in VB.NET described here.
If you wanted to cheat a bit then you could just serialize your existing object and then deserialize it into a new object like the technique here
I would say the serialize then deserialize technique is the "easiest" one.
I have a webservice that is wrapped up by a data access object and is accessed by many different UI controls.
The proxy objects look something like this:
Public Class WebProxyObject1
' Common properties, there are about 10 of these
Public Name As String
Public Address As String
' Specialized properties there are about 20 of these
Public count As Integer
End Class
The DAL layers look something like this:
Public Class DataAccessObject
Implements IDataAccessObject
' These are called in MANY, MANY, MANY locations
Public Function GetObject(ByVal name As String) As WebProxyObject1 Implements IDataAccessObject.GetObject
' Makes call to a webservice
Return New WebProxyObject1
End Function
Public Function ListObjects() As System.Collections.Generic.List(Of WebProxyObject1) Implements IDataAccessObject.ListObjects
' Makes call to a webservice
Dim list As New List(Of WebProxyObject1)
Return list
End Function
End Class
Now, I need to add a 2nd webservice to the mix. The goal is to reuse the UI controls that are currently coded to use the Proxy Objects that come from the first webservice. There are about 10 common properties and about 20 that are different. To add the 2nd webservice, I'd create a 2nd DAL object that implements the same interface. The problem is that it currently returns the proxies from the first webservice.
My thought on how to solve this is to extract an interface from each of the proxy objects and mash them together. Then implement the new interface on both proxy objects. That will create a huge class/interface where some properties aren't used. Then have the DAL return the interface.
The problem that I'm facing isn't a real bug or an issue, but extracting the 2 interfaces and smashing them together just feels kind of wrong. I think it would technically work, but it kind of smells. Is there a better idea?
The resulting interface would look like this:
Public Interface IProxyObject
' Common
Property Name() As String
Property Address() As String
' Specialized
Property Count() As Integer
Property Foo() As Integer
End Interface
Create a base class for your WebProxyObjects to inherit from.
Public MustInherit Class WebProxyObjectBase
' Common properties
Public Property Name As String
Public Property Address As String
End Class
Next create your two WebProxyObjects:
Public Class WebProxyObject1
Inherits From WebProxyObjectBase
' Specialized properties
Public Property count As Integer
End Class
Public Class WebProxyObject2
Inherits From WebProxyObjectBase
' Specialized properties
Public Property foo As Integer
End Class
Next have your DAL return the Base Class:
Public Class DataAccessObject
Implements IDataAccessObject
' These are called in MANY, MANY, MANY locations
Public Function GetObject(ByVal name As String) As WebProxyObjectBase Implements IDataAccessObject.GetObject
' Makes call to a webservice
Return New WebProxyObjectBase
End Function
Public Function ListObjects() As System.Collections.Generic.List(Of WebProxyObjectBase) Implements IDataAccessObject.ListObjects
' Makes call to a webservice
Dim list As New List(Of WebProxyObjectBase)
Return list
End Function
End Class
Then when calling your DataAccessObject you'll be able to ctype the return to the proper class:
Dim DAO as New DataAccessObject
Dim Pxy1 as WebProxyObject1 = TryCast(DAO.GetObject("BOB"), WebProxyObject1)
If Pxy1 IsNot Nothing Then
'Do stuff with proxy
End If