Как компилятор VB.NET выбирает, какую перегрузку расширения нужно запустить?

Получил интересную странность - думал, что кто-то может помочь.

Это вызвало некоторую забаву с нулевыми типами из этого вопроса:

Как проверить, является ли объект допустимым?

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

Я ожидал бы, что оба вызова IsNullable будут обработаны одним и тем же компилятором, но это не так. Вызов метода расширения использует другую перегрузку для вызова нормального метода, даже если аргумент "a" не изменяется.

Мой вопрос - почему? Что заставляет компилятор передумать между двумя вызовами?

FTR: Мы используем Visual Studio 2010,.NET Framework 4.

Ответ 1

Я думаю, что это ошибка, или, по крайней мере, функция VB.NET. (Я просто не уверен, какой из VB.NET или С# ошибочен.)

Я попытался в LINQPad 4 (потому что это то, что у меня есть на машине, которую я использую), и для С# я получил False для обоих результатов для каждого типа значения и перечисления, за исключением типов Nullable конечно.

В то время как для VB.NET я получаю False и True для всех типов значений и перечислений, за исключением типов Nullable и ValueType и [Enum], которые возвращают False, False, потому что вы не можете иметь ValueType? или [Enum]?. С Option Strict Off, Object вызывает позднюю привязку и не работает во время выполнения, чтобы найти либо перегрузку, но второй результат False также потому, что вы не можете иметь Object?.

Для полноты Nullable типы возвращают True, True для обоих языков, как ожидалось.

Тот факт, что С# выполняет что-то другое (при условии, что мой тест верен) подтверждает, что ссылка на проверку С# "Лучшее преобразование" неверна (или, по крайней мере, неверно читается), поскольку С# не делает то, что интерпретируется как причина VB.NET делает то, что он делает).

Однако я согласен с тем, что проблема, вероятно, связана с неявным преобразованием в Nullable(Of T) существующим и как-то более высоким приоритетом для неявного преобразования в ValueType.

Здесь мой LINQPad 4 "Запрос" (программа С#):

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");
    }
}

Ответ 2

Перегрузка 2 будет ТОЛЬКО работать как расширение для явно определенных Nullable (of T). Например:

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

Это связано с тем, что методы расширения расширяют тип (или базовый тип), который в этом случае является Nullable (из T). Вызов a.IsNullable() никогда не вызовет перегрузку 2. Это легко понять. Это означает, что реальный вопрос заключается в том, почему вместо перегрузки 1 вызывать перегрузку 2, как стандартный перегруженный вызов метода.

CLR определит, какую Overload использовать, выполнив проверку Better Conversion", где она неявно преобразует значения, переданные в к типу параметра (ов), определенному в перегруженных методах, а затем перейти к списку правил для определения наилучшего метода использования.

Из статьи улучшенной конверсии MSDN:

Если S является T1, C1 является лучшим преобразованием.

Если S - T2, C2 - лучшее преобразование.

Puting этот код в Visual Studio покажет вам, что Overload 2 является лучшим преобразованием, поскольку целое число a (S) является неявно преобразованной версией (T2) Nullable (Integer).

    ' 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)