Cast array in Object variable to type in System.Type variable - vb.net

I have this function:
Public Sub DoStuff(ByVal type as System.Type, ByVal value as Object)
End Sub
The 'value' argument is always an array of the same type as 'type'. How can I loop through the values of the array?
I'd like to be able to do something like this:
DoStuff(GetType(Integer), New Integer(){1,2,3})
Public Sub DoStuff(ByVal type as System.Type, ByVal value as Object)
//Strongly types arr as Integer()
Dim arr = SomeCast(type, value)
For Each i in arr
//Do something with i
Next
End Sub
Edit Ok, I think I'll add more details so you can see what I'm trying to do. I have an object that captures values coming back from another page. Once I have them captured, I want to loop through the 'Values' property. So DoStuff() above would be called for each dictionary object in 'Values'. If the value in the dictionary objct is an array I want to loop through it as well. I was saving the type added through the generic function call as a System.Type, but maybe that's not the way to go. How can I write this so I can save the type of the array and loop through the array later?
Public Class PopUpReturnValues
Implements IPopUpReturnValues
Public Sub AddValue(Of T As Structure)(ByVal name As String, ByVal value As T) Implements IPopUpReturnValues.AddValue
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(T), .Value = value, .IsArray = False})
End Sub
Public Sub AddArray(Of T As Structure)(ByVal name As String, ByVal values As T()) Implements IPopUpReturnValues.AddArray
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(T), .Value = values, .IsArray = True})
End Sub
Public Sub AddStringValue(ByVal name As String, ByVal value As String) Implements IPopUpReturnValues.AddStringValue
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(String), .Value = value, .IsArray = False})
End Sub
Public Sub AddStringArray(ByVal name As String, ByVal values As String()) Implements IPopUpReturnValues.AddStringArray
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(String), .Value = values, .IsArray = True})
End Sub
Private _values As New Dictionary(Of String, PopUpReturnValue)
Public ReadOnly Property Values() As IDictionary(Of String, PopUpReturnValue)
Get
Return _values
End Get
End Property
Public Class PopUpReturnValue
Public UnderlyingType As Type
Public Value As Object
Public IsArray As Boolean
End Class
End Class

Comments moved to answers per OP
Your "do something" code in based on the type I assume, String vs Int vs Apple, it would need to handle all three types with an If statement. Just include an overload for those three types, you don't actually need to pass the type information. However, if its just calling ToString() then just use an Object array.
And if you don't like overloads, just use the TypeOf operator to inspect the values of the array. When you throw an Integer into an Object array, its still an Integer, just a boxed one.

Is the type known at compile time? If so, perhaps you could use Generics.

You can provide an Action, like this:
Public Sub DoStuff(ByVal value As Array, ByVal process As Action(Of Object) )
For Each item In value
process(item)
Next item
End Sub
Then you just need a method that takes one parameter for each of the types you care about and knows how to cast object to that type. Then call DoStuff() passing in the address of that method. You could even use a lambda if you wanted.

Related

Create Custom Class Dynamically

I am working on a project where I need to create a multitude of custom classes to interact properly with an API (While I know there might be questions on why, and such, but the short is it has to be this way).
Is there a way to create a complete custom class dynamically on the fly? So instead of
class person
Private _Height
Property Height As Integer
Get
Return _Height
End Get
Set(value As Integer)
_Height = value
End Set
End Property
'Continue for all properties of person
I would like to be able to create a new object and through other input create this dynamically.
dim NewClass as object
dim NewProperty as property
NewProperty.name="Height"
NewProperty.datatype=string
NewClass.AddProperty(NewProperty)
Is this possible? It would save me a lot of time if it is.
I don't like late binding but there are options (I like my option strict on). Like using the DynamicObject or the ExpandoObject class. Your question is vague so I have no idea if it can work.
Sub Main()
Dim test As Object = New SampleDynamicClass()
test.SomeProperty = "123"
Console.WriteLine(test.SomeProperty)
Console.ReadLine()
End Sub
Public Class SampleDynamicClass
Inherits DynamicObject
Private _values As New Dictionary(Of String, String)
Public Sub New()
End Sub
Public Function GetPropertyValue(ByVal propertyName As String) As String
Return _values(propertyName)
End Function
Public Function SetPropertyValue(ByVal propertyName As String, ByVal value As Object) As Boolean
If _values.ContainsKey(propertyName) Then
_values(propertyName) = value.ToString()
Else
_values.Add(propertyName, value.ToString())
End If
Return True
End Function
Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder,
ByRef result As Object) As Boolean
result = GetPropertyValue(binder.Name)
Return If(result Is Nothing, False, True)
End Function
Public Overrides Function TryInvokeMember(ByVal binder As InvokeMemberBinder,
ByVal args() As Object,
ByRef result As Object) As Boolean
result = GetPropertyValue(binder.Name)
Return If(result Is Nothing, False, True)
End Function
Public Overrides Function TrySetMember(binder As SetMemberBinder, value As Object) As Boolean
Return SetPropertyValue(binder.Name, value)
End Function
Dim person = New With {Key .Height = 12}
Dim personTypes = New With {Key .Happy = 1, .Sad = 2}
Dim personsAndTypes = New With {Key .Person = person, .Type = personTypes}
The question is kind of vague, but if you have no need for other fields and methods, or reuse Anonymous Types

Property Set not fire when deserialize(xml) a list of

I got a class containing integer,bool,string and list (of integer). Each variable that need to be serialize in that class has a public property. When i deserialize my class via XmlSerializer the public property of each variable is call. Except the variable that are list of. The List of varaibles are weel deserialisze but the property setter isn't called.
Here is the property :
Public Property Ana_Offset As List(Of Integer)
Get
Return _Ana_Offset
End Get
Set(value As List(Of Integer))
Tmp_Val = _Ana_Offset
_Ana_Offset = value
RaiseEvent VariableChanged(_Ana_Offset, Tmp_Val, "_Ana_Offset", 0)
End Set
End Property
The class is something like this
<Serializable()> Public Class SACCVar
Private _Code_Produit As String
Private _Ana_Offset As New List(Of Integer)
Public Event VariableChanged(ByVal Val As Object, ByVal Old_Val As Object, desc As String, index As Integer)
End Class
The strange fact i just realise while posting is that i got no Set "event" but the get event is fired wit no data return, but for other variable the get isn't fired..?
Thanks for help

Detecting or preventing assignment operator to a class

Is there any way to make a class can be only initialized at declaration.
Public Class AnyValue
Private value As Int32
Public Sub New(ByVal aValue As Int32)
value = aValue
End Sub
End Class
'I want to be able to do this:
Dim val As New AnyValue(8)
'But not this.
val = New AnyValue(9)
Or it is possible to stop the assignment or detect when the operator = is used.
Lets just say this - No, you can't do what you want. The closest thing to it that I can think of, is to hide the constructor and give static access to the consumer as follows:
Public Class AnyValue
Private value As Int32
Private Sub New(ByVal aValue As Int32) ' Note - private constructor
value = aValue
End Sub
Public Shared Function Create(ByVal aValue As Int32) As AnyValue
Return New AnyValue(aValue)
End Function
End Class
'This will not work
Dim val As New AnyValue(8)
'This will not work
val = New AnyValue(9)
' This will work
Dim val As AnyValue = AnyValue.Create(8)
Now, if you look at this method of object creation, you can see that you can set all sort of rules for object construction. So, the client has very little input on the construction itself because how you construct the object is totally controlled by the object itself.

VBA OOP How can I make subproperties

How can I program my class module so that I can call properties on properties?
I'm not sure I'm using the right terminology, so I will try to clarify. In MsAccess whenever I want to manipulate elements on a form I can reference them using a period to separate each objects. For example, if I wanted to change the value of a text box I can call:
form("formname").txtboxname.value = "new value"
So it's like I have a form object that has a textbox object that has a value object.
How could I achieve this in my own class module.
My specific example is that I have stored an array in a private variable in the class however I cannot simply use a Property GET to return an array. (And I don't want to make it public because the array is programmatically populated) But if I want to iterate I need to know the Ubound and Lbound value of that array.
I would rather avoid having to store the Ubound and Lbound values in their own variable as that seems a waste.
How could I somehow program the class in order to get a ?subclass?
so that if I want the ubound or lbound I could call something like
set x = mycls
debug.? x.pArrayVariable.getLBound
Even the right terminology for what I'm trying to do could get me closer to an answer, I've tried searching for properties and sub properties but I'm not sure that's getting me somewhere.
Example of my class: mycls
Private pArrayVariable() as string
public property get pArrayVariable() as string
'Run Code to Populate array here
Array() = pArray()
end property
Is something called "Collections" what I'm asking about?
So a property can return an object (like a user class) which has its own properties. Example below:
Here is the code for a class called MinMax
Private m_min As Integer
Private m_max As Integer
Public Property Get MinValue() As Integer
MinValue = m_min
End Property
Public Property Let MinValue(ByVal x As Integer)
m_min = x
End Property
Public Property Get MaxValue() As Integer
MaxValue = m_max
End Property
Public Property Let MaxValue(ByVal x As Integer)
m_max = x
End Property
Public Sub SetMinMax(ByVal min_value As Integer, ByVal max_value As Integer)
m_min = min_value
m_max = max_value
End Sub
Private Sub Class_Initialize()
m_min = 0
m_max = 1
End Sub
and here is the code for a class named MyClass. Notice how it exposes a property of type MinMax
Private m_target As MinMax
Private m_name As String
Public Property Get Target() As MinMax
Target = m_target
End Property
Public Property Get Name() As String
Name = m_name
End Property
Private Sub Class_Initialize()
Set m_target = New MinMax
m_name = vbNullString
End Sub
Public Sub SetValues(ByVal a_name As String, ByVal min_value As Integer, ByVal max_value As Integer)
m_name = a_name
m_target.SetMinMax min_value, max_value
End Sub
Now the main code can have a statement like
Public Sub Test()
Dim t As New MyClass
t.SetValues "Mary", 1, 100
Debug.Print t.Target.MinValue, t.Target.MaxValue
End Sub
I still am curious about my original question above, however it came out of a problem of not being able to access the array. Seems I am incorrect.
You can use
Public Property Get ArrayVariable() As String()
Call 'code to populate array
ArrayVariable= pArrayVariable() 'Notice the paren here
End Property
And then to reference the array
debug.? ubound(clsvar.ArrayVariable()) 'Notice paren here too
or
debug.? clsvar.ArrayVariable()(1) 'Notice the parens here too

VB.NET CType: How do I use CType to change an object variable "obj" to my custom class that I reference using a string variable like obj.GetType.Name?

The code below works for the class that I hard coded "XCCustomers" in my RetrieveIDandName method where I use CType. However, I would like to be able to pass in various classes and property names to get the integer and string LIST returned. For example, in my code below, I would like to also pass in "XCEmployees" to my RetrieveIDandName method. I feel so close... I was hoping someone knew how to use CType where I can pass in the class name as a string variable.
Note, all the other examples I have seen and tried fail because we are using Option Strict On which disallows late binding. That is why I need to use CType.
I also studied the "Activator.CreateInstance" code examples to try to get the class reference instance by string name but I was unable to get CType to work with that.
When I use obj.GetType.Name or obj.GetType.FullName in place of the "XCCustomers" in CType(obj, XCCustomers)(i)
I get the error "Type 'obj.GetType.Name' is not defined" or "Type 'obj.GetType.FullName' is not defined"
Thanks for your help.
Rick
'+++++++++++++++++++++++++++++++
Imports DataLaasXC.Business
Imports DataLaasXC.Utilities
Public Class ucCustomerList
'Here is the calling method:
Public Sub CallingSub()
Dim customerList As New XCCustomers()
Dim customerIdAndName As New List(Of XCCustomer) = RetrieveIDandName(customerList, "CustomerId", " CustomerName")
'This code below fails because I had to hard code “XCCustomer” in the “Dim item...” section of my RetrieveEmployeesIDandName method.
Dim employeeList As New XCEmployees()
Dim employeeIdAndName As New List(Of XCEmployee) = RetrieveIDandName(employeeList, "EmployeeId", " EmployeeName")
'doing stuff here...
End Sub
'Here is the method where I would like to use the class name string when I use CType:
Private Function RetrieveIDandName(ByVal obj As Object, ByVal idPropName As String, ByVal namePropName As String) As List(Of IntStringPair)
Dim selectedItems As List(Of IntStringPair) = New List(Of IntStringPair)
Dim fullyQualifiedClassName As String = obj.GetType.FullName
Dim count As Integer = CInt(obj.GetType().GetProperty("Count").GetValue(obj, Nothing))
If (count > 0) Then
For i As Integer = 0 To count - 1
'Rather than hard coding “XCCustomer” below, I want to use something like “obj.GetType.Name”???
Dim Item As IntStringPair = New IntStringPair(CInt(CType(obj, XCCustomers)(i).GetType().GetProperty("CustomerId").GetValue(CType(obj, XCCustomers)(i), Nothing)), _
CStr(CType(obj, XCCustomers)(i).GetType().GetProperty("CustomerName").GetValue(CType(obj, XCCustomers)(i), Nothing)))
selectedItems.Add(Item)
Next
End If
Return selectedItems
End Function
End Class
'+++++++++++++++++++++++++++++++
' Below are the supporting classes if you need to see what else is happening:
Namespace DataLaasXC.Utilities
Public Class IntStringPair
Public Sub New(ByVal _Key As Integer, ByVal _Value As String)
Value = _Value
Key = _Key
End Sub
Public Property Value As String
Public Property Key As Integer
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCCustomer
Public Property CustomerId As Integer
Public Property CustomerName As String
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCCustomers
Inherits List(Of XCCustomer)
Public Sub New()
PopulateCustomersFromDatabase()
End Sub
Public Sub New(ByVal GetEmpty As Boolean)
End Sub
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCEmployee
Public Property EmployeeId As Integer
Public Property EmployeeName As String
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCEmployees
Inherits List(Of XCEmployee)
Public Sub New()
PopulateEmployeesFromDatabase()
End Sub
Public Sub New(ByVal GetEmpty As Boolean)
End Sub
End Class
End Namespace
From MSDN
CType(expression, typename)
. . .
typename : Any expression that is legal
within an As clause in a Dim
statement, that is, the name of any
data type, object, structure, class,
or interface.
This is basically saying you can't use CType dynamically, just statically. i.e. At the point where the code is compiled the compiler needs to know what typename is going to be.
You can't change this at runtime.
Hope this helps.
Since List(Of T) implements the non-generic IList interface, you could change your function declaration to:
Private Function RetrieveIDandName(ByVal obj As System.Collections.IList, ByVal idPropName As String, ByVal namePropName As String) As List(Of IntStringPair)
And then your troublesome line would become (with also using the property name parameters):
Dim Item As IntStringPair = New IntStringPair(CInt(obj(i).GetType().GetProperty(idPropName).GetValue(obj(i), Nothing)), _
CStr(obj(i).GetType().GetProperty(namePropName).GetValue(obj(i), Nothing)))
Of course, you could still have the first parameter by Object, and then attempt to cast to IList, but that's up to you.
ctype is used to convert in object type.