Почему этот метод расширения генерирует исключение NullReferenceException в VB.NET?

Из предыдущего опыта у меня создалось впечатление, что он совершенно легален (хотя, возможно, и нецелесообразно), ссылаться на методы расширения на нулевом экземпляре. Итак, в С# этот код компилируется и запускается:

// code in static class
static bool IsNull(this object obj) {
    return obj == null;
}

// code elsewhere
object x = null;
bool exists = !x.IsNull();

Тем не менее, я просто собрал небольшой набор примеров кода для других членов моей команды разработчиков (мы только что обновили до .NET 3.5, и мне была назначена задача получить команду до скорости на некоторых из новые функции, доступные нам), и я написал то, что я считал эквивалентом VB.NET вышеуказанного кода, только чтобы обнаружить, что на самом деле он выбрал NullReferenceException. Код, который я написал, был следующим:

' code in module '
<Extension()> _
Function IsNull(ByVal obj As Object) As Boolean
    Return obj Is Nothing
End Function

' code elsewhere '
Dim exampleObject As Object = Nothing
Dim exists As Boolean = Not exampleObject.IsNull()

Отладчик останавливается прямо там, как если бы я вызвал метод экземпляра. Я что-то делаю неправильно (например, есть ли какая-то тонкая разница в том, как я определил метод расширения между С# и VB.NET)? Действительно ли это не законно для вызова метода расширения для нулевого экземпляра в VB.NET, хотя он является законным в С#? (Я бы подумал, что это была вещь .NET, а не язык, но, возможно, я был не прав.)

Может кто-нибудь объяснить это мне?

Ответ 1

Вы не можете расширить тип объекта в VB.NET.

В основном, мы не позволяем методам расширения отменить любое выражение, которое статически вводится как "Объект". Это было необходимо для предотвращения любого существующего позднего связанного кода, который вы, возможно, написали, будучи разбитым методами расширения.

Ссылка:

Ответ 2

Обновление:

Ответ ниже, как представляется, относится к случаю расширения System.Object. При расширении других классов в VB нет NullReferenceException.

Это поведение по дизайну объясняется тем, что указано в Проблема подключения:

VB позволяет вам вызывать методы расширения, определенные в Object, но только если переменная не является статически введенный как объект.

Причина в том, что VB также поддерживает позднюю привязку, и если мы привязываемся к метод расширения при вызове от переменной, объявленной как объект, то это двусмысленно, независимо от того, вы пытаетесь вызвать расширение метод или другой поздний метод с тем же именем.

Теоретически мы можем разрешить это с помощью Strict On, но один из Принципы Option Strict - то, что это не следует изменять семантику ваш код. Если это было разрешено, тогда изменение настройки параметра Strict может привести к молчаливому другой метод, в результате чего полностью различное поведение во время выполнения.

Пример:

Imports System.Runtime.CompilerServices

Module Extensions
    <Extension()> _
    Public Function IsNull(ByVal obj As Object) As Boolean
        Return obj Is Nothing
    End Function

    <Extension()> _
    Public Function IsNull(ByVal obj As A) As Boolean
        Return obj Is Nothing
    End Function

    <Extension()> _
    Public Function IsNull(ByVal obj As String) As Boolean
        Return obj Is Nothing
    End Function

End Module

Class A
End Class

Module Module1

    Sub Main()
        ' works
        Dim someString As String = Nothing
        Dim isStringNull As Boolean = someString.IsNull()

        ' works
        Dim someA As A = Nothing
        Dim isANull As Boolean = someA.IsNull()

        Dim someObject As Object = Nothing
        ' throws NullReferenceException
        'Dim someObjectIsNull As Boolean = someObject.IsNull()

        Dim anotherObject As Object = New Object
        ' throws MissingMemberException
        Dim anotherObjectIsNull As Boolean = anotherObject.IsNull()
    End Sub

End Module

Фактически, компилятор VB создает поздний вызов привязки в случае, если ваша переменная статически введена как Object:

.locals init ([0] object exampleObject, [1] bool exists)
  IL_0000:  ldnull
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  ldnull
  IL_0004:  ldstr      "IsNull"
  IL_0009:  ldc.i4.0
  IL_000a:  newarr     [mscorlib]System.Object
  IL_000f:  ldnull
  IL_0010:  ldnull
  IL_0011:  ldnull
  IL_0012:  call       
     object [Microsoft.VisualBasic]Microsoft.VisualBasic.
       CompilerServices.NewLateBinding::LateGet(
        object,
        class [mscorlib]System.Type,
        string,
        object[],
        string[],
        class [mscorlib]System.Type[],
        bool[])
  IL_0017:  call       object [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Operators::NotObject(object)
  IL_001c:  call       bool [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToBoolean(object)
  IL_0021:  stloc.1

Ответ 3

Кажется, что что-то причудливое с Object, возможно, ошибка в VB или ограничение в компиляторе, может потребоваться, чтобы его Святейшество Джон Скит прокомментировал!

В основном, похоже, пытается запоздать связать вызов IsNull во время выполнения, а не вызывать метод расширения, что вызывает исключение NullReferenceException. Если вы включите Option Strict, вы увидите это во время разработки с красными squiggles.

Изменение exampleObject для чего-то другого, кроме самого объекта, позволит вашему образцу кода работать, даже если значение указанного типа Nothing.

Ответ 4

Кажется, проблема в том, что объект имеет значение null. Кроме того, если вы попробуете что-то вроде следующего, вы получите исключение, говорящее, что String не имеет метода расширения, называемого IsNull

Dim exampleObject As Object = "Test"
Dim text As String = exampleObject.IsNull()

Я думаю, что любое значение, которое вы вкладываете в exampleObject, фреймворк знает, какой он тип. Я лично избегаю методов расширений класса Object, а не только в VB, но также и в CSharp