Wat is een NullReferenceException, en hoe los ik het op?
Ik heb wat code en wanneer het wordt uitgevoerd, gooit het een NullReferenceException
, die zegt:
Object reference is niet ingesteld op een instantie van een object.
Wat betekent dit, en wat kan ik doen om deze fout te herstellen?
1876
3
Wat is de oorzaak?
Bottom Line
Je probeert iets te gebruiken dat
null
is (ofNothing
in VB.NET). Dit betekent dat je het of opnull
hebt gezet, of dat je het nooit op iets hebt gezet. Net als iets anders, wordtnull
doorgegeven. Als hetnull
is in methode "A", kan het zijn dat methode "B" eennull
aan methode "A" heeft doorgegeven.null
kan verschillende betekenissen hebben:NullReferenceException
.null
met opzet om aan te geven dat er geen betekenisvolle waarde beschikbaar is. Merk op dat C# het concept van nullable datatypes voor variabelen heeft (zoals databasetabellen nullable velden kunnen hebben) - je kuntnull
aan ze toewijzen om aan te geven dat er geen waarde in is opgeslagen, bijvoorbeeldint? a = null;
waarbij het vraagteken aangeeft dat het is toegestaan om null op te slaan in variabelea
. Je kunt dat controleren metif (a.HasValue) {...}
of metif (a==null) {...}
. Nullable variabelen, zoalsa
dit voorbeeld, laten toe om de waarde te benaderen viaa.Value
expliciet, of gewoon zoals normaal viaa
.Noteer** dat het benaderen via
a.Value
eenInvalidOperationException
werpt in plaats van eenNullReferenceException
alsa
null
is - je moet de controle vooraf doen, d.w.z. als je een andere on-nullable variabeleint b;
hebt, dan moet je opdrachten doen alsif (a.HasValue) { b = a.Value; }
of korterif (a != null) { b = a; }
. De rest van dit artikel gaat meer in detail en laat fouten zien die veel programmeurs vaak maken die kunnen leiden tot eenNullReferenceException
.Meer specifiek
Het gooien van een
NullReferenceException
door de runtime betekent altijd hetzelfde: je probeert een referentie te gebruiken, en de referentie is niet geinitialiseerd (of hij was eenmaal geinitialiseerd, maar is niet langer geinitialiseerd). Dit betekent dat de referentienull
is, en dat je geen toegang kunt krijgen tot leden (zoals methodes) via eennull
referentie. Het eenvoudigste geval:Dit zal een
NullReferenceException
gooien op de tweede regel omdat je de instantie methodeToUpper()
niet kan aanroepen op eenstring
referentie die naarnull
wijst.Debugging
Hoe vind je de bron van een
NullReferenceException
? Afgezien van het kijken naar de exception zelf, die precies wordt gegooid op de plaats waar hij optreedt, gelden de algemene regels van debugging in Visual Studio: plaats strategische breakpoints en inspecteer je variabelen, hetzij door met de muis over hun namen te gaan, een (Quick)Watch venster te openen of gebruik te maken van de diverse debugging panels zoals Locals en Autos. Als u wilt weten waar de referentie al dan niet is ingesteld, klik dan met de rechtermuisknop op zijn naam en selecteer "Find All References". U kunt dan een breekpunt plaatsen op elke gevonden locatie en uw programma uitvoeren met de debugger aangesloten. Elke keer dat de debugger breekt op zo'n breekpunt, moet je bepalen of je verwacht dat de verwijzing niet-null is, de variabele inspecteren en verifiëren dat deze wijst naar een instantie wanneer je dat verwacht. Door de programmastroom op deze manier te volgen, kun je de plaats vinden waar de instantie niet null zou moeten zijn, en waarom hij'niet juist is ingesteld.Voorbeelden
Enkele veel voorkomende scenario's waar de exception gegooid kan worden:
Generic
Als ref1 of ref2 of ref3 null is, dan krijg je'een
NullReferenceException
. Als je het probleem wilt oplossen, zoek dan uit welke null is door de expressie te herschrijven naar zijn eenvoudiger equivalent:Specifiek, in
HttpContext.Current.User.Identity.Name
, zou deHttpContext.Current
null kunnen zijn, of deUser
eigenschap zou null kunnen zijn, of deIdentity
eigenschap zou null kunnen zijn.Indirect
Als u de null reference van het kind (Person) wilt vermijden, kunt u het initialiseren in de constructor van het bovenliggende (Book) object's.
Nested Object Initializers
Hetzelfde geldt voor geneste object-initializers:
Dit vertaalt zich naar
Hoewel het
new
sleutelwoord wordt gebruikt, creëert het alleen een nieuwe instantie vanBook
, maar niet een nieuwe instantie vanPerson
, dus deAuthor
de eigenschap is nog steedsnull
.Nested Collection Initializers
De geneste collectie initializers gedragen zich hetzelfde:
Dit vertaalt zich naar
De
new Person
creëert alleen een instantie vanPerson
, maar deBooks
collectie is nog steedsnull
. De collectie initializer syntax creëert geen collectie voorp1.Books
, het vertaalt zich alleen naar dep1.Books.Add(...)
statements.Array
Array Elements
Jagged Arrays
Collection/List/Dictionary
Range Variabele (Indirect/Deferred)
Events
Bad Naming Conventions:
Als je velden een andere naam hebt gegeven dan locals, heb je misschien gemerkt dat je het veld nooit geïnitialiseerd hebt.
Dit kan worden opgelost door de conventie te volgen om velden voor te schrijven met een underscore:
ASP.NET Page Life cycle:
ASP.NET Session Values
ASP.NET MVC lege view models
Als de uitzondering optreedt bij het verwijzen naar een eigenschap van
@Model
in een ASP.NET MVC view, moet je begrijpen dat deModel
wordt ingesteld in je action method, wanneer je een viewreturn
. Wanneer je een leeg model (of model property) retourneert vanuit je controller, treedt de uitzondering op wanneer de views het model benaderen:WPF Besturingselement creatie volgorde en gebeurtenissen
WPF controls worden aangemaakt tijdens de aanroep van
InitializeComponent
in de volgorde waarin ze verschijnen in de visuele boom. EenNullReferenceException
zal worden opgeworpen in het geval van te vroeg aangemaakte controls met event handlers, enz. , die afgaan tijdensInitializeComponent
en die verwijzen naar laat gecreëerde besturingselementen. Bijvoorbeeld :Hier is
comboBox1
eerder aangemaakt danlabel1
. AlscomboBox1_SelectionChanged
probeert te refereren aan `label1, zal deze nog niet zijn aangemaakt.Het veranderen van de volgorde van de declaraties in de XAML (d.w.z.
label1
voorcomboBox1
, het negeren van ontwerpfilosofische kwesties, zou op zijn minst deNullReferenceException
hier oplossen.Giet met
as
Dit gooit geen InvalidCastException maar geeft een
null
terug als de cast mislukt (en als someObject zelf null is). Wees je daar dus van bewust.LINQ FirstOrDefault() en SingleOrDefault()
De gewone versies
First()
enSingle()
gooien excepties wanneer er niets is. De "OrDefault" versies geven in dat geval null terug. Wees je daar dus van bewust.foreach
foreach
gooit als je een null collection probeert te itereren. Meestal veroorzaakt door onverwachtenull
resultaten van methodes die collecties retourneren.Meer realistisch voorbeeld - selecteer nodes uit een XML document. Zal gooien als nodes niet gevonden worden, maar initiële debugging toont aan dat alle eigenschappen geldig zijn:
manieren om te vermijden
Controleer expliciet op
null
en negeer null waarden.Als je verwacht dat de referentie soms null is, kun je controleren of deze
null
is voordat je instance members benadert:Controleer expliciet op
null
en geef een standaard waarde.Methoden waarvan je verwacht dat ze een instantie teruggeven kunnen
null
teruggeven, bijvoorbeeld wanneer het gezochte object niet gevonden kan worden. Je kunt ervoor kiezen om een standaard waarde terug te geven wanneer dit het geval is:Expliciet controleren op
null
bij methode-aanroepen en een aangepaste uitzondering gooien.Je kunt ook een aangepaste exception gooien, alleen om deze op te vangen in de aanroepende code:
Gebruik
Debug.Assert
als een waarde nooitnull
mag zijn, om het probleem eerder op te vangen dan dat de exceptie optreedt.Wanneer je tijdens de ontwikkeling weet dat een methode misschien wel
null
kan, maar nooit mag retourneren, kun jeDebug.Assert()
gebruiken om zo snel mogelijk te breken wanneer dit toch gebeurt:Hoewel deze controle niet in je release build terecht zal komen, waardoor het opnieuw de
NullReferenceException
gooit wanneerbook == null
bij runtime in release mode.Gebruik
GetValueOrDefault()
voor nullable value types om een standaard waarde te geven als zenull
zijn.Gebruik de null coalescing operator:
??
[C#] ofIf()
[VB].De afkorting voor het geven van een standaard waarde als er een
null
wordt aangetroffen:Gebruik de null condition operator:
?.
of?[x]
voor arrays (beschikbaar in C# 6 en VB.NET 14):Dit wordt ook wel de veilige navigatie of Elvis (naar zijn vorm) operator genoemd. Als de expressie aan de linkerkant van de operator null is, dan wordt de rechterkant niet geëvalueerd, en wordt in plaats daarvan null teruggegeven. Dat betekent dat gevallen als deze:
Als de persoon geen titel heeft, zal dit een exception opleveren omdat het
ToUpper
probeert aan te roepen op een eigenschap met een null waarde. In C# 5 en lager, kan dit bewaakt worden met:Nu zal de titel variabele null zijn in plaats van een exception te gooien. C# 6 introduceert hiervoor een kortere syntaxis:
Dit zal ertoe leiden dat de titelvariabele
null
is, en dat de aanroep aanToUpper
niet wordt gedaan alsperson.Title
null
is. Natuurlijk moet jetitle
nog controleren opnull
of de null condition operator samen met de null coalescing operator (??
) gebruiken om een standaard waarde te geven:Evenzo kun je voor arrays
?[i]
als volgt gebruiken:Dit zal het volgende doen: Als myIntArray null is, retourneert de expressie null en kun je het veilig controleren. Als het een array bevat, zal het hetzelfde doen als:
elem = myIntArray[i];
en retourneert het ith element.Gebruik null context (beschikbaar in C# 8):
Geïntroduceerd in C# 8 er null context's en nullable reference types voeren statische analyse uit op variabelen en geven een compiler waarschuwing als een waarde potentieel null kan zijn of op null gezet is. De nullable reference types maken het mogelijk om types expliciet toe te staan om null te zijn. De nullable annotatie context en nullable waarschuwing context kunnen worden ingesteld voor een project met behulp van het Nullable element in je csproj bestand. Dit element configureert hoe de compiler de nullabiliteit van types interpreteert en welke waarschuwingen gegenereerd worden. Geldige instellingen zijn:
?
wordt toegevoegd aan het type van de variabele.Speciale technieken voor het debuggen en repareren van null derefs in iterators
C# ondersteunt "iterator blocks" (in sommige andere populaire talen "generators" genoemd). Null dereference uitzonderingen kunnen bijzonder lastig te debuggen zijn in iterator blokken vanwege de uitgestelde executie:
Als
whatever
resulteert innull
dan zalMakeFrob
gooien. Nu zou je kunnen denken dat het juiste is om het volgende te doen:Waarom is dit fout? Omdat het iteratorblok eigenlijk pas loopt bij de
foreach
! De aanroep vanGetFrobs
retourneert gewoon een object dat wanneer iterated het iterator blok zal uitvoeren. Door op deze manier een null check te schrijven voorkom je de null dereference, maar je verplaatst de null argument uitzondering naar het punt van de iteratie, niet naar het punt van de aanroep, en dat is zeer verwarrend om te debuggen. De juiste oplossing is:Dat wil zeggen, maak een private helper methode die de iterator block logica heeft, en een publieke surface methode die de null check doet en de iterator teruggeeft. Als nu
GetFrobs
wordt aangeroepen, vindt de null check onmiddellijk plaats, en dan wordtGetFrobsForReal
uitgevoerd als de reeks wordt geiterateerd. Als je de referentie bron voor LINQ to Objects bekijkt, zul je zien dat deze techniek overal wordt gebruikt. Het is iets onhandiger om te schrijven, maar het maakt het debuggen van nullity fouten veel eenvoudiger. Optimaliseer je code voor het gemak van de aanroeper, niet het gemak van de auteur.Een opmerking over null dereferences in onveilige code
C# heeft een "unsafe" mode die, zoals de naam al aangeeft, extreem gevaarlijk is omdat de normale veiligheidsmechanismen die zorgen voor geheugenveiligheid en typeveiligheid niet worden afgedwongen. U zou geen onveilige code moeten schrijven tenzij u een grondig en diep begrip heeft van hoe geheugen werkt. In onveilige modus, moet u zich bewust zijn van twee belangrijke feiten:
Zowel een null pointer als een null reference worden in C# intern voorgesteld als het getal nul, en dus zal elke poging om deze te dereferen naar de overeenkomstige geheugenopslag het besturingssysteem een fout doen produceren. De .NET runtime detecteert dan deze fout en maakt er de null dereference exception van. Dat is waarom het dereferen van zowel een null pointer als een null reference dezelfde uitzondering oplevert. Hoe zit het met het tweede punt? Dereferencing van elke ongeldige pointer die in de laagste pagina van het virtuele geheugen valt veroorzaakt dezelfde besturingssysteemfout, en daardoor dezelfde exceptie. Waarom is dit zinvol? Stel dat we een struct hebben met twee ints, en een onbeheerde pointer die gelijk is aan null. Als we proberen om de tweede int in de struct te verwijderen, zal de CLR niet proberen om de opslag op locatie nul te benaderen; het zal de opslag op locatie vier benaderen. Maar logisch gezien is dit een null dereference omdat we op dat adres komen via de null. Als je werkt met onveilige code en je krijgt een null dereference exceptie, wees je er dan van bewust dat de pointer in kwestie niet null hoeft te zijn. Het kan elke plaats in de onderste pagina zijn, en deze exceptie zal worden geproduceerd.
Het betekent dat de variabele in kwestie op niets is gericht. Ik zou dit zo kunnen genereren:
Dat zal de foutmelding opleveren omdat ik weliswaar de variabele "
connection
" heb gedeclareerd, maar hij is nergens naar gericht. Wanneer ik het lid "Open
" probeer aan te roepen, is er'geen referentie voor het om op te lossen, en het zal de fout werpen.Om deze fout te vermijden:
object == null
.JetBrains' Resharper tool zal elke plaats in uw code identificeren die de mogelijkheid van een null reference fout heeft, zodat u een null check kunt plaatsen. Deze fout is de nummer een bron van bugs, IMHO.
Het betekent dat je code een object referentie variabele gebruikte die op null was gezet (d.w.z. het verwees niet naar een werkelijk object instantie).
Om deze fout te voorkomen, moeten objecten die null kunnen zijn, getest worden op null voordat ze gebruikt worden.