Calling a method on a Object says "disallows late binding" - vb.net

I have a calls called DtaDate that stores an integer "key", a string name, a string for the date, and a Date object that is created from that string.
I have some code that needs to accept a date-like object. I'd like the user to be able to pass in anything date like - a Date object which I'll extract the information from, another DtaDate, a string with the date in it, or even the key, which I'll use to look up the DtaDate from a collection.
So I have this:
Friend Sub New(NameIn As String, DateFormulaIn As String, Optional FromDateIn As Object = Nothing)
[stuff that works]
[check that we got a FromDateIn...]
If TypeOf FromDateIn Is DtaDate Then
fdk = FromDateIn.Key
Ans.FromDate = fdk
VB tells me that "Option Strict On disallows late binding". The other cases, where a string or integer is the TypeOf, I use CInt or CStr. But this is the first time I've actually run into a case where the casting is a non-base type. What's the trick?

Your signature defines FromDateIn As Object. Later on, you're trying to treat it as a DtaDate by calling FromDateIn.Key. As far as the compiler knows, FromDateIn is just an Object, and doesn't have a Key property, and option strict prohibits late binding.
It looks like you're checking the type of FromDateIn and then acting based on that, so all you need to do is cast FromDateIn to a DtaDate. There are a few ways to do that.
fdk = CType(FromDateIn, DtaDate).Key
fdk = DirectCast(FromDateIn, DtaDate).Key
fdk = TryCast(FromDateIn, DtaDate).Key
DirectCast will convert the given variable if it is of the given type, or inherits or implements it.
CType does the same, but will also check to see if there is a conversion from its current type to the specified type. Both of these will throw an InvalidCastException on failure.
TryCast only works for reference types, and works like DirectCast, but returns Nothing on failure rather than throwing an exception.
Another alternative would be to provide three separate constructors, each of which takes a strongly typed variable, e.g.
Friend Sub New(NameIn As String, DateFormulaIn As String)
'...handle case where no date is provided
End Sub
Friend Sub New(NameIn As String, DateFormulaIn As String, FromDateIn As DateTime)
'...handle case where a DateTime is passed in
End Sub
Friend Sub New(NameIn As String, DateFormulaIn As String, FromDateIn As DtaDate)
'...handle case where a DtaDate is passed in
End Sub
Friend Sub New(NameIn As String, DateFormulaIn As String, FromDateIn As String)
'...handle case where a string is passed in
End Sub
This would be more work, but is also safer because the type being passed to the constructor can be checked at compile-time rather than at run-time.

I would use the TryCast() method. If it can cast the source object to the specified type then it will, otherwise it sets it to Nothing.
Inside the If block reference the DtaFromDate, which will be the FromDateIn cast to DtaDate.
Friend Sub New(NameIn As String, DateFormulaIn As String, Optional FromDateIn As Object = Nothing)
[stuff that works]
[check that we got a FromDateIn...]
Dim DtaFromDate As DtaDate
TryCast(FromDateIn, DtaDate)
If DtaFromDate IsNot Nothing Then
fdk = DtaFromDate.Key
Ans.FromDate = fdk
End If

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 can a Sub update it parameters?

I have some simple code and I understand what it does but not why. I have a Sub and it calls another Sub called CheckIfNothing(oList). oList is a List(Of String). The Sub CheckIfNothing checks each String and if it it Nothing it will make it "". This is the code:
Public Function GiveList(oList As List(Of String))
CheckIfNothing(oList)
Return oList
End Function
Public Sub CheckIfNothing(oList As List(Of String))
For Each s As String In oList
If s Is Nothing Then
s = ""
End If
Next
End Sub
So in GiveList I call CheckIfNothing and I don't return anything from CheckIfNothing and still, the oList in GiveList has no Strings that are Nothing.
I always thought you had to return the value you changed in the called function and set the value again in the sub you call the function in like this: oList = CheckIfNothing(oList). CheckIfNothing would be a function in this case.
Why isn't this necessary, and is this only in VB.NET or also the case in C#?
Maybe this will help explain your question. It is from MSDN regarding Visaul Basic 2013.
When passing an argument to a procedure, be aware of several different distinctions that interact with each other:
•Whether the underlying programming element is modifiable or nonmodifiable
•Whether the argument itself is modifiable or nonmodifiable
•Whether the argument is being passed by value or by reference
•Whether the argument data type is a value type or a reference type
For more information, see Differences Between Modifiable and Nonmodifiable Arguments (Visual Basic) and Differences Between Passing an Argument By Value and By Reference (Visual Basic).
This code is an example of how you can use () around your parameter to protect it from being changed.
Sub setNewString(ByRef inString As String)
inString = "This is a new value for the inString argument."
MsgBox(inString)
End Sub
Dim str As String = "Cannot be replaced if passed ByVal"
' The following call passes str ByVal even though it is declared ByRef.
Call setNewString((str))
' The parentheses around str protect it from change.
MsgBox(str)
' The following call allows str to be passed ByRef as declared.
Call setNewString(str)
' Variable str is not protected from change.
MsgBox(str)
Passing Arguments by Value and by Reference (Visual Basic) 2013

ByRef in VB.NET

I have written the following code in VB.NET:
Dim obj As Object
obj = "00"
Test(obj)
MsgBox(obj)
Private Sub Test(ByRef num As Integer)
End Sub
Private Sub Test(ByVal num As Integer)
End Sub
When the value "00" is passed "ByRef" in the method "Test" it converts to 0. But if the value "00" is passed "ByVal" it keeps the same value as "00". How the passed value is being converted only depending of the signature?
In VB6 although the default passing type is "ByRef", still the same code keeps the same value("00")
Could anybody explain the reason behind this contradictory behaviour in VB6 and VB.NET?
The way you are doing it, the ByRef changes the type of the object from string to integer. By default, integer do not have trailling "0" when covnerted to strings.
This example below might help you understand what is hapenning.
Sub Main()
Dim o1 As Object = "00"
Dim o2 As Object = "00"
Console.WriteLine(o1.GetType().ToString())
Test1(o1)
Console.WriteLine(o1.GetType().ToString())
Console.WriteLine(o2.GetType().ToString())
Test2(o2)
Console.WriteLine(o2.GetType().ToString())
Console.ReadLine()
End Sub
Sub Test1(ByVal num As Integer)
End Sub
Sub Test2(ByRef num As Integer)
End Sub
Output
System.String
System.String
System.String
System.Int32
I suggest you always turn Option Strict On, this will remove a lot of confusion.
The object is of type System.String. It cannot be passed ByRef to a method, it is of the wrong type. So the compiler has to work around it and rewrites the code:
Dim obj As Object
obj = "00"
Dim $temp As Integer
$temp = CInt(obj)
Test($temp)
obj = $temp '' <=== Here
MsgBox(obj)
The indicated statement is the one that changes the object from a string to an integer. Which, converted again to a string by the MsgBox() call, produces "0" instead of "00".
Notable is that C# does not permit this and generate a compile error. This rewriting trick is rather nasty, if the method itself changes the original object then you'll have a very hard time guessing what is going on since that doesn't change the passed argument value.
ByRef means that value passes by reference and in function will be used the same value what has been sent.
ByVal means that value passes by value (function creates a copy of passed value) and you use only copy of value.

vb.net Loop through public structure and pass to generics

I have inherited some god awful vb.net code and having trouble trying to work out the generics for looping through the current structure in place.
here is a snippet of the struct
Public Structure ApplicationDetails
Dim ID As Integer
Dim AgentID As Integer
Dim ApplicationDate As Date
Dim CompletedDate As Date
here is the madness to populate it
With ApplicationInfo
.ID = If(Not IsDBNull(DT(0)("ID")), DT(0)("ID"), Nothing)
.ApplicationDate = If(Not IsDBNull(DT(0)("ApplicationDate")), DT(0)("MortgageAmount"), Nothing)
.CompletedDate = If(Not IsDBNull(DT(0)("CompleteDate")), DT(0)("MortgageAmount"), Nothing)
now i want to do something like this:
For Each item In ApplicationInfo.GetType().GetProperties()
Dim thisType = item.GetType()
Dim name = item.Name
Dim value = DtItem(Of item.GetType())(0, name.ToString(), DT)
item.SetValue(item, value, Nothing)
Next
Private Function DtItem(Of T)(ByVal num As Integer, ByVal name As String, ByRef DT As DataTable) As T
Return If(Not IsDBNull(DT(num)(name)), DT(num)(name), Nothing)
End Function
but i am not sure on the syntax to set the value and when trying to get the type i get item.GetTYpe() is not declared. I know i must be on the right track, just missing a little something.
A number of issues.
First and foremost, SetValue only works on object types, not values, so you would have to change your Structure to a Class.
Public Class ApplicationDetails
End Class
Next, you are looping through properties but your "structure" only had fields. So you need to add properties:
Public Class ApplicationDetails
Private _ID As Integer
Property ID As Integer
Get
Return _ID
End Get
Set(ByVal value As Integer)
_ID = value
End Set
End Property
//' etc
End Class
Otherwise, you would have to work with GetFields.
I don't think Generics will work here since you are only dealing with objects and you don't know the type (despite reflection):
Private Function DtItem(ByVal num As Integer, ByVal name As String, ByRef DT As DataTable) As Object
Return If(Not IsDBNull(DT(num)(name)), DT(num)(name), Nothing)
End Function
Lastly, your reflection call is wrong. Try changing it to this:
For Each item As PropertyInfo In ApplicationInfo.GetType().GetProperties
Dim value As Object = DtItem(0, item.Name, _dt)
If item.CanWrite Then
item.SetValue(ApplicationInfo, value, Nothing)
End If
Next
I'm not sure doing this through reflection is gaining you anything. In your "madness" example, it looks like you might be trying to put in a MortgageAmount, which I assume is a decimal, into a date field. That might be needed to look at.
I think you need to rethink your functional decomposition there. You're not getting much out of moving one line into a function, and I don't think you'll be able to pass a type constructor to a generic the way you are attempting to do so. You're also confusing some of your type checks.
Try something along these lines instead:
For Each item In ApplicationInfo.GetType().GetProperties()
Dim theName As String = item.Name,
isNullable = Not item.PropertyType.IsValueType OrElse _
Nullable.GetUnderlyingType(item.PropertyType) IsNot Nothing
If item.CanWrite Then
If Not IsDBNull(DT(0)(theName))
item.SetValue(ApplicationInfo, DT(0)(theName), Nothing)
ElseIf isNullable
item.SetValue(ApplicationInfo, Nothing, Nothing)
End If
End If
Next
If your code is executed immediately after the object is initialized, the isNullable checks are extraneous and you could simply take no action as the properties will be initialized as null. Otherwise, I'd recommend performing the check so you don't attempt to assign Nothing to a value type, which is going to throw an exception. Alternatively, you could modify your structure to use nullable reference types, i.e.:
Public Structure ApplicationDetails
Property ID As Integer?
Property AgentID As Integer?
Property ApplicationDate As Date?
Property CompletedDate As Date?
End Structure
EDIT
As LarsTech points out, your structure members are not properties and this will not function as anticipated unless you modify your structure to indicate these fields are in fact properties, for which the compiler would automatically generate getters and setters.

How to properly use Nullable w/ numeric types in constructors?

In VB.NET, I have a class that implements a range of numbers, call it NumericRange(Of T). Internally, NumericRange stores T as a Nullable, T?. I have another class that wraps this class as NumericRange(Of UInt16). Call this class MyNumRange (I'm being simplistic here).
So In MyNumRange, I have a few constructors defined:
Public Sub New(ByVal num1 As UInt16?, ByVal num2 As UInt16?)
' Code
End Sub
Public Sub New(ByVal num As UInt16, ByVal flag As Boolean)
' Code
End Sub
Public Sub New(ByVal num As UInt16)
' Code
End Sub
In some code outside of MyNumRange, I try to instantiate an open-ended range. That is, a range value where one of the operands is missing to represent a greater-than-or-equal to scenario. I.e., Calling New MyNumRange(32000, Nothing) should equate (after calling MyNumRange's overridden ToString method) to 32000 ~ (note the trailing space, and assume ~ is the delimiter).
Except, calling New MyNumRange(32000, Nothing) doesn't jump to the constructor with a signature of New(UInt16?, UInt16?), but to New(UInt16?, Boolean) instead. This causes NumericRange to process the number 32000 as a single, specific value, not the open-ended range.
My question is, how can I use the constructors as I have them defined above in such a way that I can pass a Nothing value to the second argument of the New(UInt16?, UInt16?) constructor, it gets translated into Nothing, and num2.HasValue, if called from within the constructor, would report False?
Do I need to rethink how I have my constructors set up?
The default constructor of Nullable<T> can be utilized. When called as new Nullable<UInt16>(), it will act as a nullable with no value. In VB terms, you should be able to do New Nullable(of UInt16)().
DirectCast(Nothing, UInt16?) will give you the value you want to pass in, but doing so produces a compiler error:
Overload resolution failed because no accessible 'New' can be called without a narrowing conversion:
'Public Sub New(num As UShort, flag As Boolean)': Argument matching parameter 'num' narrows from 'Short' to 'UShort'.
'Public Sub New(num As UShort, flag As Boolean)': Argument matching parameter 'flag' narrows from 'UShort?' to 'Boolean'.
'Public Sub New(num1 As UShort?, num2 As UShort?)': Argument matching parameter 'num1' narrows from 'Short' to 'UShort?'.
However, it works fine if you use pass in an explicitly-typed value:
Dim num1 As UInt16? = 32000S
Dim r = New MyNumRange(num1, DirectCast(Nothing, UInt16?))