Ich habe einige Code und wenn es ausgeführt wird, wirft es eine NullReferenceException
, sagen:
Object reference not set to an instance of an object.
Was bedeutet das, und was kann ich tun, um diesen Fehler zu beheben?
Sie versuchen, etwas zu verwenden, das null
(oder Nothing
in VB.NET) ist. Das bedeutet, dass Sie es entweder auf null
setzen, oder dass Sie es überhaupt nicht auf irgendetwas setzen.
Wie alles andere auch, wird null
weitergereicht. Wenn es null
in Methode "A" ist, könnte es sein, dass Methode "B" ein null
an Methode "A" weitergegeben hat.
Null" kann verschiedene Bedeutungen haben:
NullReferenceException
.null
absichtlich, um anzuzeigen, dass kein sinnvoller Wert vorhanden ist. Beachten Sie, dass C# das Konzept der nullbaren Datentypen für Variablen hat (so wie Datenbanktabellen nullbare Felder haben können) - Sie können ihnen null
zuweisen, um anzuzeigen, dass kein Wert darin gespeichert ist, zum Beispiel int? a = null;
, wobei das Fragezeichen anzeigt, dass es erlaubt ist, null in der Variablen a
zu speichern. Sie können das entweder mit if (a.HasValue) {...}
oder mit if (a==null) {...}
überprüfen. Nullable-Variablen, wie a
in diesem Beispiel, erlauben den Zugriff auf den Wert über a.Value
explizit, oder ganz normal über a
. a.Value
eine InvalidOperationException
statt einer NullReferenceException
auslöst, wenn a
null
ist - Sie sollten die Prüfung vorher durchführen, d.h. wenn Sie eine weitere on-nullable Variable int b;
haben, dann sollten Sie Zuweisungen wie if (a.HasValue) { b = a.Value; }
oder kürzer if (a != null) { b = a; }
durchführen.
Der Rest dieses Artikels geht mehr ins Detail und zeigt Fehler auf, die viele Programmierer oft machen und die zu einer NullReferenceException
führen können.Die Laufzeit, die eine NullReferenceException
auslöst, bedeutet immer das Gleiche: Sie versuchen, eine Referenz zu verwenden, und die Referenz ist nicht initialisiert (oder sie war einmal initialisiert, ist aber nicht mehr initialisiert).
Das bedeutet, dass die Referenz null
ist, und Sie können nicht auf Mitglieder (wie Methoden) durch eine null
Referenz zugreifen. Der einfachste Fall:
string foo = null;
foo.ToUpper();
Das wird eine NullReferenceException
in der zweiten Zeile auslösen, weil man die Instanzmethode ToUpper()
nicht mit einer String
-Referenz aufrufen kann, die auf null
zeigt.
Wie findet man die Quelle einer NullReferenceException
? Abgesehen von der Betrachtung der Exception selbst, die genau an der Stelle ausgelöst wird, an der sie auftritt, gelten die allgemeinen Debugging-Regeln in Visual Studio: Setzen Sie strategische Haltepunkte und inspizieren Sie Ihre Variablen, indem Sie entweder mit der Maus über deren Namen fahren, ein (Quick)Watch-Fenster öffnen oder die verschiedenen Debugging-Panels wie Locals und Autos verwenden.
Wenn Sie herausfinden wollen, wo die Referenz gesetzt ist oder nicht, klicken Sie mit der rechten Maustaste auf den Namen und wählen Sie "Find All References". Sie können dann an jeder gefundenen Stelle einen Haltepunkt setzen und Ihr Programm mit angeschlossenem Debugger ausführen. Jedes Mal, wenn der Debugger an einem solchen Haltepunkt anhält, müssen Sie feststellen, ob Sie erwarten, dass die Referenz nicht null ist, die Variable untersuchen und überprüfen, ob sie auf eine Instanz zeigt, wenn Sie dies erwarten.
Wenn Sie den Programmablauf auf diese Weise verfolgen, können Sie die Stelle finden, an der die Instanz nicht null sein sollte, und herausfinden, warum sie nicht richtig gesetzt ist.
Einige häufige Szenarien, in denen die Ausnahme ausgelöst werden kann:
ref1.ref2.ref3.member
Wenn ref1 oder ref2 oder ref3 null ist, dann erhalten Sie eine NullReferenceException
. Wenn Sie das Problem lösen wollen, dann finden Sie heraus, welches der beiden null ist, indem Sie den Ausdruck in seine einfachere Entsprechung umschreiben:
var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member
Konkret könnte in HttpContext.Current.User.Identity.Name
die Eigenschaft HttpContext.Current
null sein, oder die Eigenschaft User
könnte null sein, oder die Eigenschaft Identity
könnte null sein.
public class Person {
public int Age { get; set; }
}
public class Book {
public Person Author { get; set; }
}
public class Example {
public void Foo() {
Book b1 = new Book();
int authorAge = b1.Author.Age; // You never initialized the Author property.
// there is no Person to get an Age from.
}
}
Wenn Sie die Null-Referenz des untergeordneten Objekts (Person) vermeiden wollen, können Sie es im Konstruktor des übergeordneten Objekts (Buch) initialisieren.
Das Gleiche gilt für verschachtelte Objektinitialisierungen:
Book b1 = new Book { Author = { Age = 45 } };
Dies bedeutet
Book b1 = new Book();
b1.Author.Age = 45;
Während das Schlüsselwort new
verwendet wird, erzeugt es nur eine neue Instanz von Book
, aber nicht eine neue Instanz von Person
, so dass die Eigenschaft Author
immer noch null
ist.
public class Person {
public ICollection<Book> Books { get; set; }
}
public class Book {
public string Title { get; set; }
}
Die verschachtelten Sammlungsinitialisierer verhalten sich gleich:
Person p1 = new Person {
Books = {
new Book { Title = "Title1" },
new Book { Title = "Title2" },
}
};
Dies bedeutet
Person p1 = new Person();
p1.Books.Add(new Book { Title = "Title1" });
p1.Books.Add(new Book { Title = "Title2" });
Die neue Person
erzeugt nur eine Instanz von Person
, aber die Books
Sammlung ist immer noch null
. Die Syntax des Sammlungsinitialisierers erstellt keine Sammlung
für p1.Books
, sie übersetzt nur die p1.Books.Add(...)
Anweisungen.
int[] numbers = null;
int n = numbers[0]; // numbers is null. There is no array to index.
Person[] people = new Person[5];
people[0].Age = 20 // people[0] is null. The array was allocated but not
// initialized. There is no Person to set the Age for.
long[][] array = new long[1][];
array[0][0] = 3; // is null because only the first dimension is yet initialized.
// Use array[0] = new long[2]; first.
Dictionary<string, int> agesForNames = null;
int age = agesForNames["Bob"]; // agesForNames is null.
// There is no Dictionary to perform the lookup.
public class Person {
public string Name { get; set; }
}
var people = new List<Person>();
people.Add(null);
var names = from p in people select p.Name;
string firstName = names.First(); // Exception is thrown here, but actually occurs
// on the line above. "p" is null because the
// first element we added to the list is null.
public class Demo
{
public event EventHandler StateChanged;
protected virtual void OnStateChanged(EventArgs e)
{
StateChanged(this, e); // Exception is thrown here
// if no event handlers have been attached
// to StateChanged event
}
}
Wenn Sie Felder anders benannt haben als lokale Variablen, haben Sie vielleicht festgestellt, dass Sie das Feld nie initialisiert haben.
public class Form1 {
private Customer customer;
private void Form1_Load(object sender, EventArgs e) {
Customer customer = new Customer();
customer.Name = "John";
}
private void Button_Click(object sender, EventArgs e) {
MessageBox.Show(customer.Name);
}
}
Dies kann gelöst werden, indem man der Konvention folgt, Feldern einen Unterstrich voranzustellen:
private Customer _customer;
public partial class Issues_Edit : System.Web.UI.Page
{
protected TestIssue myIssue;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Only called on first load, not when button clicked
myIssue = new TestIssue();
}
}
protected void SaveButton_Click(object sender, EventArgs e)
{
myIssue.Entry = "NullReferenceException here!";
}
}
// if the "FirstName" session value has not yet been set,
// then this line will throw a NullReferenceException
string firstName = Session["FirstName"].ToString();
Wenn die Ausnahme beim Verweis auf eine Eigenschaft von @Model
in einer ASP.NET MVC-Ansicht auftritt, müssen Sie verstehen, dass das Model
in Ihrer Action-Methode gesetzt wird, wenn Sie eine Ansicht "zurückgeben". Wenn Sie ein leeres Modell (oder eine Modelleigenschaft) von Ihrem Controller zurückgeben, tritt die Ausnahme auf, wenn die Views darauf zugreifen:
// Controller
public class Restaurant:Controller
{
public ActionResult Search()
{
return View(); // Forgot the provide a Model here.
}
}
// Razor view
@foreach (var restaurantSearch in Model.RestaurantSearch) // Throws.
{
}
<p>@Model.somePropertyName</p> <!-- Also throws -->
WPF-Steuerelemente werden während des Aufrufs von InitializeComponent
in der Reihenfolge erstellt, in der sie im visuellen Baum erscheinen. Eine NullReferenceException
wird im Fall von früh erstellten Steuerelementen mit Ereignishandlern usw. ausgelöst. , die während InitializeComponent
ausgelöst werden und auf spät erstellte Steuerelemente verweisen.
Zum Beispiel :
<Grid>
<!-- Combobox declared first -->
<ComboBox Name="comboBox1"
Margin="10"
SelectedIndex="0"
SelectionChanged="comboBox1_SelectionChanged">
<ComboBoxItem Content="Item 1" />
<ComboBoxItem Content="Item 2" />
<ComboBoxItem Content="Item 3" />
</ComboBox>
<!-- Label declared later -->
<Label Name="label1"
Content="Label"
Margin="10" />
</Grid>
Hier wird comboBox1
vor label1
erstellt. Wenn comboBox1_SelectionChanged
versucht, label1
zu referenzieren, ist es noch nicht erstellt worden.
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
label1.Content = comboBox1.SelectedIndex.ToString(); // NullReference here!!
}
Eine Änderung der Reihenfolge der Deklarationen in der XAML (d.h. Auflistung von label1
vor comboBox1
, ungeachtet der Designphilosophie) würde zumindest die NullReferenceException
hier beheben.
as
var myThing = someObject as Thing;
Das wirft keine InvalidCastException, sondern gibt einen null
zurueck, wenn der Cast scheitert (und wenn someObject selbst null ist). Also seien Sie sich dessen bewusst.
Die einfachen Versionen First()
und Single()
werfen Ausnahmen, wenn nichts vorhanden ist. Die "OrDefault"-Versionen geben in diesem Fall null zurück. Seien Sie sich dessen also bewusst.
foreach
wird ausgelöst, wenn Sie versuchen, eine Null-Sammlung zu iterieren. Normalerweise verursacht durch unerwartete null
Ergebnisse von Methoden, die Sammlungen zurückgeben.
List<int> list = null;
foreach(var v in list) { } // exception
Realistischeres Beispiel - Auswahl von Knoten aus einem XML-Dokument. Wird geworfen, wenn Knoten nicht gefunden werden, aber die anfängliche Fehlersuche zeigt, dass alle Eigenschaften gültig sind:
foreach (var node in myData.MyXml.DocumentNode.SelectNodes("//Data"))
null
und ignorieren Sie Nullwerte.Wenn Sie erwarten, dass die Referenz manchmal null ist, können Sie vor dem Zugriff auf Instanzmitglieder prüfen, ob sie null
ist:
void PrintName(Person p) {
if (p != null) {
Console.WriteLine(p.Name);
}
}
null
und geben Sie einen Standardwert an.Methodenaufrufe, von denen man erwartet, dass sie eine Instanz zurückgeben, können null
zurückgeben, zum Beispiel wenn das gesuchte Objekt nicht gefunden werden kann. Sie können wählen, ob Sie einen Standardwert zurückgeben wollen, wenn dies der Fall ist:
string GetCategory(Book b) {
if (b == null)
return "Unknown";
return b.Category;
}
null
und lösen Sie eine benutzerdefinierte Ausnahme aus.Sie können auch eine benutzerdefinierte Ausnahme auslösen, nur um sie im aufrufenden Code abzufangen:
string GetCategory(string bookTitle) {
var book = library.FindBook(bookTitle); // This may return null
if (book == null)
throw new BookNotFoundException(bookTitle); // Your custom exception
return book.Category;
}
Debug.Assert
, wenn ein Wert niemals null
sein sollte, um das Problem abzufangen, bevor die Ausnahme auftritt.Wenn Sie waehrend der Entwicklung wissen, dass eine Methode vielleicht null
zurueckgeben kann, aber niemals sollte, koennen Sie Debug.Assert()
verwenden, um so frueh wie moeglich abzubrechen, wenn es doch auftritt:
string GetTitle(int knownBookID) {
// You know this should never return null.
var book = library.GetBook(knownBookID);
// Exception will occur on the next line instead of at the end of this method.
Debug.Assert(book != null, "Library didn't return a book for known book ID.");
// Some other code
return book.Title; // Will never throw NullReferenceException in Debug mode.
}
Obwohl diese Prüfung [nicht in Ihrem Release-Build] (https://stackoverflow.com/questions/3021538/debug-assert-appears-in-release-mode) landen wird, was dazu führt, dass die NullReferenceException
erneut geworfen wird, wenn book == null
zur Laufzeit im Release-Modus.
GetValueOrDefault()
für nullbare Wertetypen, um einen Standardwert bereitzustellen, wenn sie null
sind.DateTime? appointment = null;
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the default value provided (DateTime.Now), because appointment is null.
appointment = new DateTime(2022, 10, 20);
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the appointment date, not the default
??
[C#] oder If()
[VB].Die Abkürzung für die Bereitstellung eines Standardwertes, wenn ein Null
angetroffen wird:
IService CreateService(ILogger log, Int32? frobPowerLevel)
{
var serviceImpl = new MyService(log ?? NullLog.Instance);
// Note that the above "GetValueOrDefault()" can also be rewritten to use
// the coalesce operator:
serviceImpl.FrobPowerLevel = frobPowerLevel ?? 5;
}
?.
oder ?[x]
für Arrays (verfügbar in C# 6 und VB.NET 14):Dieser Operator wird manchmal auch Safe Navigation oder Elvis (nach seiner Form) genannt. Wenn der Ausdruck auf der linken Seite des Operators null ist, wird die rechte Seite nicht ausgewertet und stattdessen null zurückgegeben. Das bedeutet Fälle wie diesen:
var title = person.Title.ToUpper();
Wenn die Person keinen Titel hat, wird eine Ausnahme ausgelöst, weil versucht wird, ToUpper
für eine Eigenschaft mit einem Nullwert aufzurufen.
In C# 5 und darunter kann dies mit geschützt werden:
var title = person.Title == null ? null : person.Title.ToUpper();
Jetzt wird die Titelvariable null sein, anstatt eine Ausnahme auszulösen. C# 6 führt eine kürzere Syntax für diesen Fall ein:
var title = person.Title?.ToUpper();
Dies führt dazu, dass die Titelvariable null
ist, und der Aufruf von ToUpper
erfolgt nicht, wenn person.Title
null
ist.
Natuerlich muessen Sie title
immer noch auf Null ueberpruefen oder den Null-Bedingungsoperator zusammen mit dem Null-Koaleszenzoperator (??
) verwenden, um einen Standardwert zu liefern:
// regular null check
int titleLength = 0;
if (title != null)
titleLength = title.Length; // If title is null, this would throw NullReferenceException
// combining the `?` and the `??` operator
int titleLength = title?.Length ?? 0;
Ebenso können Sie für Arrays ?[i]
wie folgt verwenden:
int[] myIntArray=null;
var i=5;
int? elem = myIntArray?[i];
if (!elem.HasValue) Console.WriteLine("No value");
Dies bewirkt das Folgende: Wenn myIntArray null ist, gibt der Ausdruck null zurück und Sie können ihn gefahrlos überprüfen. Wenn er ein Array enthält, wird er dasselbe tun wie:
elem = myIntArray[i];
und gibt das ite Element zurück.
Die in C# 8 eingeführten null context's und nullable reference types führen eine statische Analyse von Variablen durch und geben eine Compiler-Warnung aus, wenn ein Wert potenziell null sein kann oder auf null gesetzt wurde. Die nullable Referenztypen erlauben es, dass Typen explizit null sein dürfen. Der Annotationskontext "nullable" und der Warnkontext "nullable" können für ein Projekt mithilfe des Elements "nullable" in Ihrer csproj-Datei festgelegt werden. Dieses Element konfiguriert, wie der Compiler die Nullbarkeit von Typen interpretiert und welche Warnungen erzeugt werden. Gültige Einstellungen sind:
?
wird an den Typ der Variablen angehängt.C# unterstützt "Iteratorblöcke" (in einigen anderen populären Sprachen "Generatoren" genannt). Null-Dereferenz-Ausnahmen können in Iteratorblöcken wegen der verzögerten Ausführung besonders schwierig zu debuggen sein:
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
for (int i = 0; i < count; ++i)
yield return f.MakeFrob();
}
...
FrobFactory factory = whatever;
IEnumerable<Frobs> frobs = GetFrobs();
...
foreach(Frob frob in frobs) { ... }
Wenn was auch immer
in Null
resultiert, wird MakeFrob
geworfen. Nun könnten Sie denken, dass das Richtige ist, dies zu tun:
// DON'T DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
if (f == null)
throw new ArgumentNullException("f", "factory must not be null");
for (int i = 0; i < count; ++i)
yield return f.MakeFrob();
}
Warum ist das falsch? Weil der Iterator-Block bis zum foreach
nicht wirklich läuft! Der Aufruf von GetFrobs
gibt einfach ein Objekt zurück, das bei der Iteration den Iterator-Block ausführt.
Indem Sie eine Null-Prüfung wie diese schreiben, verhindern Sie die Null-Dereferenz, aber Sie verschieben die Null-Argument-Ausnahme an den Punkt der Iteration, nicht an den Punkt des Aufrufs, und das ist sehr verwirrend beim Debuggen.
Die richtige Lösung ist:
// DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
// No yields in a public method that throws!
if (f == null)
throw new ArgumentNullException("f", "factory must not be null");
return GetFrobsForReal(f, count);
}
private IEnumerable<Frob> GetFrobsForReal(FrobFactory f, int count)
{
// Yields in a private method
Debug.Assert(f != null);
for (int i = 0; i < count; ++i)
yield return f.MakeFrob();
}
Das heißt, eine private Hilfsmethode, die die Iterator-Blocklogik hat, und eine öffentliche Oberfl�chenmethode, die die Null-Pr�fung durchf�hrt und den Iterator zur�ckgibt. Wenn nun GetFrobs
aufgerufen wird, erfolgt die Nullprüfung sofort, und dann wird GetFrobsForReal
ausgeführt, wenn die Sequenz iteriert wird.
Wenn Sie sich die Referenzquelle für LINQ to Objects ansehen, werden Sie feststellen, dass diese Technik durchgängig verwendet wird. Es ist etwas umständlicher zu schreiben, aber es macht das Debuggen von Nullity-Fehlern viel einfacher. Optimieren Sie Ihren Code für die Bequemlichkeit des Aufrufers, nicht für die Bequemlichkeit des Autors.
C# verfügt über einen "unsicheren" Modus, der, wie der Name schon sagt, extrem gefährlich ist, da die normalen Sicherheitsmechanismen, die für Speichersicherheit und Typsicherheit sorgen, nicht durchgesetzt werden. Sie sollten keinen unsicheren Code schreiben, wenn Sie nicht ein gründliches und tiefes Verständnis der Funktionsweise des Speichers haben. Im unsicheren Modus sollten Sie sich über zwei wichtige Tatsachen im Klaren sein:
Es bedeutet, dass die betreffende Variable auf nichts zeigt. Ich könnte dies folgendermaßen erzeugen:
SqlConnection connection = null;
connection.Open();
Das führt zu dem Fehler, weil ich die Variable "Verbindung" zwar deklariert habe, sie aber auf nichts zeigt. Wenn ich versuche, das Mitglied "
Open`" aufzurufen, gibt es keinen Verweis, der aufgelöst werden kann, und es wird der Fehler ausgelöst.
Um diesen Fehler zu vermeiden:
object == null
.Das Resharper-Tool von JetBrains identifiziert jede Stelle in Ihrem Code, an der ein Null-Referenz-Fehler auftreten kann, und ermöglicht es Ihnen, eine Null-Prüfung einzubauen. Dieser Fehler ist meines Erachtens die Hauptquelle für Bugs.
Das bedeutet, dass Ihr Code eine Objektreferenzvariable verwendet, die auf null gesetzt wurde (d.h. sie referenziert nicht auf eine tatsächliche Objektinstanz).
Um diesen Fehler zu vermeiden, sollten Objekte, die null sein könnten, vor ihrer Verwendung auf null getestet werden.
if (myvar != null)
{
// Go ahead and use myvar
myvar.property = ...
}
else
{
// Whoops! myvar is null and cannot be used without first
// assigning it to an instance reference
// Attempting to use myvar here will result in NullReferenceException
}