J'ai du code et quand il s'exécute, il lance un NullReferenceException
, disant:
La référence d'objet n'est pas définie à une instance d'un objet.
Qu'est-ce que cela signifie et que puis-je faire pour corriger cette erreur?
Vous essayez d'utiliser quelque chose qui est null
(ou Nothing
dans VB.NET). Cela signifie que vous le définissez sur null
ou que vous ne le définissez jamais sur quoi que ce soit.
Comme toute autre chose, se null
fait passer. Si c'est null
dans la méthode "A", il se peut que la méthode "B" ait passé a null
à la méthode "A".
null
peut avoir différentes significations:
NullReferenceException
.null
intentionnellement pour indiquer qu'aucune valeur significative n'est disponible. Notez que C # a le concept de types de données Nullable pour les variables (comme les tables de base de données peuvent avoir des champs Nullable) - vous pouvez leur attribuer null
pour indiquer qu'il n'y a pas de valeur stockée, par exemple int? a = null;
lorsque le point d'interrogation indique qu'il est autorisé à stocker null variable a
. Vous pouvez le vérifier avec if (a.HasValue) {...}
ou avec if (a==null) {...}
. Les variables nulles, comme a
cet exemple, permettent d'accéder à la valeur via a.Value
explicitement, ou tout simplement via a
. a.Value
lève un InvalidOperationException
au lieu d'un NullReferenceException
if a
estnull
- vous devriez faire la vérification au préalable, c'est-à-dire que si vous avez une autre variable sur nullable, int b;
vous devriez faire des affectations comme if (a.HasValue) { b = a.Value; }
ou plus courtes if (a != null) { b = a; }
.Le reste de cet article va plus en détail et montre des erreurs que de nombreux programmeurs font souvent et qui peuvent conduire à un NullReferenceException
.
Le runtime
lancer a signifie NullReferenceException
toujours la même chose: vous essayez d'utiliser une référence, et la référence n'est pas initialisée (ou elle a été une fois initialisée, mais n'est plus initialisée).
Cela signifie que la référence est null
et que vous ne pouvez pas accéder aux membres (tels que les méthodes) via une null
référence. Le cas le plus simple:
string foo = null;
foo.ToUpper();
Cela lancera un NullReferenceException
à la deuxième ligne car vous ne pouvez pas appeler la méthode d'instance ToUpper()
sur une string
référence pointant vers null
.
Comment trouvez-vous la source d'un NullReferenceException
? En plus de regarder l'exception elle-même, qui sera lancée exactement à l'endroit où elle se produit, les règles générales de débogage dans Visual Studio s'appliquent: placez des points d'arrêt stratégiques et inspectez vos variables , soit en passant la souris sur leurs noms, en ouvrant un ( Rapide) Regardez la fenêtre ou en utilisant les différents panneaux de débogage tels que Locals et Autos.
Si vous voulez savoir où se trouve ou non la référence, cliquez avec le bouton droit sur son nom et sélectionnez "Rechercher toutes les références". Vous pouvez ensuite placer un point d'arrêt à chaque emplacement trouvé et exécuter votre programme avec le débogueur attaché. Chaque fois que le débogueur s'arrête sur un tel point d'arrêt, vous devez déterminer si vous vous attendez à ce que la référence soit non nulle, inspectez la variable et vérifiez qu'elle pointe vers une instance lorsque vous vous y attendez.
En suivant le déroulement du programme de cette façon, vous pouvez trouver l'emplacement où l'instance ne doit pas être nulle et pourquoi elle n'est pas correctement définie.
Quelques scénarios courants dans lesquels l'exception peut être levée:
ref1.ref2.ref3.member
Si ref1 ou ref2 ou ref3 est nul, alors vous obtiendrez un NullReferenceException
. Si vous voulez résoudre le problème, trouvez lequel est nul en réécrivant l'expression dans son équivalent le plus simple:
var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member
Plus précisément, dans HttpContext.Current.User.Identity.Name
, la HttpContext.Current
peut être null, ou la User
propriété peut être nulle, ou la Identity
propriété peut être nulle.
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.
}
}
Si vous souhaitez éviter la référence null enfant (Person), vous pouvez l'initialiser dans le constructeur de l'objet parent (Book).
La même chose s'applique aux initialiseurs d'objets imbriqués:
Book b1 = new Book
{
Author = { Age = 45 }
};
Cela se traduit par:
Book b1 = new Book();
b1.Author.Age = 45;
Lorsque le new
mot-clé est utilisé, il crée uniquement une nouvelle instance de Book
, mais pas une nouvelle instance de Person
, donc Author
la propriété est toujours null
.
public class Person
{
public ICollection<Book> Books { get; set; }
}
public class Book
{
public string Title { get; set; }
}
La collection imbriquée Initializers
se comporte de la même manière:
Person p1 = new Person
{
Books = {
new Book { Title = "Title1" },
new Book { Title = "Title2" },
}
};
Cela se traduit par:
Person p1 = new Person();
p1.Books.Add(new Book { Title = "Title1" });
p1.Books.Add(new Book { Title = "Title2" });
Le new Person
seul crée une instance de Person
, mais la Books
collection est toujours null
. La Initializer
syntaxe de la collection ne crée pas de collection pour p1.Books
, elle se traduit uniquement par les p1.Books.Add(...)
instructions.
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
}
}
Si vous avez nommé les champs différemment des locaux, vous avez peut-être réalisé que vous n'avez jamais initialisé le champ.
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);
}
}
Cela peut être résolu en suivant la convention de préfixer les champs avec un trait de soulignement:
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();
Si l'exception se produit lors du référencement d'une propriété de @Model
dans un ASP.NET MVC View
, vous devez comprendre que le Model
est défini dans votre méthode d'action, lorsque vous return
une vue. Lorsque vous renvoyez un modèle (ou une propriété de modèle) vide depuis votre contrôleur, l'exception se produit lorsque les vues y accèdent:
// 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
les contrôles sont créés lors de l'appel InitializeComponent
dans l'ordre dans lequel ils apparaissent dans l'arborescence visuelle. Un NullReferenceException
sera déclenché dans le cas de contrôles créés au début avec des gestionnaires d'événements, etc., qui se déclenchent pendant InitializeComponent
lesquels font référence à des contrôles créés en retard.
Par exemple:
<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>
Voici comboBox1
créé avant label1
. Si comboBox1_SelectionChanged
vous essayez de référencer `label1, il n'aura pas encore été créé.
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
label1.Content = comboBox1.SelectedIndex.ToString(); // NullReference here!!
}
Changer l'ordre des déclarations dans le XAML
(c'est-à-dire énumérer label1
avant comboBox1
, en ignorant les problèmes de philosophie de conception, résoudrait au moins le NullReferenceException
ici.
as
var myThing = someObject as Thing;
Cela ne lance pas un InvalidCastException
mais retourne un null
lorsque la conversion échoue (et quand someObject
est lui-même nul). Soyez donc conscient de cela.
FirstOrDefault()
etSingleOrDefault()
Les versions simples First()
et Single()
jettent des exceptions quand il n'y a rien. Les versions "OrDefault" renvoient null dans ce cas. Soyez donc conscient de cela.
foreach
jette lorsque vous essayez d'itérer une collection nulle. Généralement causé par un null
résultat inattendu des méthodes qui renvoient des collections.
List<int> list = null;
foreach(var v in list) { } // exception
Exemple plus réaliste - sélectionnez des nœuds dans un document XML. Lance si les nœuds ne sont pas trouvés mais le débogage initial montre que toutes les propriétés sont valides:
foreach (var node in myData.MyXml.DocumentNode.SelectNodes("//Data"))
null
et ignorez explicitement les valeurs nulles.Si vous vous attendez à ce que la référence soit parfois nulle, vous pouvez la vérifier null
avant d'accéder aux membres de l'instance:
void PrintName(Person p)
{
if (p != null)
{
Console.WriteLine(p.Name);
}
}
null
et indiquez explicitement une valeur par défaut.Les méthodes que vous prévoyez de renvoyer peuvent renvoyer une instance null
, par exemple lorsque l'objet recherché est introuvable. Vous pouvez choisir de renvoyer une valeur par défaut lorsque c'est le cas:
string GetCategory(Book b)
{
if (b == null)
return "Unknown";
return b.Category;
}
null
appels de méthode et lève une exception personnalisée.Vous pouvez également lever une exception personnalisée, uniquement pour l'attraper dans le code d'appel:
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
si une valeur ne doit jamais être null
, pour détecter le problème avant que l'exception ne se produise.Lorsque vous savez au cours du développement qu'une méthode peut peut-être, mais ne doit jamais revenir null
, vous pouvez utiliser Debug.Assert()
pour interrompre dès que possible quand cela se produit:
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.
}
Bien que cette vérification ne se terminera pas dans votre version de version , ce qui l' amènera à lancer à NullReferenceException
nouveau le lors book == null
de l'exécution en mode de publication.
GetValueOrDefault()
pour les nullable
types valeur pour fournir une valeur par défaut lorsqu'ils le sont null
.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 #] ou If()
[VB].Le raccourci pour fournir une valeur par défaut lorsqu'un null
est rencontré:
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;
}
?.
ou ?[x]
pour les tableaux (disponible en C # 6 et VB.NET 14):Ceci est aussi parfois appelé l'opérateur de navigation sécurisée ou Elvis (après sa forme). Si l'expression sur le côté gauche de l'opérateur est null, alors le côté droit ne sera pas évalué et null est renvoyé à la place. Cela signifie des cas comme celui-ci:
var title = person.Title.ToUpper();
Si la personne n'a pas de titre, cela lèvera une exception car elle essaie d'appeler ToUpper
une propriété avec une valeur nulle.
Dans C# 5
et ci-dessous, cela peut être gardé avec:
var title = person.Title == null ? null : person.Title.ToUpper();
Désormais, la variable title sera nulle au lieu de lancer une exception. C # 6 introduit une syntaxe plus courte pour cela:
var title = person.Title?.ToUpper();
Cela entraînera la valeur de la variable title null
et l'appel à ToUpper
n'est pas effectué si l' person.Title
est null
.
Bien sûr, vous devez toujours vérifier la valeur title
null ou utiliser l'opérateur de condition null avec l'opérateur de fusion nul ( ??
) pour fournir une valeur par défaut:
// 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;
De même, pour les tableaux, vous pouvez utiliser ?[i]
comme suit:
int[] myIntArray = null;
var i = 5;
int? elem = myIntArray?[i];
if (!elem.HasValue) Console.WriteLine("No value");
Cela fera ce qui suit: Si myIntArray
est null, l'expression renvoie null et vous pouvez la vérifier en toute sécurité. S'il contient un tableau, il fera la même chose que: elem = myIntArray[i];
et renvoie l' i<sup>th</sup>
élément.
Introduits dans C# 8
ces types de contexte null et de référence nullable effectuent une analyse statique sur les variables et fournissent un avertissement au compilateur si une valeur peut être potentiellement nulle ou avoir été définie sur null. Les types de référence Nullable permettent aux types d'être explicitement autorisés à être Null.
Le contexte d'annotation Nullable et le contexte d'avertissement Nullable peuvent être définis pour un projet à l'aide de l' Nullable
élément dans votre csproj
fichier. Cet élément configure la manière dont le compilateur interprète la nullité des types et quels avertissements sont générés. Les paramètres valides sont:
enable
: Le contexte d'annotation Nullable est activé. Le contexte d'avertissement Nullable est activé. Les variables d'un type référence, chaîne par exemple, ne sont pas Nullables. Tous les avertissements de nullité sont activés.disable
: Le contexte d'annotation Nullable est désactivé. Le contexte d'avertissement Nullable est désactivé. Les variables d'un type référence sont inconscientes, tout comme les versions antérieures de C #. Tous les avertissements de nullité sont désactivés.safeonly
: Le contexte d'annotation Nullable est activé. Le contexte d'avertissement Nullable est safeonly. Les variables d'un type référence ne peuvent pas être nulles. Tous les avertissements de nullité de sécurité sont activés.warnings
: Le contexte d'annotation Nullable est désactivé. Le contexte d'avertissement Nullable est activé. Les variables d'un type de référence sont inconscientes. Tous les avertissements de nullité sont activés.safeonlywarnings
: Le contexte d'annotation Nullable est désactivé. Le contexte d'avertissement Nullable est safeonly. Les variables d'un type de référence sont inconscientes. Tous les avertissements de nullité de sécurité sont activés.Un type de référence Nullable est noté en utilisant la même syntaxe que les types valeur Nullable: a ?
est ajouté au type de la variable.
C#
prend en charge les «blocs d'itérateur» (appelés «générateurs» dans certains autres langages populaires). Les exceptions de déréférencement nul peuvent être particulièrement délicates à déboguer dans les blocs d'itérateur en raison de l'exécution différée:
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) { ... }
Si les whatever
résultats, null
alors MakeFrob
jettera. Maintenant, vous pourriez penser que la bonne chose à faire est la suivante:
// 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();
}
Pourquoi est-ce faux? Le bloc itérateur ne fait pas courir jusqu'à ce que le foreach
! L'appel à GetFrobs
renvoie simplement un objet qui, une fois itéré , exécutera le bloc itérateur.
En écrivant un contrôle nul comme celui-ci, vous évitez le déréférencement nul, mais vous déplacez l'exception d'argument nul au point de l' itération , pas au point de l' appel , et c'est très déroutant à déboguer .
Le correctif correct est:
// 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();
}
Autrement dit, créez une méthode d'assistance privée qui a la logique de bloc d'itérateur et une méthode de surface publique qui vérifie la valeur NULL et retourne l'itérateur. Désormais, lors de l' GetFrobs
appel, la vérification de la valeur nulle se produit immédiatement, puis GetFrobsForReal
s'exécute lorsque la séquence est itérée.
Si vous examinez la source de référence pour LINQ
les objets, vous verrez que cette technique est utilisée partout. C'est un peu plus compliqué à écrire, mais cela facilite beaucoup le débogage des erreurs de nullité. Optimisez votre code pour la commodité de l'appelant, pas pour la commodité de l'auteur .
C#
a un mode "unsafe" qui est, comme son nom l'indique, extrêmement dangereux parce que les mécanismes de sécurité normaux qui assurent la sécurité de la mémoire et la sécurité du type ne sont pas appliqués. Vous ne devriez pas écrire de code dangereux à moins d'avoir une compréhension approfondie et approfondie du fonctionnement de la mémoire .
En mode non sécurisé, vous devez être conscient de deux faits importants:
Pour comprendre pourquoi, il est utile de comprendre comment .NET produit des exceptions de déréférencement nul en premier lieu. (Ces détails s'appliquent à .NET fonctionnant sous Windows; d'autres systèmes d'exploitation utilisent des mécanismes similaires.)
La mémoire est virtualisée dans Windows
; chaque processus obtient un espace mémoire virtuel de plusieurs "pages" de mémoire qui sont suivies par le système d'exploitation. Chaque page de mémoire a des indicateurs définis sur elle qui déterminent comment elle peut être utilisée: lire, écrire, exécuter, etc. La page la plus basse est marquée comme "produire une erreur si jamais utilisée de quelque manière que ce soit".
Un pointeur nul et une référence nulle dans C#
sont représentés en interne comme le nombre zéro, et par conséquent, toute tentative de le déréférencer dans sa mémoire de stockage correspondante provoque une erreur du système d'exploitation. Le runtime .NET détecte ensuite cette erreur et la transforme en exception de déréférencement nul.
C'est pourquoi le déréférencement à la fois d'un pointeur nul et d'une référence null produit la même exception.
Et le deuxième point? Le déréférencement de tout pointeur non valide qui se trouve dans la page la plus basse de la mémoire virtuelle provoque la même erreur du système d'exploitation, et donc la même exception.
Pourquoi cela a-t-il un sens? Eh bien, supposons que nous ayons une structure contenant deux entiers et un pointeur non managé égal à null. Si nous essayons de déréférencer le deuxième int dans la structure, le CLR
ne tentera pas d'accéder au stockage à l'emplacement zéro; il accédera au stockage à l'emplacement quatre. Mais logiquement, il s'agit d'un déréférencement nul car nous arrivons à cette adresse via le null.
Si vous travaillez avec du code non sécurisé et que vous obtenez une exception de déréférencement nul, sachez simplement que le pointeur incriminé n'a pas besoin d'être nul. Il peut s'agir de n'importe quel emplacement de la page la plus basse, et cette exception sera générée.
Cet article est collecté sur Internet, veuillez indiquer la source lors de la réimpression.
En cas d'infraction, veuillez [email protected] Supprimer.
laisse moi dire quelques mots