Can you pass a "type" as an argument? - vb.net

I want to do something like the following in VB.NET, is it possible?
Function task(value as Object, toType as Type)
Return DirectCast(value, toType)
End Function

Yes. There is System.Type. You may actually want to do a Generic however.
Function SomeFunction(Of T)(obj As Object) As T
'' Magic
End Function

Great Answer - Here is a generic function to do the same:
Public Sub BindListControlToEnum(Of T)(ListCtrl As ListControl)
Dim itemValues As Array = System.Enum.GetValues(GetType(T))
Dim itemNames As Array = System.Enum.GetNames(GetType(T))
For i As Integer = 0 To itemNames.Length - 1
Dim item As New ListItem(itemNames(i), itemValues(i))
ListCtrl.Items.Add(item)
Next
End Sub
Call it like this:
BindDropdownToEnum(Of MyClass.MyEnum)(MyRadioButtonListControl)

you want to use the
function task(of myType)(value as myType) as MyType
''stuff
return value
end function

Yes, though, depending on your requirements, you may want to use CType to do any type casting/conversion. CType will work so long as there is a valid type conversion, whereas DirectCast requires that value be of the type toType.

I had to do something similar today (essentially filling in the '' magic from the accepted answer):
Private Function Convert_Value_Or_Fallback(Of T)(ByRef value As Object, ByRef fallback As Object) As Object
Try
Return DirectCast(value, T)
Catch ex As Exception
Return fallback
End Try
End Function
'call it like this:'
Convert_Value_Or_Fallback(Of Double)(value, 0)

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

How to set a generic return type

I read values from a cell and the return is not fix. Instead of writing many functions for each return type I try to write a function with a generic return type but I stuck with this:
Public Function GetValueFromCells(Of t)(ByVal CellName As String) As t
Return CType(_xlWorksheet.Range(CellName).Value2)
End Function
No matter what type I will find in the cell, I want to convert the value to the return type I set in the function. But all my attemps have been in vain. It would be great if anyone could help me get this accomplished.
It's been awhile since I touched VB.Net, but you should be able to do this:
Public Function GetValueFromCells(Of t)(ByVal CellName As String) As t
Return CType(_xlWorksheet.Range(CellName).Value2, t)
End Function
I would address a couple of things.
Use more descriptive Generic Type arguments and just declare a type of the Generic and return that.
Public Function GetValueFromCells(Of TCellValue)(ByVal cellName As String) As TCellValue
Dim specificValue As TCellValue = Nothing
specificValue = DirectCast(_xlWorksheet.Range(cellName).Value2, TCellValue)
Return specificValue
End Function

Function return string or boolean

I want a function to return a String or Boolean. Something like this:
Public Function GetString(Byval What As String) 'As... someting?
If (What = "A") Then
Return "String to return"
Else if (What = "B") Then
Return True
End If
Return False 'Nothing to return
End Function
How can i now do this? If i ask like
If GetString("A") Then
MsgBox(GetString())
End IF
...it returns a string and of course it gives an error on converting string to bool.
I could always return strings and checks it lengths, but it feels bad. Or maybe I'm just into PHP too much?
But is there a way to do it more like this? If i ask for "B" i know it would return a bool, if i ask for "A" i want to alert the string if there was any and so on.
How can i now do this?
You can't.
A function can only return one type, not multiple.
You can return a custom type that contains a string and a boolean.
I would use an Array list. You can store whatever type you need in the list and then parse it on the return. This is really not best practice as explained above, but when you gotta get things done... The end justifies the means. Not recommended.
Public Function GetString(Byval What As String) As ArrayList
Dim b as boolean = True
dim myArrayList as Arraylist = New ArrayList
If (What = "A") Then
ArrayList.Add("String to return")
Else if (What = "B") Then
ArrayList.Add(b)
End If
Return False 'Nothing to return
End Function
Proof of concept below:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim a As Boolean = True
Dim myarraylist As ArrayList = New ArrayList
myarraylist.Add(a)
myarraylist.Add("g")
Debug.WriteLine(myarraylist.GetType.ToString)
Debug.WriteLine(myarraylist(0).GetType.ToString)
Debug.WriteLine(myarraylist(1).GetType.ToString)
If myarraylist(0).GetType.ToString = "System.string" Then
Debug.WriteLine("Function returned a String")
ElseIf myarraylist(0).GetType.ToString = "System.boolean" Then
Debug.WriteLine("Function returned a Boolean")
End If
End Sub
You can return Object, but it is considered very bad form for a function to return 2 data types.
As Oded said, you can't return more than one parameter from a function.
It is not too clear what you are doing from your code example, but you may look into passing parameters by reference. As pointed out in the answer there, passing a parameter by reference is useful for:
when you want to return a state or status of an operation plus the result from the operation.
This is how int.TryParse and similar methods work.

List(of Int32).Contains([int64 value]) doesn't work because of disimilar types

I have a list of integers that have been deserialized by WCF from json. This list deserializes as a list(of int32). I don't have any control over how it deserializes.
I need to be able to see if this list contains a value that happens to be an Int64. It obviousy doesn't work. Here is a sample of the function:
Private Shared Function IsIn(aPropertyValue As Int64, aList As IList) As Boolean
Return aList.Contains(aPropertyValue)
End Function
The reason I'm passing IList is because I don't want to have to create this function 12 times, once for each numeric type, byte through uint64. If I did create it 12 times, acutally 12 x 12 times for every possible option, I guess I could use List(of T).Exists().
The documentation for Contains() says it uses IEquatable(Of T).Equals to perform the comparison. I have to think that an Int32 and an Int64 with the same value can be compared and be found to be equal.
I must be missing something obvious.
Try this:
Private Function IsIn(ByVal aPropertyValue As Long, ByVal list As IList) As Boolean
Dim genericListType As Type = CType(list, Object).GetType().GetGenericArguments()(0)
Dim convertedPropertyValue As Object = Nothing
Try
convertedPropertyValue = Convert.ChangeType(aPropertyValue, genericListType)
Catch
End Try
If convertedPropertyValue IsNot Nothing Then
Return list.Contains(convertedPropertyValue)
Else
Return False
End If
End Function

VB.NET Generic Function

What I want to do is, based on the type of T do different opperations. Below is a simple example of my problem.
Public Shared Function Example(Of T)() As T
Dim retval As T
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
ElseIf TypeOf retval Is Integer Then
Dim myInt As Integer = 101
retval = myInt
End If
Return retval
End Function
I get the error "Value of Type 'String' Cannot be converted to 'T'" Same with the integer part. If I cast either to an object before asigning them to retval it works but I think that would defeat my purpose and be less efficient. Any Ideas? Thanks!
It's probably a bit late, but try this:
Public Shared Function CAnyType(Of T)(ByRef UTO As Object) As T
Return CType(UTO, T)
End Function
Public Shared Function ExecuteSQLstmtScalar(Of T)(ByVal strSQL As String) As T
Dim T_ReturnValue As T
' Here we have the result of a DB query '
Dim obj As Object = "Value from DB query cmd.ExecuteScalar"
Dim strReturnValue As Object = obj.ToString();
Try
Dim tReturnType As Type = GetType(T)
If tReturnType Is GetType(String) Then
Return CAnyType(Of T)(strReturnValue)
ElseIf tReturnType Is GetType(Boolean) Then
Dim bReturnValue As Boolean = Boolean.Parse(strReturnValue)
Return CAnyType(Of T)(bReturnValue)
ElseIf tReturnType Is GetType(Integer) Then
Dim iReturnValue As Integer = Integer.Parse(strReturnValue)
Return CAnyType(Of T)(iReturnValue)
ElseIf tReturnType Is GetType(Long) Then
Dim lngReturnValue As Long = Long.Parse(strReturnValue)
Return CAnyType(Of T)(lngReturnValue)
Else
MsgBox("ExecuteSQLstmtScalar(Of T): This type is not yet defined.")
End If
Catch ex As Exception
End Try
Return Nothing
End Function
(the secrect is casting your generic result to object, then casting from type Object to template type T).
PS:
You are responsible to ensure that your code works correctly with nullable types and NOT nullable types, as well as System.DbNull.Value. For example when string is NULL and return value type is Boolean (not nullable). On a sidenote, please also note that VB Nothing is NOT equal NULL, it's equal to C#'s default(T) (e.g. System.Guid.Empty for Guid)
With a generic method, T will be of exactly one type each time. Let's say that you have code calling Example(Of Integer). Now, in your mind, replace T with Integer. The resulting method will contain these lines (amongst others).
Dim retval As Integer
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
' more code follows '
Assigning a String to an integer like that will never work. Sure, that code will also never execute, since the If-block prevents that, but the code will still not compile. (As a side not, the above code will fail to compile because the TypeOf keyword is restricted to use with reference types, but that is another story)
Typically when creating generic methods, you will want to do the same thing with whatever input you get, but in a type safe manner. If you want to have different behavior for different types of input, you are usually better off by overloading the methods instead.
retVal = (T) "Hello World!"
Do retval = Ctype(Mystring, T) or retVal = Ctype(MyInt, T)
An alternative solution is encapsulate this kind of logic in a class and use VB CallByName function:
Class Aux(Of T)
Public Value As T
Private dicc As Dictionary(Of String, Object)
Sub New()
dicc = New Dictionary(Of String, Object)
dicc.Add("system.string", "hola")
dicc.Add("system.int32", 15)
dicc.Add("system.double", 15.0)
End Sub
Public Function Test() As T
Dim typeName As String = GetType(T).ToString.ToLower
If dicc.ContainsKey(typeName) Then
CallByName(Me, "Value", CallType.Set, dicc(typeName))
End If
Return Value
End Function
Protected Overrides Sub Finalize()
MyBase.Finalize()
If Not (dicc Is Nothing) Then dicc.Clear()
dicc = Nothing
End Sub
End Class