Почему этот метод расширения создает исключение NullReferenceException в VB.NET? - PullRequest
23 голосов
/ 08 марта 2010

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

// 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()

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

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

Ответы [ 4 ]

13 голосов
/ 08 марта 2010

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

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

Ссылка:

8 голосов
/ 08 марта 2010

Обновление:

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

Такое поведение предусмотрено по причине, указанной в этой проблеме с подключением :

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

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

Теоретически мы могли бы разрешить это при строгом включении, но один из принципы Option Strict заключается в том, что он не следует менять семантику ваш код. Если это было разрешено, то изменение настроек Option 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 голосов
/ 08 марта 2010

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

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

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

0 голосов
/ 08 марта 2010

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

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

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

...