Programatically setting an object array's object - vb.net

I have
Public stack() As CTest
I want
Public stack() As Object
The latter is giving the error "Unable to cast object of type 'Object' to 'CTest'." when used:
Dim thestack As CTest() = testdatabase.getStack
Where testdatabase.getStack simply returns stack();
Public Function getStack() As Object()
Return stack
End Function
This fixes it, but it's not ideal (for me personally):
Dim thestack As Object() = testdatabase.getStack
So if I could keep the variable as-is (Public stack() As Object) and then do something along the lines of class.stack() = CTest I should be able to do Dim thestack As CTest() = testdatabase.getStack because the object array will programmatically have changed from Object to CTest.
Is this possible at all?

One way to approach this would be to write a conversion function that accepts Object and returns CTest. The function itself would cast Object to the boxed datatype, assign the value of each property on the object to the new CTest object and then return it. Something like this:
Function ConvertToCTest(o as Object) as CTest
dim unboxed as ObjectsDataType
unbox = directCast(o ObjectsDataType)
Dim result as CTest
result.Prop1 = unboxed.Prop1
result.Prop2 = unboxed.Prop2
Return result
End Function

Related

Generic Function and action/return depending on Type

I have a function which deserializes some custom serialization sent by an API.
I want to build a generic function so that the deserialized object is not of type Object but of the correct type.
The strings which contain the serialized object can be deserialized into one of the following types:
A String,
an IList(Of String),
an IDictionnary(Of String),
one of many SomeNameContainer classes, all derived from a
BaseContainer class,
an IList(Of SomeNameContainer), or
an IDictionnary(Of SomeNameContainer).
I would like to have a single Function Deserialize(Of T)(MyString as String) as T.
Inside this function, I tried to run some Select Case T: GetType(String):Etc tests in order to separate the different actions to run on MyString, depending on the expected object to create from the deserialization.
For example, deserializing into a SomeNameContainer is normally done via another generic function: Dim Deserialized as SomeNameContainer = GetFromContainer(SomeNameContainer)(MyString)
However, I get quickly limited, mainly because:
I cannot return a String type, because it is unable to cast it
into T.
String is a value type, whilst SomeNameContainer are classes. So it is not possible to add an (Of T As {New}) constraint. Which means I am unable to do something like Dim NameContainer as New T: If TypeOf NameContainer Is BaseContainer in order to apply the same operation to all the classes derived from BaseContainer.
One track I have found is to use CTypeDynamic(Of T)(obj as object), which casts at run-time. That might fix problem 1, but problem 2 is still on.
Function Deserialize(Of T)(MyString as String) as T
Select Case GetType(T)
Case GetType(String)
Return SomeFunction(String) '<- Only run-time casting allowed: Return CTypeDynamic(Of String)(SomeFunction(String))
Case GetType(IList(Of String)
Return SomeOtherFunction(String)
Case GetType(...)
'...
Case Else
Dim MyContainer as New T '<- Not Allowed to use New
if TypeOf MyContainer Is T then
Return GetFromContainer(Of T)(String)
else
'...
End If
End Select
End Function
I could decide to split each Type into a separate function. I would like to avoid so that I do not end up with 6 functions. That is because I also need to run some other operations on the string before it is deserialized. For the story, the strings come under various encoding/encryption formats. So if I have 4 formats, that is now 4x6=24 functions I would need to deal with.
I would love to have the luxury of encapsulating all the decoding/deserialization into a single function: Dim MyObject as Something = Deserialize(Of Something)(StringFromAPI, MyEncodingEnumOptions.Option42)
Many thanks in advance!
Performing a specific action depending on the type of a specific variable: that feels similar to Overloading, except that here instead of performing the action based on the type of the input variables, it should be base on the type of the output variables.
Unfortunately, it is not possible to overload the TypeName of a generic function. For example, Function MyFunction(Of T as New)(SomeParameter as String) as T and Function MyFunction(Of T as Structure)(SomeParameter as String) as T cannot coexist in the same namespace.
An alternative is to pass the expected output type as an input argument, so that regular overloading can be performed: Sub MyFunction(ByVal SomeParameter as String, ByRef OutputVar as SomeType). Each overload including a different SomeType TypeName.
The output of the "function" is stored into OutputVar, which is passed ByRef and retrieved after running the Sub:
Dim MyObject as Something = Deserialize(Of Something)(StringFromAPI, MyEncodingEnumOptions.Option42)
Becomes
Sub Deserialize(ByRef MyObject as String, ByVal MyString As String, ByVal EncodingOption As MyEncodingEnumOptions)
MyString = SomeDecoding(MyString, EncodingOption)
MyObject = SomeFunction(MyString)
End Sub
Sub Deserialize(ByRef MyObject as IList(Of String), ByVal MyString As String, ByVal EncodingOption As MyEncodingEnumOptions)
MyString = SomeDecoding(MyString, EncodingOption)
MyObject = SomeOtherFunction(MyString)
End Sub
'...
Dim MyObject as Something
Deserialize(MyObject, StringFromAPI, MyEncodingEnumOptions.Option42)
'Now MyObject has been filled with the relevant data.
An alternative is to use late binding / runtime object initilization, using Activator.CreateInstance(Of T). A typical switch over T would then look like:
Public Function GetDeserializedObject(Of T)(ByVal MyString As String) As T
Select Case GetType(T)
Case GetType(String)
Return CTypeDynamic(MyString, GetType(T)) '<-- Runtime Casting
Case Else
If Not MyString.IsDeserializable Then Throw New ArgumentException(String.Format("Unable to deserialize to a {0} object: The provided string is not valid.", GetType(T).ToString))
Select Case GetType(T)
Case GetType(IList(Of String))
Return CollectionString.ToStringList(MyString)
Case Else
Dim MyReturn As T = Activator.CreateInstance(Of T) '<-- Object instantiation to the type provided at Runtim
If TypeOf MyReturn Is BaseContainer Then '<-- Now we can use TypeOf ... Is ... which will return True for all Object derived from BaseContainer
Return Activator.CreateInstance(GetType(T), MyString)
ElseIf TypeOf MyReturn Is IList(Of BaseContainer) Then
Dim MyCollectionString As CollectionString = MyString
Return MyCollectionString.ExportToContainerList(MyReturn.GetType)
Else
Throw New ArgumentException(String.Format("Unable to deserialize to a {0} object: This type of object is not supported.", GetType(T).ToString))
End If
End Select
End Select
End Function

Public member 'ToCSVValue' on type 'Integer' not found for VB Extension method

I am trying to write a ToCSV() extension in VB based on Scott Hanselman's blog. It could be that my C# to VB is not correct, but it all seems right.
I added a module with:
<System.Runtime.CompilerServices.Extension>
Public Function ToCSV(Of T)(items As IEnumerable(Of T)) As String
Try
Dim csvBuilder = New StringBuilder()
Dim properties = GetType(T).GetProperties()
For Each item As T In items
'' Test Code
Dim newline As String = ""
For Each l2 As Reflection.PropertyInfo In properties
' This works
newline &= l2.GetValue(item, Nothing)
' This works too
Dim int As Integer = 1234
Dim s As String = int.ToCSVValue()
'This works
Dim nl = l2.GetValue(item, Nothing)
' This blows up with "Public member 'ToCSVValue' on type 'Integer' not found."
' The Debugger type shows "Object {Integer}" which I assume to mean that the debugger interprets the object as an integer.
nl = nl.ToCSVValue()
Next
' Original code
Dim line As String = String.Join(",", properties.Select(Function(p) p.GetValue(item, Nothing).ToCSVValue()).ToArray())
csvBuilder.AppendLine(line)
Next
Return csvBuilder.ToString()
Catch ex As Exception
Throw
End Try
End Function
<System.Runtime.CompilerServices.Extension>
Private Function ToCSVValue(Of T)(item As T) As String
If item Is Nothing Then
Return """"""
End If
If TypeOf item Is String Then
Return String.Format("""{0}""", item.ToString().Replace("""", "\"""))
End If
Dim dummy As Double
If Double.TryParse(item.ToString(), dummy) Then
Return String.Format("{0}", item)
End If
Return String.Format("""{0}""", item)
End Function
When I call it with something like:
Dim s As String = ctx.Customers.Where(Function(x) x.CustomerID = 123456).Select(Function(x) New With {.CustomerID = x.CustomerID, .CustomerName = x.CustomerName}).ToCSV()
it gets to the function ToCSV just fine. It recognizes the items passed in. It pulls out the first item and sees that there are the 2 fields in it. All good!
The GetValue() works just fine.
If I create a static integer and call ToCSVValue on it, it works fine.
If I create a static string and call ToCSVValue on it, it works fine.
When I call ToCSVValue on the GetValue() I get:
Public member 'ToCSVValue' on type 'Integer' not found.
Likewise, if I have just strings in the dataset, I get:
Public member 'ToCSVValue' on type 'String' not found.
Ideally this would work as it is in the "Original code" section and I can kill all this other test code.
Can anyone tell me what is happening and why the "(Of T)" is not working the get GetValue() types, but it is for the directly cast types?
You need to have 'Option Infer On'.
When I use Option Infer On, it works fine.
If you don't use this, then VB is using 'Object' whenever you leave off the type.
Also, although this isn't causing your problem, the proper conversion of the ToCSV method is:
Public Function ToCSV(Of T As Class)(items As IEnumerable(Of T)) As String
The short answer is that calling it as a method ToCSVValue(p.GetValue(item, Nothing)) will work as in the C# version.
The longer answer is that you can't call extension methods on Object in VB. In VB Object is treated more like dynamic in C#. For example:
<Extension()> Function toStr(Of T)(item As T) As String
Return item.ToString
End Function
then this will result in compile-time Warning "Late bound resolution; runtime errors could occur." and a run-time Error "Public member 'toStr' on type 'Integer' not found.", but it will work in C#:
Dim i As Object = 123
Dim s = i.toStr

How do I copy Array values to a structure

I would like to to copy that values of an array into a Structure.
Example:
' The Array
Dim Columns(2) As String
' The Structure
Private Structure Fields
Public FName As String
Public LName As String
Public Email As String
End Structure
' I would like to map it like so:
Fields.FName = Columns(0)
Fields.LName = Columns(1)
Fields.Email = Columns(2)
Obviously I could write a function if it was so simple, but really there are over 25 columns and it's a pain to write a function that would map it.
Is there some way to do this?
There really is no simple way that will work in all cases. What you are complaining is too much effort is the only way to guarantee that it will work in all cases.
That said, if you can guarantee that the number of elements in the array matches the number of properties/fields in the structure/class and that they are in the same order and of the same types then you could use Reflection in a loop, e.g.
Private Function Map(source As Object()) As SomeType
Dim result As New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return result
End Function
EDIT:
The code I have provided works as is if SomeType is a class but, as I missed the first time around, not for a structure. The reason is that structures are value types and therefore a copy of the original object is being sent to SetValue, so the field value never gets set on that original object. In theory, to prevent a copy being created, you should be able to simply box the value, i.e. wrap it in an Object reference:
Private Function Map(source As Object()) As SomeType
Dim result As Object = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function
As it turns out though, the VB compiler treats that a little differently than the C# compiler treats the equivalent C# code and it still doesn't work. That's because, in VB, the boxed value gets unboxed before being passed to the method, so a copy is still created. In order to make it work in VB, you need to use a ValueType reference instead of Object:
Private Function Map(source As Object()) As SomeType
Dim result As ValueType = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function

Create an object from another objects type

In Visual Basic.net, can I create an Object, or a List of T with the type from another object.
Here is some code:
Dim TestObjectType As Author
Dim TestObject As TestObjectType.GetType
I am getting an error:
TestObjectType.GetType is not defined
EDIT
Can I create a Type object of a certain type, and then create objects, lists or cast objects to this type from this Type object?
Dim TestObject As TestObjectType.GetType will look for a type named GetType in the namespace TestObjectType.
To create an instance of a class using System.Type, you can use Activator.CreateInstance:
Dim TestObject = Activator.CreateInstance(TestObjectType.GetType())
To create a generic list, you can use Type.MakeGenericType:
Dim listType = GetType(List(Of )).MakeGenericType(TestObjectType.GetType())
Dim list = Activator.CreateInstance(listType)
Note that both snippets above return an Object; however, you can make use of generics to achieve compile time safety:
Dim TestObject = CreateNew(TestObjectType)
Dim AuthorList = CreateNewList(TestObjectType)
...
Function CreateNew(Of T As New)(obj As T) As T
Return New T()
End Function
Function CreateNewList(Of T)(obj As T) As List(Of T)
Return New List(Of T)
End Function

Object Reference casting vb.net

I have no idea on how to cast an object that type was 'Object' to a user defined class type.
I have a private instance variable:
Private studyType as Object
What i need to do is to instantiate this object from an event handling method. And no, not to instance new Object().
Basically it would look like this:
studyType = new VCEOnly()
However, I am only allowed to use the Object class subs and functions as the type was defined as Object. So i need to cast it to VCEOnly class type so i can access its subs and functions.
Basically, studyType needs to be casted from Object to VCEOnly. I am not allowed to pre-define studyType as VCEOnly when declared.
you can also use:
dim studyType as Object = new VCEOnly()
...
dim studyTypeVCE as VCEOnly = nothing
if trycast(studytype,VCEOnly) IsNot Nothing then
studyTypeVCE = DirectCast(studytype,VCEOnly)
'... do your thing
end if
the if statement checks if the object can be casted to the wanted type and if so variable of type VCEOnly will be filled in with a cast of studytype.
Use CType to cast an object from one type to another
Something like this should do it:
Dim studyType as Object
Dim studyTypeVCE as New VCEOnly
studyTypeVCE = Ctype(studyType,VCEOnly)
or you can just do this:
With CType(studyType, VCEOnly)
.SomeVCEOnlyProperty = "SomeValue"
End With