Perché questo metodo di estensione gettare un NullReferenceException in VB.NET?
-
18-09-2019 - |
Domanda
Per esperienza precedente avevo avuto l'impressione che è perfettamente legale (anche se forse non consigliabile) per chiamare metodi di estensione su un'istanza null. Quindi, in C #, questo codice viene compilato ed eseguito:
// code in static class
static bool IsNull(this object obj) {
return obj == null;
}
// code elsewhere
object x = null;
bool exists = !x.IsNull();
Comunque, stavo solo mettendo insieme una piccola suite di codice di esempio per gli altri membri del mio team di sviluppo (siamo appena trasferiti in .NET 3.5 e sono stato assegnato il compito di ottenere la squadra fino a velocità su alcune delle a nostra disposizione le nuove funzioni), e ho scritto quello che i Riflessione era l'equivalente VB.NET del codice di cui sopra, solo per scoprire che in realtà getta una NullReferenceException
. Il codice che ho scritto è stato questo:
' 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()
Il debugger si ferma proprio lì, come se avessi chiamato un metodo di istanza. Sto facendo qualcosa di sbagliato (per esempio, c'è qualche sottile differenza nel modo in cui ho definito il metodo di estensione tra C # e VB.NET)? E 'in realtà non legale per chiamare un metodo di estensione su un'istanza nulla in VB.NET, anche se è legale in C #? (Avrei pensato che questo era una cosa .NET al contrario di una cosa specifica lingua, ma forse mi sbagliavo.)
Qualcuno può spiegare questo a me?
Soluzione
Non è possibile estendere il tipo di oggetto in VB.NET.
Soprattutto, non permettiamo i metodi di estensione di essere chiamato fuori di ogni espressione che viene digitato in modo statico come "Oggetto". Ciò è stato necessario per evitare qualsiasi codice di ritardo legato esistente potrebbe essere scritto da essere rotto da metodi di estensione.
Riferimento:
Altri suggerimenti
Aggiornamento:
La risposta qui sotto sembra essere specifico per il caso del System.Object
è esteso. Quando si estende altre classi non v'è alcuna NullReferenceException
in VB.
Questo comportamento legato alla progettazione per la ragione indicata in questo Collegare problema :
VB consente di chiamare metodi di estensione definito su oggetti, ma solo se la variabile non è staticamente digitato come oggetto.
Il motivo è VB supporta anche late-binding, e se ci si legano a un metodo di estensione quando si effettua una chiamata fuori una variabile dichiarata come oggetto, allora è ambiguo o meno si sta cercando di chiamare un interno metodo o un altro tardo-bound metodo con lo stesso nome.
In teoria potremmo permettere questo con Strict On, ma uno dei principi di Option Strict è che non dovrebbe cambiare la semantica di il tuo codice. Se questo è stato permesso poi cambiare l'impostazione Option Strict potrebbe causare una rilegatura in silenzio per un metodo diverso, con conseguente totalmente comportamento runtime diverso.
Esempio:
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
In realtà, il compilatore VB crea una chiamata tardiva in caso il vostro variabile è staticamente tipizzato come 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
Sembra essere qualcosa di eccentrico con oggetto, forse un bug in VB o una limitazione nel compilatore, potrebbe essere necessario Sua Santità Jon Skeet commento!
In sostanza sembra essere cercando di legare in ritardo la chiamata IsNull in fase di esecuzione, piuttosto che chiamare il metodo di estensione, che fa sì che il NullReferenceException. Se si attiva l'opzione Strict vedrete questo in fase di progettazione con i ghirigori rossi.
La modifica exampleObject a qualcosa di diverso oggetto stesso permetterà il vostro codice di esempio al lavoro, anche se il valore di tale tipo è nulla.
Sembra che il problema è che l'oggetto è nullo. Inoltre, se si cerca qualcosa di simile a quanto segue, si otterrà un'eccezione dicendo che String non ha un metodo di estensione chiamato IsNull
Dim exampleObject As Object = "Test"
Dim text As String = exampleObject.IsNull()
Credo che qualsiasi valore che si sta mettendo in exampleObject, il quadro sa che tipo è. Io personalmente evitare estensioni metodi sulla classe Object, non solo in VB, ma anche in CSharp