locked
Another bug ?? If() function this time... RRS feed

  • Question

  • User-41154591 posted

    I have nullable decimal variable.
    I can assign null (nothing) to it.

    Everything works fine, until I try to assign null (nothing) to it from If() or IIf() functions!
    Somehow tests done in aftermentioned functions return wrong logical result!

    Am I missing something??? i'm so tired by now, that it is possible :) help please!

    <!DOCTYPE html>
    <html>
       <head>
          <title></title>
    		<meta charset="utf-8" />
       </head>
       <body>
          <div>
    			@Code
    				Dim dec As Decimal? = Nothing
    				
    				If dec Is Nothing Then
    	 	 	 	' works correctly
    					@:Nothing
    				Else
    					@dec
    				End If
    				
    				@<br />
    				
    				Dim strx = ""
    				dec = If(Len(strx) > 0, strx.AsDecimal(), Nothing)
    				
    				If dec Is Nothing Then
    					@:Nothing
    	 	 	 	 Else
    	 	 	 	' WHY this code goes here ??? dec should be nothing!!!!!
    					@dec
    				End If
    			End Code
    		</div>  
       </body>
    </html>
    
    Monday, June 6, 2011 7:47 AM

Answers

  • User548266002 posted

    I'm Lucian Wischik, the VB specification lead, and I re-implemented the "If(,,)" and "If(,)" operators in VS2010, so here's a definitive answer:

     

    It's important to note that "Nothing" in VB does NOT mean "null". Instead it means "default(type)", i.e. the default value of the type in question. This is what lead to the problem in this thread. It's something we don't like in the language, and have been trying to figure out how to improve, but we haven't yet figured out a good answer:
    http://blogs.msdn.com/b/lucian/archive/2010/03/05/req24-warnings-where-nothing-isn-t-null.aspx

     

    Oh, and to get the answer first, here's how I'd have written the code in question:

    dec = If( strx.IsNullOrEmpty(), CType(Nothing, Decimal?), str.AsDecimal())

     

    The behavior is explained in the VB language specification, found in "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VB\Specifications\1033", section $11.22, although I'm afraid that the language of the spec is a bit confusing.

    • Always remember that "Nothing" just means "default(type)" for whatever type is expected. For example:
      • "Dim x As Integer = Nothing" will make x be the default integer 0
      • "Dim x As Integer? = Nothing" will make x be the default nullable, i.e. a NULL value of type Integer?.
    • The type of If(b,TruePart,FalsePart) is inferred to be the dominant type of TruePart and FalsePart.
      • "Nothing" doesn't really have a type of its own. It doesn't contribute to the question of dominant type.
      • If we have the two expressions "strx.AsDecimal()" and "Nothing", what is their dominant type? Well, Nothing doesn't contribute, so the dominant type is Decimal.
      • "If(b, strx.AsDecimal(), Nothing)" will pick DECIMAL as the dominant type, therefore. It will treat Nothing as the default value of Decimal, i.e. the decimal number 0. It will NOT treat Nothing as a nullable NULL.
    • Suggested code: "If(b, strx.AsDecimal(), CType(Nothing,Decimal?))"
      • This picks "Decimal?" as the dominant type. That's why it works.

     

    So what about the other behavior that has been observed in this thread? Well, ...

     

    • Dim x As Decimal? = IIf(b, strx.AsDecimal(), Nothing)...
      • The IIf runtime function (with two "I"s) casts both of its arguments as Object. In particular, Nothing will be a NULL pointer of type Object. If the condition ends up false, the result of IIf will be a NULL reference of type Object. This can be converted to Decimal?. The conversion involves a runtime check with slight additional perf overhead, and is counted as "narrowing".
    • Dim x As Decimal? = If(b, Nothing, Nothing)...
      • Here the If operator has nothing at all to go on to help it find the dominant type. In these cases it just falls back to Object as the dominant type. So again, the result of If will be a NULL reference of type Object, which involves the runtime narrowing conversion.

     

    I want to stress once again that "Nothing" doesn't necessarily have type Object. It will fall back to Object if it doesn't have anything else to go on. But if you do give it something else to go on, e.g. "Dim x As String = Nothing", then it's not Object at all.

     

     

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, June 8, 2011 1:05 PM

All replies

  • User-41154591 posted

    ha, I think I understood the reason of this - when returning result function "If" tries to convert it to the needed type.

    But it works incorrectly - it converts result to "Decimal" and not to "Decimal?",
    so "Nothing" is converted to decimal 0 and no error is thrown.. :)

    Monday, June 6, 2011 8:09 AM
  • User-41154591 posted

    no, it's not the reason - if i use code:

    dec = If(Len(strx) > 0, Nothing, Nothing)

    everything works as expected - dec is nothing..

    quite enjoying talking to myself here... :)))

    EDIT: dec = If(LHtml.fIsDecimal(strx), Nothing, Nothing) changed to dec = If(Len(strx) > 0, Nothing, Nothing)

    Monday, June 6, 2011 8:14 AM
  • User-125547262 posted

    It is because  strx.AsDecimal() converts it to a decimal which is zero hence the reason why your code hits the other block

    Monday, June 6, 2011 8:15 AM
  • User-41154591 posted

    i have ment

    dec = If(Len(strx) > 0, Nothing, Nothing)

    in previous post..

    Monday, June 6, 2011 8:17 AM
  • User-41154591 posted

    Jeev - thanx for answer, but i DO NOT convert anything before test is done:
    first i check if string is not empty - if it is - i do not want to convert it to decimal - i want to return null (nothing)
    and then insert this value into database, so my nullable column will have null value instead of 0 (zero).

    if i do check like so:

    dim dec as decimal? = if(str.length()>0,str.asdecimal(),nothing)

    it does NOT work

    if i do check like so:

    dim dec as decimal?
    if str.length()>0 then dec=str.asdecimal() else dec=nothing

    everything works fine...

    :((((((( cannot trust If function anymore..

    Monday, June 6, 2011 8:23 AM
  • User-727365369 posted

    dim dec as decimal? = if(str.length()>0,str.asdecimal(),nothing)

    IF vs IIF

    IF short circuits - you never get to assigning "nothing" to your nullable type. Once it hits str.asdecimal(), you have in fact assigned a value to to your nullable type.

    IIF should work - just make sure that's what you want. Both true and false are evaluated so:

    Dim d As Decimal? = IIf(str.Length > 0, str.AsDecimal(), Nothing)

    Edit:

    My big mistake, short circuit has nothing to do with it. Mea culpa.

    IIF returns object (nothing = nothing), while IF returns value (default value of decimal is 0)

    Monday, June 6, 2011 11:42 AM
  • User-41154591 posted

    EdSF - yes, that is the answer that i was looking for :) this was one of my versions, why this is happening.

    It's sad - because i'm now stuck with slower function (or more code)...

    when you posted your answer before edit, i haven't really understood what you've meant.

    so i began to write console test application :)

    Module Module1
    
    	Function f1() As String
    		Console.WriteLine("f1")
    		Return "return from f1"
    	End Function
    
    	Function f2() As String
    		Console.WriteLine("f2")
    		Return "return from f2"
    	End Function
    
    	 Sub Main()
    		Dim str = ""
    		Console.WriteLine(If(str.Length > 0, f1, f2))
    		Console.WriteLine(IIf(str.Length > 0, f1, f2))
    
    		Console.WriteLine("-------")
    
    		Dim dec As Decimal?
    		dec = If(str.Length > 0, f1, Nothing)
    		If dec Is Nothing Then Console.WriteLine("Nothing") Else Console.WriteLine(dec)
    
    		Console.WriteLine("-------")
    
    		dec = IIf(str.Length > 0, f1, Nothing)
    		If dec Is Nothing Then Console.WriteLine("Nothing") Else Console.WriteLine(dec)
    	 End Sub
    
    End Module
    

    anyway - thank you for clarification, i totally have thought, that IF and IIF have only one difference - speed.

    P.S. it's interesting though, why IF returns nothing in this case: IF(str.length>0, nothing, nothing) and does not convert nothing to decimal 0D.

    Monday, June 6, 2011 4:54 PM
  • User-727365369 posted

    IF returns nothing in this case: IF(str.length>0, nothing, nothing)
     

    Ok, I took a trip on that one......I can't find any reference to that except possibly this blog post:
    http://blogs.msdn.com/b/vbteam/archive/2008/03/11/if-operator-a-new-and-improved-iif-sophia-salim.aspx

    Note the note: 
    TruePart, is the return value of the IF operator if the test expression above evaluates to true. Note that the return type of the operator is decided based on the wider of the types of the truepart and the falsepart, e.g, if the truepart is an integer and the falsepart is a decimal, then decimal which is the wider of the two types will be the return value's type. For details on type conversions and for a list of wider and narrower types, please refer here

    Nothing (true part) vs Nothing (false part) = nothing (object)?

    Unsure why the note is written in such a way that it it seems to refer only to the True part (the statement does say "the return type of the operator").....but it's a blog post....why it doesn't seem document/explained/noted anywhere else is another mystery....at least I can't find it in docs.....or perhaps missed it....

    Anybody from MS care to comment on this?

    Great thread!

     

     

    Monday, June 6, 2011 6:56 PM
  • User-41154591 posted

    BTW. when using IF as coalesce, it works as expected:

    dim dec as decimal? = nothing
    dim result as decimal? = 5D
    
    result = IF(dec, nothing)
    
    'result is now nothing...

    there is question in that blog post:

    For those addicted IIF users wondering if they can still use the legacy IIF function in Orcas, the answer is yes. We still support IIF, but with all the amazing things that you can do with the IF operator, why would you want to keep on typing those extra "i"s?

    the answer to this question is in this thread :)

    EdSF - also, as you have said, in that blog post there are the lines:

    Note that the return type of the operator is decided based on the wider of the types of the truepart and the falsepart, e.g, if the truepart is an integer and the falsepart is a decimal, then decimal which is the wider of the two types will be the return value's type

    So, if we have:

    IF(str.length>0,str.AsDecimal(),nothing)

    I think that "Nothing" is wider - it's Object and Object is the widest type in .net ?

    P.S. it may be not BUG, this behavior may be by design, but still - for me it's counter intuitive..

    Tuesday, June 7, 2011 2:06 AM
  • User548266002 posted

    I'm Lucian Wischik, the VB specification lead, and I re-implemented the "If(,,)" and "If(,)" operators in VS2010, so here's a definitive answer:

     

    It's important to note that "Nothing" in VB does NOT mean "null". Instead it means "default(type)", i.e. the default value of the type in question. This is what lead to the problem in this thread. It's something we don't like in the language, and have been trying to figure out how to improve, but we haven't yet figured out a good answer:
    http://blogs.msdn.com/b/lucian/archive/2010/03/05/req24-warnings-where-nothing-isn-t-null.aspx

     

    Oh, and to get the answer first, here's how I'd have written the code in question:

    dec = If( strx.IsNullOrEmpty(), CType(Nothing, Decimal?), str.AsDecimal())

     

    The behavior is explained in the VB language specification, found in "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VB\Specifications\1033", section $11.22, although I'm afraid that the language of the spec is a bit confusing.

    • Always remember that "Nothing" just means "default(type)" for whatever type is expected. For example:
      • "Dim x As Integer = Nothing" will make x be the default integer 0
      • "Dim x As Integer? = Nothing" will make x be the default nullable, i.e. a NULL value of type Integer?.
    • The type of If(b,TruePart,FalsePart) is inferred to be the dominant type of TruePart and FalsePart.
      • "Nothing" doesn't really have a type of its own. It doesn't contribute to the question of dominant type.
      • If we have the two expressions "strx.AsDecimal()" and "Nothing", what is their dominant type? Well, Nothing doesn't contribute, so the dominant type is Decimal.
      • "If(b, strx.AsDecimal(), Nothing)" will pick DECIMAL as the dominant type, therefore. It will treat Nothing as the default value of Decimal, i.e. the decimal number 0. It will NOT treat Nothing as a nullable NULL.
    • Suggested code: "If(b, strx.AsDecimal(), CType(Nothing,Decimal?))"
      • This picks "Decimal?" as the dominant type. That's why it works.

     

    So what about the other behavior that has been observed in this thread? Well, ...

     

    • Dim x As Decimal? = IIf(b, strx.AsDecimal(), Nothing)...
      • The IIf runtime function (with two "I"s) casts both of its arguments as Object. In particular, Nothing will be a NULL pointer of type Object. If the condition ends up false, the result of IIf will be a NULL reference of type Object. This can be converted to Decimal?. The conversion involves a runtime check with slight additional perf overhead, and is counted as "narrowing".
    • Dim x As Decimal? = If(b, Nothing, Nothing)...
      • Here the If operator has nothing at all to go on to help it find the dominant type. In these cases it just falls back to Object as the dominant type. So again, the result of If will be a NULL reference of type Object, which involves the runtime narrowing conversion.

     

    I want to stress once again that "Nothing" doesn't necessarily have type Object. It will fall back to Object if it doesn't have anything else to go on. But if you do give it something else to go on, e.g. "Dim x As String = Nothing", then it's not Object at all.

     

     

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, June 8, 2011 1:05 PM
  • User-41154591 posted

    wow, what a comprehensive answer! ok, I see the difference now, but that behavior is very un-intuitive :(
    (also I doubt that there are many programmers who read VB language specification to learn the language)..

    It's something we don't like in the language, and have been trying to figure out how to improve, but we haven't yet figured out a good answer:

    why not just add another keyword, lets say "Null", that would behave like C# "Null"?
    There is VBNull, but it behaves like Nothing as I understand.
    Or add another vb option like "NothingAsNull on", "NothingAsNull off". And by default it would be "off" for compatibility ?
    Yeah, I understand that it would be a mega-work, but maybe it is worth considering (just to make VB and C# closer)...

    Anyway, big thanks for the answer Lucian :)

    Thursday, June 9, 2011 2:28 PM
  • User-727365369 posted

    This was an EXCELLENT thread! Bravo!

    Thanks for the question, and to Lucian for the succint answer - which should hopefully be in MSDN/VS Help documentation!

    • IF - if you read the documenation, you'll only glean "short circuiting", it doesn't include any of the details above
    • NOTHING - documentation does include the concept, just not explained as good as the above/Lucian's blog post (by example/gotchas)
    Thursday, June 9, 2011 6:16 PM