¿Por qué este método de extensión arroja una NullReferenceException en VB.NET?
extension-methods (4)
Por experiencia previa, tenía la impresión de que es perfectamente legal (aunque quizás no aconsejable) llamar a los métodos de extensión en una instancia nula. Entonces en C #, este código compila y ejecuta:
// code in static class
static bool IsNull(this object obj) {
return obj == null;
}
// code elsewhere
object x = null;
bool exists = !x.IsNull();
Sin embargo, estaba armando un pequeño paquete de código de ejemplo para los demás miembros de mi equipo de desarrollo (acabamos de actualizar a .NET 3.5 y se me ha asignado la tarea de poner al equipo al día sobre algunas de las nuevas funciones). disponible para nosotros), y escribí lo que pensé que era el equivalente VB.NET del código anterior, solo para descubrir que en realidad arroja una NullReferenceException
. El código que escribí fue este:
'' 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()
El depurador se detiene allí mismo, como si hubiera llamado a un método de instancia. ¿Estoy haciendo algo mal (por ejemplo, hay alguna diferencia sutil en la forma en que definí el método de extensión entre C # y VB.NET)? ¿En realidad no es legal llamar a un método de extensión en una instancia nula en VB.NET, aunque es legal en C #? (Hubiera pensado que esto era algo de .NET en lugar de algo específico del idioma, pero quizás estaba equivocado).
¿Alguien puede explicarme esto?
Actualizar:
La respuesta a continuación parece ser específica para el caso en que System.Object
se extienda. Al extender otras clases, no hay NullReferenceException
en VB.
Este comportamiento es por diseño por el motivo indicado en este problema de Connect :
VB le permite llamar a los métodos de extensión definidos en Object, pero solo si la variable no está tipada estáticamente como Object.
El motivo es que VB también admite la vinculación tardía, y si nos vinculamos a un método de extensión cuando realiza una llamada fuera de una variable declarada como Objeto, entonces es ambiguo si está intentando llamar a un método de extensión oa un método de extensión diferente. método vinculado con el mismo nombre.
Teóricamente podríamos permitir esto con Strict On, pero uno de los principios de Option Strict es que no debería cambiar la semántica de su código. Si esto está permitido, entonces cambiar la configuración de Option Strict podría causar un rebinding silencioso a un método diferente, lo que da como resultado un comportamiento de tiempo de ejecución totalmente diferente.
Ejemplo:
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
De hecho, el compilador de VB crea una llamada de enlace tardía en caso de que su variable esté tipada estáticamente como 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
No puede extender el tipo de objeto en VB.NET.
Principalmente, no permitimos que los métodos de extensión se desactiven de ninguna expresión que esté tipeada estáticamente como "Objeto". Esto fue necesario para evitar que los códigos de extensión destruyan cualquier código de límite tardío existente que haya escrito.
Referencia:
Parece que el problema es que el objeto es nulo. Además, si prueba algo como lo siguiente, obtendrá una excepción que dice que String no tiene un método de extensión llamado IsNull.
Dim exampleObject As Object = "Test"
Dim text As String = exampleObject.IsNull()
Creo que cualquier valor que pongas en exampleObject, el framework sabe de qué tipo es. Personalmente, evitaría los métodos de extensiones en la clase Object, no solo en VB sino también en CSharp.
Parece ser algo peculiar con Object, posiblemente un error en VB o una limitación en el compilador, ¡podría necesitar su Santidad Jon Skeet para comentar!
Básicamente, parece estar intentando enlazar tarde la llamada IsNull en tiempo de ejecución, en lugar de llamar al método de extensión, que causa la NullReferenceException. Si activa Option Strict, verá esto en el momento del diseño con los garabatos rojos.
Cambiar exampleObject a algo que no sea el Object en sí mismo permitirá que su código de muestra funcione, incluso si el valor de dicho tipo es Nothing.