I looking at some code and came acorss this:
Dim TestString As String
...
If TestString <> Nothing And TestString <> "" Then
...
EndIf
Are both conditions checking the same thing?
Thanks
Nothing would be no string at all (null in other languages), which is different than an empty string (""), which is actually a string.
However, the check should be replaced with If Not String.IsNullOrEmpty(TestString) Then, which makes it clearer what exactly you are doing.
I just played around with this some in LINQPad, and found something mildly surprising. In VB.NET:
Dim s1 as string = Nothing
Dim s2 as string = ""
Console.WriteLine(s1 is Nothing) 'True
Console.WriteLine(s2 is Nothing) 'False
Console.WriteLine(s1 = "") 'True
Console.WriteLine(s2 = "") 'True
Console.WriteLine(string.IsNullOrEmpty(s1)) 'True
Console.WriteLine(string.IsNullOrEmpty(s2)) 'True
In C#:
string s1 = null;
string s2 = "";
Console.WriteLine(s1 == null); //True
Console.WriteLine(s2 == null); //False
Console.WriteLine(s1 == ""); //False
Console.WriteLine(s2 == ""); //True
Console.WriteLine(string.IsNullOrEmpty(s1)); //True
Console.WriteLine(string.IsNullOrEmpty(s2)); //True
I wasn't quite expecting that. It appears that VB.Net treats an Nothing as an empty string. My guess is for compatibility with older versions of VB.
This reinforces all the more that you should be using String.IsNullOrEmpty for these sorts of checks, as it is more explicit what you are checking for, and works as expected.
They are checking the samething, but they MIGHT be meant to check different things.
If IsNothing(TestString) Then
And
If TestString = Nothing Then
Are different test -- the first is seldom used, because typicaly you only really want to know if it has a non-empty value. But it can be used for treating an empty string anf a null value differently in a DB, or for detecting usage of an optional parameter (both cases requiring extra work to make sure that you don't inadvertantly slip in a wrong value, so somewhat fragile).
In the example given, the test is actually a bit wordy and confusing, if that's what you want to test then
If String.IsNullOrEmpty(TestString) Then
Is the way to go about it. If that "and" was supposed to be an "or" then it might make sense to have used IsNothing(TestString).
Yes, by definition in VB.NET "" is equivalent to Nothing including for =, <> and all VB functions; unless you explicitly care about the difference, such as by checking with Is.
Of course, you'll see differences when using general .NET functions and especially methods where str.Method will fail with a Null reference exception.
BTW I would guess the excerpt in the OP is C# code (badly) converted.
Related
I have a stored procedure which has some OUT parameters. On the .NET side I'm perplexed about how to check if the parameter is null before converting it to a .NET Double?. I've done this sort of thing a lot before across several different projects and I've never encountered this issue before...
This is every combination I've tried:
Command.Parameters.Add("PparStsValue", OracleDbType.Decimal).Direction = ParameterDirection.Output
PparStsValue = If(IsDBNull(Command.Parameters("PparStsValue").Value) OrElse
IsNothing(Command.Parameters("PparStsValue").Value) OrElse
Command.Parameters("PparStsValue").Value.ToString = "",
Nothing,
CDbl(Command.Parameters("PparStsValue").Value.ToString))
There is one other thing I've done which does work, but it just seems ridiculous which is to check:
Command.Parameters("PparStsValue").Value.ToString = "null"
This evaluates to True and therefore I can identify nulls... But why does a null value convert to the string "null"? That seems so inconsistent with everything else which usually converts to "".
That aside, the main question is why don't either IsDBNull and IsNothing return true?
When using ODP your parameter does not automatically return CLR type but rather oracle type. So the way to do it for numeric OracleParameter is this
Dim oraDec as OracleDecimal =
DirectCast(Command.Parameters("PparStsValue").Value, OracleDecimal)
Dim dbl as Double
If Not oraDec.IsNull Then
dbl = Convert.ToDouble(oraDec.Value)
End If
Edit for clarity: This is a pretty odd behaviour from the compiler, I'm asking for why it behaves this way in general, rather than how to work around it (there are several simple solutions already).
Recently, I came across a piece of code which throws contains a subtle mistake, and ends up throwing an exception. A shortened, contrived example:
Dim list As List(Of Integer) = Nothing
If list?.Any() AndAlso list.First() = 123 Then
MessageBox.Show("OK then!")
End If
In the real example, list was only occasionally Nothing, I'm just shortening the code for clarity. The intention with the first term in the If statement was to both check that list is not Nothing, and to also test for the existence of at least one element. Since in this case, list is Nothing, the list?.Any() actually / typically evaluates to Nothing. Somewhat counter-intuitively, the 2nd term, namely list.First() = 123 is also evaluated by the runtime, causing an obvious exception. This is somewhat counter-intuitive, since at first guess most people would imagine that Nothing is seem as False, and since we're using an AndAlso here, the short-circuit operator would prevent the 2nd half of the If statement from executing.
Additional investigation / "What have you tried:"
Quick check to confirm that a shortened If list?.Any() Then seems to treat list?.Any() as a False:
Dim list As List(Of Integer) = Nothing
If list?.Any() Then
MessageBox.Show("OK then!") 'This line doesn't get hit / run
End If
Also, we can work around the issue by in several ways: If list IsNot Nothing AndAlso list.Any() AndAlso list.First() = 123 Then would work just fine, as would If If(list?.Any(), False) AndAlso list.First() = 123 Then.
Since VB.Net is not my usual language, I thought I'd have a look at this in C#:
List<int> list = null;
if (list?.Any() && list.First() == 123)
{
MessageBox.Show("OK then!");
}
However, this gives a compilation error:
error CS0019: Operator '&&' cannot be applied to operands of type 'bool?' and 'bool'
Apart from the obvious fact that the stricter compiler check would prevent this mistake from being made in the C# scenario, this leads me to believe that type coercion is happening in the VB.Net scenario. One guess might be that the compiler is trying to cast the Boolean result of the 2nd term to a nullable Boolean, however this doesn't make a whole lot of sense to me. Specifically, why would it evaluate it prior to / same time as the left side, and abandoning the entire process early, like it should? Looking back at the VB.Net examples that do work correctly, all involve explicit checks which have a simple Boolean result, rather than a nullable Boolean.
My hope is that someone can give some good insight into this behaviour!
This appears to be an omission(bug) in the VB compiler's syntax evaluation. The documentation for the ?. and ?() null-conditional operators (Visual Basic) states:
Tests the value of the left-hand operand for null (Nothing) before
performing a member access (?.) or index (?()) operation; returns
Nothing if the left-hand operand evaluates to Nothing. Note that, in
the expressions that would ordinarily return value types, the
null-conditional operator returns a Nullable.
The expression list?.Any() (Enumerable.Any Method) would ordinarily return a Boolean (a ValueType), so we should expect list?.Any() to yield a Nullable(Of Boolean).
We should see a compiler error as a Nullable(Of Boolean) can not participate in an AndAlso Operator expression.
Interestingly, if we treat list?.Any() as a Nullable(Of Boolean), it is seen as documented.
If (list?.Any()).HasValue() AndAlso list.First = 123 Then
' something
End If
Edit: The above does not really address your why?.
If you de-compile the generated IL, you get something like this:
Dim source As List(Of Integer) = Nothing
Dim nullable As Boolean?
Dim nullable2 As Boolean? = nullable = If((Not source Is Nothing), New Boolean?(Enumerable.Any(Of Integer)(source)), Nothing)
nullable = If((nullable2.HasValue AndAlso Not nullable.GetValueOrDefault), False, If((Enumerable.First(Of Integer)(source) Is &H7B), nullable, False))
If nullable.GetValueOrDefault Then
MessageBox.Show("OK then!")
End If
This obviously will will not compile, but if we clean it up a bit, the source of the issue becomes apparent.
Dim list As List(Of Integer) = Nothing
Dim nullable As Boolean?
Dim nullable2 As Boolean? = If(list IsNot Nothing,
New Boolean?(Enumerable.Any(Of Integer)(list)),
Nothing)
' nullable2 is nothing, so the 3rd line below is executed and throws the NRE
nullable = If((nullable2.HasValue AndAlso Not nullable.GetValueOrDefault),
False,
If((Enumerable.First(Of Integer)(list) = 123), nullable, False))
If nullable.GetValueOrDefault Then
MessageBox.Show("OK then!")
End If
Edit2:
The OP has found the following statement from the documentation for Nullable Value Types (Visual Basic)
AndAlso and OrElse, which use short-circuit evaluation, must evaluate
their second operands when the first evaluates to Nothing.
This statement makes sense if Option Strict Off is in force and using the OrElse Operator as Nothing can implicitly be converted to False. For the OrElse operator, the second expression is not evaluated only if the first expression is True. In the case of the AndAlso operator, the second operator is not evaluated if the first expression is True.
Also, consider the following code snippet with Option Strict On.
Dim list As List(Of Integer) = Nothing
Dim booleanNullable As Nullable(Of Boolean) = list?.Any()
Dim b As Boolean = (booleanNullable AndAlso list.First() = 123)
If b Then
' do something
End If
This re-arrangement of the original logic does yield a compiler error.
With Option Strict Off, no compiler error is generated, yet the same run-time error occurs.
My Conclusion: As originally stated, this is a bug. When an AndAlso operator is included in a If-Then block the compiler treats the result of the null conditional operator using Option Strict Off type conversion relaxation regardless of the actual state of Option Strict.
Due in part to its heritage of strong support for database access, classic VB supported Null values as a first-class part of the type system. Null is included in the truth tables for all of the built-in logical operators (for example, see https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/imp-operator ).
For this reason, it is not surprising that (as you later noted) VB would choose to use similar semantics to a classic Null for a Boolean? which does not have a value.
As a practical matter, the best way to address this is probably the following:
If (list?.Any()).GetValueOrDefault() AndAlso list.First() = 123 Then
In the alternative, you could use FirstOrDefault to avoid needing the check Any first, although this does rely on the value you seek not being the default (or using a "magic" replacement value, remember that you can override the default in GetValueOrDefault), e.g.
If list?.FirstOrDefault().GetValueOrDefault() = 123 Then
Please try
if (list?.Any() IsNot Nothing AndAlso list.First() == 123)
For example, one could avoid a possible run-time null exception in the case below by instantiating s as it is declared. For example, Dim s as String = "" Is there a reason for us to allow the possibility of a run-time exception? Perhaps it does not make sense for s to be ""? This is not a homework question. I am just curious.
Public Function X() As String
Dim s as String
Dim sMethod As String = X
Try
Dim vowels() As String = {"a", "e", "i", "o", "u"}
For Each vowel As String In vowels
s = "0"
Next
Catch ex As Exception
Throw System.Exception(ex)
End Try
Return s
End Function
Editing to throw exception
Visual Studio seems to be smart enough to tell us that "A null reference exception could result at runtime." which means some capable, professional developers ignored this warning. This leads me to think that must be for a reason. The developers have since moved on though so I can't ask them. It might be something fundamental that I can't understand. Please help me understand this if you will. (I can't be the only person wondering about this.)
Suppose you don't initialize your String variable. And there goes some processing. In the end you expect get a definitive value, which you return via a Return statement. If, for some reason, you forgot to account for a possible execution path, VS will warn you about possible null-reference exception. Then you may think to yourself: "oh yeah, how did I forget that?"
For example, you have a Function that converts a Integer into a word (limited practical use, so consider it just for demo purposes):
Function ConvertIntToWord(a As Integer) As String
Dim retString As String
If a = 0
retString = "Zero "
ElseIf a = 1
retString = "One "
End If
Return retString
End Function
In the above lines, you were unlucky to forget about the Else path. Then suppose you want to run further processing on a resulting string:
ConvertIntToWord(5).TrimEnd
With 5 passed as an argument, you will get an exception at this point. If you instead listened to VS, you would have noticed a warning - underlined retString in Return retString line. So you could fix this problem without running your code.
Now suppose you initialize Dim retString As String = "". You will no longer see the warning, and have to debug your program to solve this issue. Depending on the complexity of your program, it may take much more effort.
Bottom line - it is always better to spot problems in compile time, rather than run-time.
EDIT: Forgetting to initialize a variable can be intentional, for example why using TryGetValue. For this method, value is passed uninitialized, according to documentation. However, VS does not know about this, so it will continue to show a warning, until you explicitly assign it to Nothing:
Dim retString As String = Nothing
So this is a way to tell VS that yes, you know it is uninitialized, but for this particular scenario, it is intentional.
I found a simple bug in VB.NET that can be easily reproduced:
Dim pDate As Date?
Dim pString As String = ""
' works fine as expected
pDate = If(False, "", Nothing)
' expected: pDate will be set to Nothing.
' BUG: Conversion from string "" to type 'Date' is not valid.
pDate = If(False, pString, Nothing)
'These both fail with the same error
pDate = pString
Dim pDate2 As Date? = ""
Question: Is this a bug? Or is there something wrong with me or my PC?
If this is a bug, is there a bug report of this (I cant seem to find it)?
Lessons learned:
this is not a bug
nullable date accepts object nothing
nullable date rejects string nothing
pDate = Nothing ' ok. nullable date accepts object nothing
pString = Nothing
pDate = pString ' error. nullable date rejects string nothing
The bug is in your first use of If(), not the second. Contrary to your comment, the result there is not "expected". That call should fail, because "" cannot convert to a date and the ternary operator is typesafe at all levels whether or not the expression is used.
I suspect it succeeds because of a compiler optimization: since everything is all literals, the condition is optimized away. It's harder to make the optimization the second time, because the pString variable might be changed by another thread the compiler doesn't know about yet.
Someone who's handier with IL can probably confirm this.
The real surprise for me is that this is not caught until runtime. I would expect the compiler to notice the type mismatch and complain at that level, rather than waiting until execution. Your VB Option settings may have something to do with that.
This is pretty interesting. The example above is pretty clear that there is something odd going on. That said, Stack Overflow isn't really a good place to "report" bugs. If you think you have really found a bug you can post your findings to Microsoft connect.
I did a search on connect and there are quite a few quirks with both the VB.NET and C# ternary operator, especially when Nullable value types are involved. This just may be another one of them?
For what it's worth, you can even simplify the case to look like:
Dim pDate As Date?
pDate = If(False, "", Nothing) ' Works fine
pDate = If(False, String.Empty, Nothing) ' Doesn't work
It is worth noting that every situation that appears to be broken (all cases expect usage of "") does work when the line looks like: pDate = If(False, String.Empty, CType(Nothing, Date?))
Also, Option Strict [On|Off] plays a very big role in this. When Option Strict On is set, then all of these are compile errors. This behavior can only be seen when Option Strict Off. I've put together an example of all the situations here.
In the end, I don't think this is really a bug, but simply one of the pitfalls of using Option Strict Off. It does seem odd (illogical), but then again so does having Option Strict Off. ;)
If Object.Value IsNot "Something" Then
Can you do this, or are there certain cases in which it wouldn't work? Wasn't sure if this should only be used for integers and booleans.
Thanks!
I'm not sure if this works or not but if it did it would be a very bad idea to use. The Is and IsNot operators in VB.Net do reference comparisons. When dealing with String values you almost always want to do a value comparison which is through = and <>.
Reference comparisons tell you if it's literally pointing to the same object. In .Net it's very possible for the same identical string to be captured in 2 objects allowing for confusing cases like the following
Function CreateFoo() As String
return "foo"
End Function
Dim str1 = "foo"
Dim str2 = CreateFoo()
if str1 Is str2 Then
' This is possible
Else
' This is also possible
End If
Value comparison provides much more sanity here
Dim str1 = "foo"
Dim str2 = CreateFoo()
if str1 = str2 Then
' This will run
Else
' This is simply not possible
End If
That is will tell you if Object.Value and "Something" are literally the same object.
99.999% of the time, you don't care about that. All you care about is if they are semantically equal, that is they both contain the word 'Something'.
From the documentation: "The IsNot operator determines if two object references refer to different objects."
Thus, you would not want to compare strings with it because it is unlikely that two identical strings would actually refer to the same object. This would only happen if they were compile-time constants, were interned, or both copies of the same variable.