Почему этот метод расширения генерирует исключение 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