.NET Reflection - Retrieve a Property Without Knowing Its Name - vb.net

I've been given a collection of WCF services to use which are all the same, save for the way certain properties of the objects returned by the services are named. If I have services A, B, and C, they each return nearly identical objects except that the objects' properties have been changed to reflect the service from whence they came. So, service A returns an object called responseA with a property responseA.AValidation and B returns a similar object with a property called responseB.BValidation.
Now, as much as might like to make changes to the services, I can't. Which means I'm stuck writing the exact same code for each service all to check for various possible error conditions,
If responseA.AErrors.Length > 0
...
If responseB.BErrors.Length > 0
...
and so on
What I'd love is to write one generic class that can use any of these services and has a single method to perform the above checking on any of them However, I'm stumped on how to check the value of a property when the name of that property can be slightly different from class to class.
So, is it possible to return the value of a property that I don't know the name of until runtime?
Here's what I've tried so far
Dim responseType As Type = responseA.GetType()
For Each Prop In responseType.GetProperties()
If Prop.Name.Contains("ErrorInfoTypes") Then
Dim errorTypes()
errorTypes = Prop.GetValue(GetType(Array), Nothing)
If errorTypes.Length > 0 Then
'Deal with the fact there are errors.
End If
Exit For
End If
Next
This complies, but .GetProperties returns no properties. What am I missing?

Related

Best practice when validating class properties in VBA?

I haven't worked much with classes, so I think this is a beginner's question.
I have a class that has the following property:
Private pAvtalsslut As Date
''''''''''''''''''''''
' Avtalsslut property
''''''''''''''''''''''
Public Property Get Avtalsslut() As Date
Avtalsslut = pAvtalsslut
End Property
Public Property Let Avtalsslut(Value As Date)
pAvtalsslut = Value
End Property
When I set property values to objects of this class I use the following validation in my subs:
If IsDate(exportWks.Cells(r, lColumnAvtalsslut)) Then
avtal.Avtalsslut = exportWks.Cells(r, lColumnAvtalsslut)
End If
I do this because otherwise I would get an error when the cell I read from is empty.
When I get property values from objects of this class I use the following validation in my subs:
If avtal.Avtalsslut <> 0 Then
wUnderlag.Cells(row, 3) = avtal.Avtalsslut
End If
I do this because I don't want to write zeros where there are no dates. I want to leave cells blank in that case.
Now to my question. What are some best practices for these kinds of validations? Should I have themin my class or in my subs? If they should be in my class, how should that look?
(PS.Is validation the correct vocabulary for these kinds of checks?)
Generally speaking, it's best to let the class validate the data before getting or setting a property, and then raise an error if the data doesn't meet specifications. This way you don't have to repeat that validation code all over your code base anytime you use your class.
However, in this case, you're trying to avoid a type mismatch error. So, you would need to change the property type to Variant instead of Date and then you're going to surprise and confuse anyone who uses your class expecting to give/receive dates. This violates the principal of least surprise and makes the class useless if you need to use it for anything but reading/writing to a spreadhsheet.
So, neither. Let's go for option C.
Create a second class (say, MyClassReaderWriter) that has the job of acting as an intermediary between your existing class and the spreadsheet. This allows for two things.
You don't have to make any changes to your existing class, and it remains useful in other contexts.
It dries up your code by placing the logic into one specific place.

Create a "clone" of this object, not point to it

Let's say I got a list called
myFirstList
And then I want to create a copy of that list so I can do some tweaks of my own. So I do this:
mySecondList = myFirstList
mySecondList.doTweaks
But I noticed that the tweaks also affect the myFirstList object! I only want the tweaks to affect the second one...
And afterwards I will want to completely delete mySecondList, so I do mySecondList = Nothing and I'm good, right?
Adam Rackis, I don't like your "Of course it does", because it is not at all obvious.
If you have a string variable that you assign to another string variabe, you do not change them both when making changes to one of them. They do not point to the same physical piece of memory, so why is it obvious that classes do?
Also, the thing is not even consistent. In the following case, you will have all elements in the array pointing at the same object (they all end up with the variable Number set to 10:
SourceObject = New SomeClass
For i = 1 To 10
SourceObject.Number = i
ObjectArray.Add = SourceObject
Next i
BUT, the following will give you 10 different instances:
For i = 1 To 10
SourceObject = New SomeClass
SourceObject.Number = i
ObjectArray.Add = SourceObject
Next i
Apparently the scope of the object makes a difference, so it is not at all obvious what happens.
Here is how you do it:
'copy one object to another via reflection properties
For Each p As System.Reflection.PropertyInfo In originalobject.GetType().GetProperties()
If p.CanRead Then
clone.GetType().GetProperty(p.Name).SetValue(clone, p.GetValue(OriginalObject, Nothing))
End If
Next
in some cases when the clone object got read-only properties you need to check that first.
For Each p As System.Reflection.PropertyInfo In originalobject.GetType().GetProperties()
If p.CanRead AndAlso clone.GetType().GetProperty(p.Name).CanWrite Then
clone.GetType().GetProperty(p.Name).SetValue(clone, p.GetValue(OriginalObject, Nothing))
End If
Next
Since you have not divulged the type of item that you are storing n your list, I assume it's something that's implementing IClonable (Otherwise, if you can, implement IClonable, or figure out a way to clone individual item in the list).
Try something like this
mySecondList = myFirstList.[Select](Function(i) i.Clone()).ToList()
But I noticed that the tweaks also
affect the myFirstList object! I only
want the tweaks to affect the second
one...
Of course it does. Both variables are pointing to the same object in memory. Anything you do to the one, happens to the other.
You're going to need to do either a deep clone, or a shallow one, depending on your requirements. This article should give you a better idea what you need to do
Expanding on Adam Rackies' answer I was able to implement the following code using VB.NET.
My goal was to copy a list of objects that served mainly as data transfer objects (i.e. database data). The first the class dtoNamedClass is defined and ShallowCopy method is added. A new variable named dtoNamedClassCloneVar is created and a LINQ select query is used to copy the object variable dtoNamedClassVar.
I was able to make changes to dtoNamedClassCloneVar without affecting dtoNamedClassVar.
Public Class dtoNamedClass
... Custom dto Property Definitions
Public Function ShallowCopy() As dtoNamedClass
Return DirectCast(Me.MemberwiseClone(), dtoNamedClass)
End Function
End Class
Dim dtoNamedClassVar As List(Of dtoNamedClass) = {get your database data}
Dim dtoNamedClassCloneVar =
(From d In Me.dtoNamedClass
Where {add clause if necessary}
Select d.ShallowCopy()).ToList
Here's an additional approach that some may prefer since System.Reflection can be slow.
You'll need to add the Newtonsoft.Json NuGet package to your solution, then:
Imports Newtonsoft.Json
And given a class type of MyClass, cloning can be as easy as:
Dim original as New MyClass
'populate properties of original...
Dim copy as New MyClass
copy = JsonConvert.DeserializeObject(Of MyClass)(JsonConvert.SerializeObject(original))
So the approach is to first use the JSON converter to serialize the original object, and than take that serialized data and deserialize it - specifying the class type - into the class instance copy.
The JSON converters are extremely powerful and flexible; you can do all sorts of custom property mappings and manipulations if you need something the basic approach above doesn't seem to address.
this works for me:
mySecondList = myFirstList.ToList
clone is the object you are attempting to clone to.
dim clone as new YourObjectType
You declare it like that.

A function that will convert input string to the actual object

I am unsure how to describe what I am looking for, so hopefully the situation will make it somewhat clear.
I have an object with a number of properties (let's say object.one, object.two, object.three). There are about 30 of these properties and they all hold a string ("Pass" or "Fail").
Right now the existing code checks whether the property has value "Pass" or "Fail" and then runs some code that prints stuff out. That is, the same snippet of code is duplicated 30 times, one for each of these properties.
The code looks something like this
If (object.one = ... )
...
End if
If (object.two = ... )
...
End if
If (object.three = ... )
...
End if
I want to use a loop to clean this mess up (each block is huge), but am not sure how to do it. I was thinking perhaps there was a way such that I might be able to construct a string like "object.one" and run some function that will tell the compiler that this is actually an object's property?
That way I could create an array containing the object's name like my array = {"object.one", "object.two", "object.three"} and then do something like, in pseudocode
For each string in my array
If (some_function(string) = ...)
...
End If
Essentially, it would take those massive blocks of duplicated code and reduce it to just one block. Is there such a some_function that I am looking for?
This is in VB.net.
I'm not sure i've understand but you can use reflection and get object properties runtime ?
With reflection you can access public object properties, use PropertyFiled.GetValue() to get one, two, etc. and build an array (i suppose that one, two, etc are object Properties, true?)
Here you can find more information: http://msdn.microsoft.com/en-us/library/system.reflection.aspx
Sorry for my bad english, i'm italian.
You seem to be describing serialization, which is the act of converting object state to a format that can be stored/transmitted and deserialization, which is the opposite.
The .NET framework has several different serializers that can work with text - either XML or JSON - the DataContractSerializer for XML and the DataContractJsonSerizlizer for JSON amongst them.

Using Zope object unique id ( _p_oid ) to access object itself

Every Zope object has it's own unique id ( _p_oid ).
To convert it into integer value:
from Shared.DC.xml.ppml import u64 as decodeObjectId
oid = decodeObjectId(getattr(<Object instance>, '_p_oid'))
Is it possible to get object itself having it's _p_oid?
I tried this:
from ZODB.utils import p64
object = <RootObject instance>._p_jar[p64(oid)]
But it seems it's a wrong way because after getting object I can't change any property and object.absolute_url() returns empty string.
This should work, as long as the class of the object you're trying to load is available in the Python environment, and as long as your oid isn't from another database mounted somewhere within the root.
Can you describe the way in which this is failing to work for you?
See whether the following works (it should get the root object, which has _p_oid == 0):
>>> object = root_obj._p_jar[p64(0)]
You can access the object just fine that way, but you get an unwrapped object.
In Zope, the object is normally retrieved via traversal, and every next object you retrieve this way is wrapped in the correct acquisition context. This context tells every object what it's parent object is, and this is in turn used to calculate the object's absolute URL and it's security context.
You would be better off using the Zope intid facilities (via it's five.intid integration layer); that gives you a unique integer ID for each object, and the utility not only keeps track of the object but also of it's path, so you can get the object back with the correct context.
As far as I know, the correct way to do it is to use the get method of the connection instance:
>>> db = DB(...)
>>> conn = db.open()
>>> obj = conn.get(oid)
EDIT: it seems that dbroot._p_jar is an ZODB.Connection.Connection object just like the return type of db.open() so perhaps it can be assumed that both ways are equivalent. Arguably, conn.get(...) seems cleaner as it does not involve accessing underscore-prefixed methods.

Type casting in VB .NET

I am adding a feature to an existing VB .Net application that involves retrieving data from a .Net web service. The web service returns an array of Locations. A Location is pretty simple, it has 3 properties – an integer and two strings.
So that the rest of my application does not have to be dependent on this web service, I would like to create my own Location type within my application. My thought is I could call a method that returns a generic list of my Location type which internally calls the web service and populates the list I return. That way, if the data source for Locations changes in the future to something other than the web service, I only have to fix the method instead of fixing all the callers.
So I created my own Location that has identical properties as the service Location. But I don’t seem to be able to cast the array of service locations into a generic list of my locations. I also tried casting a single service Location into one of my Locations and that didn’t work either.
So is casting an option or do I have to loop through each service Location and assign each property to a new one of my Locations? Or is there a completely different way to go about this?
By default you will not be able to cast one Location to another. They are completely unrelated types and thus cannot be the subject of casting. You can make it possible to cast though by defining a custom operator for the application version of CType.
' Location for application
Public Class Location
...
Public Shared Operator Widening CType(p1 as Namespace.Of.WebService.Location) As Location
Dim loc = ConvertWebServiceLocationToApplicationLocation
return loc
End Operator
End Class
This allows you to now do a CType operation between a WebService Location the Application Location.
Casting the array though, simply won't be possible. There is no way to define a conversion operator for arrays so they can't use the above trick. But you can write a quick and dirty function for this purpose
Public Shared Function ConvertArray(ByVal arr As Namespace.Of.WebServiec.Location()) As Location()
Dim newArray(arr.Length) As Location
For i as Integer = 0 To arr.Length - 1
newArray(i) = CType(arr(i), Location)
Next
return newArray
End Function
Casting will not work because these are not the same type. Even if two types look exactly the same, you cannot cast from one to the other, unless you define a CType operator which describes how to transform an object from one type into an object from another type.
Even then, you cannot cast a List(of Type1) into a List(Of Type2) directly.
You will have to loop through and create a new object of your class.