I'm trying to use the following code to check for a DBNull and set the variable to nothing if it is, or a short if it isn't. The problem is it is failing to set the variable to Nothing and sets it to a 0 instead. Anybody know why?
variable = If(currentRow.Item("variable") Is DBNull.Value,
Nothing, CShort(currentRow.Item("variable")))
If variable is declared As Short? then the code works with a slight tweak: you need to cast either operand of If to the target type first:
variable = If(condition, CType(Nothing, Short?), CShort(…))
(You could also have cast the third operand instead, or both.)
This cast is necessary because of how If deduces types: if the two result types mismatch, a common type is deduced which is the closest parent type, i.e. a type from which both inherit. However, with Nothing, new rules come into play because as far as VB is concerned, Nothing is already a valid Short – a default-initialised one (see old answer below for explanation). So VB doesn’t try any type coercion, it simply uses Short as the return value.
Old answer below, assuming that OP had declared variable As Short:
You cannot set value types to Nothing. If you assign Nothing to a value type then it will be set to its type’s default value instead – which is 0 for Short.
You can test this easily:
Dim s as Short = Nothing
Console.WriteLine(s)
Setting a value type to Nothing is the same as invoking its default constructor (New Short()) or declaring a new variable of that type without initialising it. The corresponding operation in C# would be to assign default(T) (short s = default(short)).
If you want to represent null value types, you have to use nullable types:
Dim s as Short? = Nothing
Now s is of type Nullable<Short> (Short? is a shortcut of that) and can be assigned a proper Nothing.
Related
This question already has answers here:
Why does (int)(object)10m throw "Specified cast is not valid" exception?
(4 answers)
Closed 2 years ago.
Okay, so I'm no stranger to coding and this strikes me as absolutely silly. Maybe someone can shed some light on why this is failing as usually VB.Net is intuitive and flexible with this type (pun intended) of thing?
I have a simple example, where I want to pass an object set with an integer value of lets say 6 to a procedure with a nullable Int64 parameter. I'd like to do so as I can't change the incoming value from object, it is a property of a class and may very well be nothing, hence the nullable Int64 being used.
Private Sub NullableParamTest(ByVal ID As Int64?)
MsgBox(ID)
End Sub
Now a simple sample like this will cause the exception:
Dim objTest As Object = 6
NullableParamTest(objTest)
Why is this not being boxed properly when the object is being set to an integer and passing to a procedure with an Int64? type? If I wrap the objTest with CInt(objTest) and cast it first it's fine, but I shouldn't need to do that at least in VB. I have logging methods that optionally take in various IDs as Int64? but the source is this Object property causing it to fail...even though they have perfectly valid Int64s set.
I wanted to avoid the whole Optional...Int64 = Nothing as that's not really setting it to nothing, granted it works as my IDs would never be zero but it's not true to what is really going on.
Thanks to the comments from #GSerg, it appears the following is the answer to this question found here:
A boxed value can only be unboxed to a variable of the exact same type.
It links to another answer describing this behavior as being performance related. In short you have to convert/cast the value within the object first before passing it.
In my case my passed value of an ID would never be 0 so using a regular Int64 and checking that it's nothing would work, otherwise using Object for a type is the alternative as wrapping all of the method calls with over half a dozen converts would be hideous.
The other comment by #GSerg mentions the notation of adding an & after the value to make it an explicit Int64, also allowing it to work in this case. Note that casting the value in this way as an Int32, which in theory would work (as it fits within an Int64 space), in fact does not due to the first part of my comment that it has to be unboxed as the exact same value type for the parameter.
Given the following VBA code, assuming that a Something is just a VBA class module....
Public Type Foo
SomeThing As Something
End Type
Public Sub TestFoo()
Dim x As Foo
With x
'Correct way to do it
Set .someThing = New Something
End With
With x
'This is wrong but realized only as a RTE
'438: Object doesn't support this property or method
.SomeThing = New Something
End With
End Sub
In contrast, if you change the type to something like VBA.Collection as below:
Public Type Foo
SomeThing As VBA.Collection
End Type
Public Sub TestFoo()
Dim x As Foo
With x
.SomeThing = New VBA.Collection
End With
End Sub
This is now a compile error, with Argument Not Optional. This is obviously wrong but why is it a compile-time error only with VBA.Collection?
This is explained in the VBA Language Specification. The semantics of the assignment inside the With block is driven by whether or not the statement is a Set statement or a Let statement. In this context, it is a Let statement:
With x
.SomeThing = New Something
End With
Note that in the VBA grammar, the keyword Let is optional (obsolete):
let-statement = ["Let"] l-expression "=" expression
In a Set statement, the Set keyword is required:
set-statement = "Set" l-expression "=" expression
Inside of a With block, the l-expression is basically the UDT member, although the exact same behavior applies if x is used directly.
When evaluating a Let expression, the semantics are described in section 5.4.3.8:
Static Semantics.
This statement is invalid if any of the following is true:
<expression> cannot be evaluated to a simple data value (section 5.6.2.2 ).
Following that to 5.6.2.2 (Evaluation to a simple data value), the following runtime semantics apply (applicable rule only):
Runtime semantics.
At runtime, the simple data value’s value and value type are
determined based on the classification of the expression, as follows:
If the expression’s value type is a specific class:
If the source object has a public default Property Get or a public
default function, and this default member’s parameter list is
compatible with an argument list containing 0 parameters, the simple
data value’s value is the result of evaluating this default member as
a simple data value.
Otherwise, if the source object does not have a public default
Property Get or a public default function, runtime error 438 (Object
doesn’t support this property or method) is raised.
Thus the runtime error 438 for SomeThing As Something.
With the Collection, the Let static semantics still apply, but it fails the static semantics of 5.6.2.2 (which gives the compile error). Again, the preceding inapplicable semantics are omitted:
Static semantics. The following types of expressions can be evaluated to produce a simple data value:
An expression classified as a value expression may be evaluated as a simple data value based on the following rules:
If the declared type of the expression is a specific class:
If this class has a public default Property Get or function and this default member’s parameter list is compatible with an argument
list containing 0 parameters, simple data value evaluation restarts as
if this default member was the expression.
The default member of Collection is a function (.Item) that takes a single parameter Index. In this code, the parameter is not provided, so the argument list is incompatible:
With x
.SomeThing = New VBA.Collection
End With
Thus the Argument Not Optional compile error.
In typing up the question, it struck me that the VBA.Collection had a default member. Thus, the compiler was interpreting the .Something = New VBA.Collection as an assignment to the default member... except that the Item is an indexed property. That explains why we get Argument not optional which would be quite a strange error to get with a Set statement.
In contrast, a VBA class module might not have a default member at all, so that there is no indexed property + default member to trigger a compile-time error. However, it also means that the bad syntax won't be caught until runtime.
I am trying to write one line IF condition when assign value to property.
I've tried these syntax in VB.NET, type_of_documents is nullable integer:
1) vehicle.type_of_documents = If(vehicle.Istype_of_documentsNull, SqlTypes.SqlInt32.Null, vehicle.type_of_documents)
2) vehicle.type_of_documents = If(vehicle.Istype_of_documentsNull, DBNull.Value, vehicle.type_of_documents)
3) vehicle.type_of_documents = If(vehicle.Istype_of_documentsNull, Nothing, vehicle.type_of_documents)
Well, I am a little pushy to do this in one line. Somehow, all these syntax have failed to assign null value to my database. Syntax 1 & 2 have thrown cast integer exception. Syntax 3 is no error but no change/update value in database (same as previous).
Can anyone show me better syntax? Since I am really not into VB.NET.
Thanks in advance
The If operator is basically generic, i.e. the type it returns is inferred from the type of the second and third arguments. That means that those two arguments have to be the same type or one must assignable from the type of the other. A simple cast is all you need:
vehicle.type_of_documents = If(vehicle.Istype_of_documentsNull,
CObj(DBNull.Value),
vehicle.type_of_documents)
Now that the first argument is specified to be type Object, the two arguments share a type.
Return type of If method will be inferred from result parameters (as #jmcilhinney already answered).
In 1 and 2 samples you get cast exception because Integer cannot be casted to SqlTypes.SqlInt32.Null and DbNull.Value types.
Third sample compile because Nothing can be introduced as Integer, it simply default value of Integer which equals 0.
If you want return DbNull.Value in case when type_of_documents has no value - you need cast results in their common type, which is Object.
Instead of If method you can create extension method which return common type(object) required by SqlParameters
Public Module Extensions
<Extension>
Public Function GetValueOrDbNull(this As Integer?) As Object
If this.HasValue Then Return this
Return DBNull.Value
End Function
End Module
And use it
Dim sqlValue = vehicle.type_of_documents.GetValueOrDbNull()
If I were to type the following into a method body:
Dim myInt = 1
the Visual Studio IDE (and therefore, I am guessing, the compiler) infers the type of myInt to be Integer.
EDIT
Apparently using a literal was a bad choice here, since I've become embroiled in a lengthy debate that has nothing to do with the question. If you take issue with the fact that the expression 1 might be interpreted as an instance of different numeric types, pretend I had written:
Dim myInstance = New MyClass()
END EDIT
However, when I put a field declaration with the exact same code at the top of a class, the type of myList is not inferred:
Public Class Foo
Dim myInt = 1
End Class
On mouseover, it mentions the absence of an As clause, and says a type of Object has been assumed. I cannot pass myInt as an argument to a function or sub that expects an Integer argument, without explicitly adding an As clause or casting to Integer.
Is there a discrepancy between how the IDE and compiler deal with type inference? If, on the other hand, the compiler can't infer type in this situation either, why the discrepancy between method variables and class fields?
What you've found is that way on purpose. here is the MSDN expalanation.
Local type inference applies at procedure level. It cannot be used to
declare variables at module level (within a class, structure, module,
or interface but not within a procedure or block). If num2 in the
previous example were a field of a class instead of a local variable
in a procedure, the declaration would cause an error with Option
Strict on, and would classify num2 as an Object with Option Strict
off. Similarly, local type inference does not apply to procedure level
variables declared as Static.
I'm attempting to create a dataset based on the properties of an object. For example, I have an instance of a Person class with properties including ID, Forename, Surname, DOB etc. Using reflection, I'm adding columns to a new dataset based on the object properties:
For Each pi As PropertyInfo In person.GetType().GetProperties()
Dim column As New DataColumn(pi.Name, pi.PropertyType)
table.Columns.Add(column)
Next
My problem is that some of those properies are nullable types which aren't supported by datasets. Is there any way to extract the underlying system type from a nullable type?
Thanks.
Here's your answer, in VB. This may be overkill for your purposes, but it also might be useful to some other folks.
First off, here's the code to find out if you're dealing with a Nullable type:
Private Function IsNullableType(ByVal myType As Type) As Boolean
Return (myType.IsGenericType) AndAlso (myType.GetGenericTypeDefinition() Is GetType(Nullable(Of )))
End Function
Note the unusual syntax in the GetType. It's necessary. Just doing GetType(Nullable) as one of the commentors suggested did not work for me.
So, armed with that, you can do something like this... Here, in an ORM tool, I am trying to get values into a generic type that may or not be Nullable:
If (Not value Is Nothing) AndAlso IsNullableType(GetType(T)) Then
Dim UnderlyingType As Type = Nullable.GetUnderlyingType(GetType(T))
Me.InnerValue = Convert.ChangeType(value, UnderlyingType)
Else
Me.InnerValue = value
End If
Note that I check for Nothing in the first line because Convert.ChangeType will choke on it... You may not have that problem, but my situation is extremely open-ended.
Hopefully if I didn't answer your question directly, you can cannibalize this and get you where you need to go - but I just implemented this moments ago, and my tests are all passing.
Nullable.GetUnderylingType(myType)
will return the underlying type or null if it's not a nullable type.
I'm guessing that the problem is recognizing whether the property is nullable or not. In C# you do this with this code:
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
... but I'm not sure what the equivalent of that last clause is in VB.NET.
You can also use the GetGenericParameters() method on that type. myNullableObject.GetType().GetGenericParameters()[0] should give you the type of nullable it is (so Guid, Int32, etc.)
#Mendelt Siebenga: You can only call GetType on the value property if the variable is not set to null; otherwise, you'll get an exception.
What you want to do is use the "GetValueOrDefault" property and call GetType on that, since you are guaranteed it will not be null. Example:
Dim i As Nullable(Of Integer) = Nothing
Dim t As Type = i.GetValueOrDefault().GetType()