Why is my Nullable(Of Int32) = 0 after I set it to Nothing? - vb.net

I think I'm missing something fundamental about nullable types. Hopefully this example will open up new understanding, but at the very least, maybe we can get this one thing working right.
In a Class (a dialog form), I declare:
Property ProductStructureHeaderKey As Int32?
In another Class, I declare an instance of that dialog and attempt to set that Property with this line:
dr.ProductStructureHeaderKey = If(parentRow.Cells(1).Value Is Nothing, Nothing, Int32.Parse(parentRow.Cells(1).Value))
When that line assigns Nothing to the property, the property is equal to 0. (And then later, it's passing 0 to the DB when I want it passing NULL.)
That's not what I expect and I keep finding code (SO, MSDN, etc) that looks like I'm doing things right, but clearly, I'm not. So, friends, what am I doing wrong? How do I employ Nullable types to meet my needs?

That's one of the differences between C# and VB.NET. In VB.NET Nothing does not only mean null but also default. So you are assigning the default value of Int32 to the property what is 0. This is caused by the If-operator that has to infer the type from the two values not from the property that you want to assign.
Instead use either an If...Else:
If parentRow.Cells(1).Value Is Nothing Then
dr.ProductStructureHeaderKey = Nothing ' Now it's not 0 but Nothing
Else
dr.ProductStructureHeaderKey = Int32.Parse(parentRow.Cells(1).Value)
End If
or force the nullable with new Nullable(Of Int32):
dr.ProductStructureHeaderKey = If(parentRow.Cells(1).Value Is Nothing, new Nullable(Of Int32), Int32.Parse(parentRow.Cells(1).Value))
Further read: Why is there a difference in checking null against a value in VB.NET and C#?

Related

How to Get Visual Studio to Warn when Nullable (of T) might be Nothing at Runtime?

This code will throw a System.InvalidOperationException when the click event happens because the nullable (of decimal) has not been given a value.
Public Class Form1
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim myob As New MyObject
Dim price As Decimal = CDec(myob.Price)
End Sub
End Class
Class MyObject
Public Property Price As Decimal?
End Class
However, even with Option Strict on, I do not get any warnings. Can I get Visual Studio 2019 Professional to warn me about this? The value is coming from a database, and therefore null/nothing is a valid value and obviously means something different than 0. I am not asking how to check if Price has a value, I am wondering if I can get Visual Studio to give me a warning if I miss checking for a value.
I believe the answer to your question is that you should always use the same type when creating a new private variable.
Example:
Nullable(of Decimal) = Nullable(of Decimal) or Decimal = Decimal
Unless you check your var for value of Nothing before using or setting it.
like this
Dim var1 as Nullable(of Decimal) = Nothing
Dim var2 as Decimal
If var1 IsNot Nothing Then
var2 = var1
End If
UnFortunately
Visual Studio doesn't seem have any feature that allows
checking of this problem(Maybe you should build a free extension that does this)
Installing VS 2019 doesn't resolve an issue that doesn't exists
Primitive types (decimal, integer, boolean etc) cannot be null on declaration, those have a default value.
But if a Dev need a variable of primitive type to be NULL/Nothing just add on end of type an question mark “?”.
At this point the variable become a Nullable Type, so in your case a Nothing value of “Thing” cannot be 0.
Remove your question mark “?” to have default value. :)
C# 8.0 introduces nullable reference types and non-nullable reference types that enable you to make important statements about the properties for reference type variables.
A reference can be null. No warning is issued when a reference type
is initialized to null, or null is later assigned to it.
A reference is assumed to be not null. The compiler doesn't issue any
warnings when reference types are dereferenced. (With nullable
references, the compiler issues warnings whenever you dereference a
variable that may be null).
With the addition of nullable reference types, you can declare your intent more clearly. The null value is the correct way to represent that a variable doesn't refer to a value. Don't use this feature to remove all null values from your code. Rather, you should declare your intent to the compiler and other developers that read your code. By declaring your intent, the compiler informs you when you write code that is inconsistent with that intent. Please see the document fore more details.

Cell ColumnType is NULL using Smartsheet API

I'm trying to update my SmartSheet API from v1 to v2 and am having some difficulty on the code below.
The code returns rows for the selected sheet, however the "ColumnType" property of all the Cell's within the rows are NULL.
I know to return this you have to specify it as an inclusion - which I believe I have.
Dim sheet As Sheet = smartsheet.SheetResources.GetSheet(curSheet.Id, New RowInclusion() {RowInclusion.COLUMN_TYPE, RowInclusion.COLUMNS}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing)
For Each Row As Row In sheet.Rows
If Row.ParentRowNumber Is Nothing Then
Dim i As Integer = 0
Dim colType As ColumnType
If Not Row.Cells(i).ColumnType = ColumnType.TEXT_NUMBER Then
'Do some stuff here...
End if
Next
Any help would be great.
Thanks,
Steve
The short answer is just get the latest SDK from https://github.com/smartsheet-platform/smartsheet-csharp-sdk/pull/60 and update your GetSheet to the following:
Dim sheet As Sheet = client.SheetResources.GetSheet(SHEETID, New SheetLevelInclusion() {SheetLevelInclusion.COLUMN_TYPE}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing)
Notice the use of SheetLevelInclusion rather than RowInclusion. You should be all set.
If you care about the details, the longer answer is... The GetSheet method doesn't accept an array/IEnumerable of RowInclusion as the second argument. It expects an array/IEnumerable of SheetLevelExclusion. In C#, the same invocation call would fail as C# imposes stricter type checking on the generic type parameter of IEnumerable. However, due to Visual Basic's leniency around implicit conversions between Enum types and its lenient conversions for arrays (and similar types like IEnumerable) it is possible to invoke a function with the "wrong" type of argument when the argument is an array/IEnumerable and the elements are Enums. In this case, Visual Basic is actually converting the RowInclusion values to their underlying numeric value (Enum is always implicitly or explicitly backed by an underlying numeric type) and converting those values to the SheetLevelExclusion value corresponding to the same underlying numeric value so that it can invoke the GetSheet method.
The other complication here is that the SDK didn't have COLUMN_TYPE as an available SheetLevelExclusion value. So the pull request/branch I linked to above adds that. In my simple test here that made it work.

Strange behaviour of the If() statement

today I stumbled upon a strange behaviour of the VB.net If() statement. Maybe you can explain why it is working like it does, or maybe you can confirm that it is a bug.
So, I have a SQL database with a table "TestTable" with an int column "NullableColumn" that can contain NULL. I'd like to read out the content of this column.
So I declare a variable of type Nullable(Of Integer) for that matter, open a SqlClient.SqlDataReader for "SELECT NullableColumn FROM TestTable" and use the following code to get the content of this column:
Dim content as Nullable(Of Integer)
...
Using reader as SqlClient.SqlDataReader = ...
content = If(reader.IsDBNull(reader.GetOrdinal("NullableColumn")), Nothing, reader.GetInt32(reader.GetOrdinal("NullableColumn")))
End Using
But after that my variable content has the value 0, not Nothing as I would have expected.
When debugging everything looks alright, so
reader.GetOrdinal("NullableColumn") delivers the correct ordinal position of this column (which is 0)
reader.IsDBNull(0) and reader.IsDBNull(reader.GetOrdinal("NullableColumn")) deliver True, since the content of this column indeed is NULL
If(1=2, Nothing, "Not Nothing") delivers the String "Not Nothing"
If(1=1, Nothing, "Not Nothing") delivers Nothing
reader.GetInt32(reader.GetOrdinal("NullableColumn")) throws an error, since NULL can't be converted to Integer
So, why does my variable has the value 0?
In VB Nothing is not the same as null. The If operator must determine the type of its result based on the arguments passed to it. Nothing, of course, has no type so the only type that the If operator can return in your code is Int32. If the IsDBNull method returns true, then the If operator returns Nothing cast as Int32. In VB, Nothing returns the default value for a type. For an Int32, the default value is 0.
From MSDN on the Nothing keyword:
Nothing represents the default value of a data type. The default value depends
on whether the variable is of a value type or of a reference type.
For non-nullable value types, Nothing in Visual Basic differs from null in C#.
In Visual Basic, if you set a variable of a non-nullable value type to Nothing,
the variable is set to the default value for its declared type. In C#, if you
assign a variable of a non-nullable value type to null, a compile-time error
occurs.
I think just a regular If would work best:
If Not reader.IsDBNull(reader.GetOrdinal("NullableColumn")) Then
content = reader.GetInt32(reader.GetOrdinal("NullableColumn"))
End If
Or to keep it shorter
If Not reader.IsDBNull(reader.GetOrdinal("NullableColumn")) Then content = reader.GetInt32(reader.GetOrdinal("NullableColumn"))
But after that my variable content has the value 0, not Nothing as I would have expected.
How do you check the content value?
First of all, you should start with content.HasValue property. It should be False for your case of Nothing and True when correct value was fetched from database.
You should also get InvalidOperationException while accessing content.Value when it hasn't got value.
Chris has given the explanation but I dislike the style of assignment he’s chosen because it splits assignment from variable declaration.
By contrast, I recommend initialising variables upon declaration. In this case it’s admittedly slightly convoluted since you need cast either operator of If to the correct type first.
Dim content = If(reader.IsDBNull(reader.GetOrdinal("NullableColumn")),
DirectCast(Nothing, Integer?),
reader.GetInt32(reader.GetOrdinal("NullableColumn")))
Actually you can also use the slightly shorter New Integer?() instead of the DirectCast.
Of course now content is declared inside the Using block – this might not be what you want but you should try to make the declaration as local as possible.
Furthermore, this code is complex and will probably be reused. I suggest creating a separate (extension) method to convert database NULL values to nullables:
<Extension> _
Public Shared Function GetNullable(Of T)(SqlClient.SqlDataReader this, String fieldName) As T?
Dim i = this.GetOrdinal(fieldName)
Return If(this.IsDBNull(i), New T?(), this.GetFieldValue(Of T)(i))
End Function
Now you can use it as follows:
Dim content = reader.GetNullable(Of Integer)("NullableColumn")

Difference between nothing and system.DBNull

I am working in VB.NET and I am wondering about the difference between Nothing and System.DBNull.
When I fire save query at that time I am giving value from grid at runtime like as follow:
gvMain.Rows(j).Cells("Brand").Value.ToString()
But it shows me error when it has value of Nothing and it works perfectlly when it has value of System.DBnull.
What to do in this case?
Thanks in advance
The keyword Nothing is used to specify or asign that a var of reference type is not pointing anything, no object is instanciated for this var.
DBNull.Value, on the other hand, is an object used to point out that a type of a field of the DataBase is of null value.
Nothing is of Type System.Object.
It represents the default value of any data type.
DBNull.Value is of Type System.DBNull
If something shows up as System.DBNull, that means that even though it doesn't have a value, it has a valid pointer. As you may have found out, it cannot be converted to a string, integer, etc. You must do a check (preferably using IsDBNull.
If IsDBNull(gvMain.Rows(j).Cells("Brand").Value) Then
Return String.Empty
Else
Return gvMain.Rows(j).Cells("Brand").Value.ToString().Trim()
End If

Function in VB that doesn't have a return type

I am not much familiar with Visual Basic 6.0 and am not having a VB compiler installed, but I was looking at some VB code for some debugging and saw this:
Private Function IsFieldDeleted(oLayoutField As Object)
Dim oColl As Collection
Set oColl = GetFieldIdsForField(oLayoutField)
IsFieldDeleted = (oColl.Count = 0)
Set oColl = Nothing
End Function
In other functions I see they define the return type with an "As" for example "As Boolean" but this one does not have an "As" :D and then how they have used it is like this:
If Not IsFieldDeleted(oRptField.GetUCMRLayoutField) Then
Call oCollection.Add(oRptField, oRptField.ObjectKeyString)
Call AddToNewLineSeperatedString(sCaseFldDescMsg, oFld.FieldDescription)
End If
How is this working? Is it just like rewriting it and saying that the function returns an integer and compare the return type to be either 0 or 1? Or are there some other hidden tips in there?
When no type is specified, in VB.NET it assumes Object for the return type. In VB6, it assumes Variant. In VB.NET you can make things much more obvious by turning Option Strict On, but I don't believe that option was available in VB6.
The value that is returned, in reality, is still typed as a Boolean, but you are viewing the returned value as a Variant. So, to do it "properly", you really ought to cast the return value like this:
If Not CBool(IsFieldDeleted(oRptField.GetUCMRLayoutField)) Then
....
End If
Calling CBool casts the value to a Boolean instead of a Variant. This is unnecessary, though, since VB will use late-binding to determine the type of the return value is a boolean.
The best thing to do in this case is to change the function to As Boolean. Doing so will not break any existing code since that's all it ever returned anyway. However, if it's a public member in a DLL, that would break compatibility.