Как компилятор 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)