7

Got an interesting oddity - thought someone might be able to help.

This came out of some fun with nullable types from this question:

How to check if an object is nullable?

Option Strict On

Module Test
  ' Call this overload 1
  <Extension()>
  Function IsNullable(obj As ValueType) As Boolean
    Return False
  End Function

  ' Call this overload 2
  <Extension()>
  Function IsNullable(Of T As {Structure})(obj As Nullable(Of T)) As Boolean
    Return True
  End Function

  Sub Test() 
    ' a is an integer!
    Dim a As Integer = 123

    ' calling IsNullable as an extension method calls overload 1 and returns false
    Dim result1 As Boolean = a.IsNullable()

    ' calling IsNullable as method calls overload 2 and returns true
    Dim result2 As Boolean = IsNullable(a)

    ' why? surely the compiler should treat both those calls as equivalent
  End Sub
End Module

I would expect that both calls to IsNullable would be treated the same by the compiler, but that is not the case. The extension method call uses a different overload to the normal method call even though the argument "a" is unchanged.

My question is why? What makes the compiler change its mind between the two calls?

FTR: We are using Visual Studio 2010, .NET Framework 4.

Community
  • 1
  • 1
James Close
  • 862
  • 9
  • 15
  • Your question is not clear "You would think both calls to IsNullable in Test sub would result in the same overload being used, actually they each use a different one." – Micah Armantrout Sep 07 '12 at 14:20
  • Point taken I have added some comments into my code. Hope this makes things clear. – James Close Sep 07 '12 at 15:14

2 Answers2

2

Overload 2 will ONLY work as an extension on explicitly defined Nullable(of T)'s. For example:

    Dim y As New Nullable(Of Integer)
    y.IsNullable()

This is because extension methods extend the type (or a base type), which in this case is Nullable(of T). Calling a.IsNullable() will never call overload 2. That's the easy part to figure out. This means the real question is why would overload 2 be called instead of overload 1 as a standard overloaded method call.

The CLR will determine which Overload to use by performing a "Better Conversion" check, where it implicitly converts the value(s) passed in to the type of the parameter(s) defined in the overloaded methods and then go down a checklist of rules to determine the best method to use.

From the MSDN Better Conversion Article:

If S is T1, C1 is the better conversion.

If S is T2, C2 is the better conversion.

Puting this code into Visual Studio will shows you that Overload 2 is the better conversion because the integer a (S) is the implicitly converted Nullable(of Integer) version of a (T2).

    ' a is an integer! 
    Dim a As Integer = 123

    Dim objValueType As ValueType = 123 'Or CType(a, ValueType)
    Dim objNullable As Nullable(Of Integer) = 123 'Or CType(a, Nullable(Of Integer))

    'Oh No, a compiler error for implicit conversion done for overload 1!
    Dim bolValueTypeConversionIsBetter As Boolean = (objValueType = a)

    'No error as long as Option Strict is off and it will equal True.
    Dim bolNullableConversionIsBetter As Boolean = (objNullable = a)
NoAlias
  • 9,218
  • 2
  • 27
  • 46
  • It's not the CLR determining this, it is the VB.NET compiler, and your link is to the C# version. Nevertheless it may be more readable than the [equivalent VB.NET documentation](http://msdn.microsoft.com/en-us/library/aa712031.aspx). – Mark Hurd Sep 08 '12 at 07:17
  • @MarkHurd, good point on the compiler. Thank you for that clarification. – NoAlias Sep 08 '12 at 13:14
  • BTW The problem with the comparison to `ValueType` is that there isn't an `==` defined for `ValueType`, not that the implicit conversion cannot be done. – Mark Hurd Sep 09 '12 at 07:15
0

I think this is a bug, or at least a VB.NET "feature". (I'm just not sure which of VB.NET or C# is wrong.)

I have tried in LINQPad 4 (because that's what I've got on the machine I'm using) and for C# I got False for both results, for every value type and enum except for Nullable types, of course.

Whereas for VB.NET I get the False and True for all value types and enums, except for Nullable types, and ValueType and [Enum] which return False, False because you can't have a ValueType? or [Enum]?. With Option Strict Off, Object causes late binding, and fails at runtime to locate either overload, but the second result is False also because you can't have Object?.

For completeness, Nullable types return True, True for both languages as expected.

The fact that C# is doing something different (assuming my test is correct) confirms the reference to the C# "Better Conversion" check is wrong (or being misread at least - in that C# is not doing what is being interpreted as why VB.NET is doing what it is doing).

However, I do agree that the issue is probably related to the implicit conversion to Nullable(Of T) existing and somehow being a higher priority to the implicit conversion to ValueType.

Here's my LINQPad 4 "Query" (C# Program):

void Main()
{
    Test.test();
}

// Define other methods and classes here
static class Test
{
    static bool IsNullable(this ValueType obj)
    {
        return false;
    }

    static bool IsNullable<T>(this T? obj) where T:struct
    {
        return true;
    }

    public static void test()
    {
        int x = 42;

        bool result1 = x.IsNullable();
        bool result2 = IsNullable(x);

        result1.Dump("result1");
        result2.Dump("result2");
    }
}
Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
  • BTW Note the error when referring to `ValueType?` in either language :-) – Mark Hurd Sep 08 '12 at 15:47
  • I note this has not changed with VB.NET 14/C# 6 (VS2015). So I assume Microsoft have decided it's a feature that the two languages differ here. (I also note the VB.NET version of the abovementioned error messages is more informative, but both still feel "funny".) – Mark Hurd Sep 19 '15 at 07:11
  • Nope, I'm now fairly sure it's still a C# bug: C# chooses an `IsNullable(this int? obj)` overload if it is defined (as does VB.NET of course). (And neither language calls it ambiguous when the `T?` overload is still available too.) – Mark Hurd Sep 19 '15 at 12:13