J'ai un certain code et quand il s'exécute, il jette une NullReferenceException
, disant :
La référence à un objet n'est pas définie comme 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
en VB.NET). Cela signifie que soit vous lui attribuez la valeur null
, soit vous ne lui attribuez jamais rien du tout.
Comme toute autre chose, null
est transmis. S'il est null
dans la méthode "A" ;, il se peut que la méthode "B" ; ait transmis un null
à la méthode "A" ;.
null
peut avoir différentes significations :
NullReferenceException
.null
intentionnellement pour indiquer qu'il n'y a pas de valeur significative disponible. Notez que C# a le concept de types de données nullables pour les variables (comme les tables de base de données peuvent avoir des champs nullables) - vous pouvez leur assigner null
pour indiquer qu'il n'y a pas de valeur stockée dedans, par exemple int ? a = null;
où le point d'interrogation indique qu'il est autorisé de stocker null dans la variable a
. Vous pouvez le vérifier soit avec if (a.HasValue) {...}
, soit avec if (a==null) {...}
. Les variables nullables, comme a
dans cet exemple, permettent d'accéder à la valeur via a.Value
explicitement, ou juste normalement via a
. a.Value
lève une InvalidOperationException
au lieu d'une NullReferenceException
si a
est null
- vous devriez faire la vérification au préalable, c'est-à-dire que si vous avez une autre variable on-nullable int b;
alors vous devriez faire des assignations comme if (a.HasValue) { b = a.Value ; }
ou plus court if (a != null) { b = a ; }
.
La suite de cet article entre dans le détail et montre les erreurs que de nombreux programmeurs font souvent et qui peuvent conduire à une NullReferenceException
.L'exécution d'une NullReferenceException
signifie 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é parfois initialisée, mais ne l'est plus).
Cela signifie que la référence est null
, et vous ne pouvez pas accéder aux membres (comme les méthodes) à travers une référence null
. Le cas le plus simple :
string foo = null;
foo.ToUpper();
Ceci lancera une NullReferenceException
à la deuxième ligne car vous ne pouvez pas appeler la méthode d'instance ToUpper()
sur une référence string
pointant vers null
.
Comment trouver la source d'une NullReferenceException
? En dehors de l'exception elle-même, qui sera levé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] (https://msdn.microsoft.com/en-us/library/esta7c62.aspx), soit en passant la souris sur leurs noms, soit en ouvrant une fenêtre de (Quick)Watch, soit en utilisant les différents panneaux de débogage comme Locals et Autos.
Si vous voulez savoir où la référence est ou n'est pas fixée, cliquez avec le bouton droit de la souris sur son nom et sélectionnez "Find All References" ;. 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 la référence doit être non nulle, inspecter la variable et vérifier qu'elle pointe vers une instance au moment où vous le souhaitez.
En suivant le déroulement du programme de cette manière, vous pouvez trouver l'endroit où l'instance ne devrait pas être nulle, et pourquoi elle n'est pas correctement définie.
Quelques scénarios courants où l'exception peut être levée :
ref1.ref2.ref3.member
Si ref1 ou ref2 ou ref3 est nul, vous obtiendrez une NullReferenceException
. Si vous voulez résoudre le problème, déterminez lequel est nul en réécrivant l'expression en 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
, le HttpContext.Current
pourrait être null, ou la propriété User
pourrait être null, ou la propriété Identity
pourrait être null.
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 voulez éviter la référence nulle de l'enfant (Personne), vous pouvez l'initialiser dans le constructeur de l'objet parent (Livre).
Il en va de même pour les initialisateurs d'objets imbriqués :
Book b1 = new Book { Author = { Age = 45 } };
Cela se traduit par
Book b1 = new Book();
b1.Author.Age = 45;
Bien que le mot-clé new
soit utilisé, il crée seulement une nouvelle instance de Book
, mais pas une nouvelle instance de Person
, donc la propriété Author
est toujours null
.
public class Person {
public ICollection<Book> Books { get; set; }
}
public class Book {
public string Title { get; set; }
}
Les initialisateurs de collections imbriquées se comportent 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
crée seulement une instance de Person
, mais la collection Books
est toujours null
. La syntaxe de l'initialisateur de collection ne crée pas de collection
pour p1.Books
, elle se traduit uniquement par les instructions p1.Books.Add(...)
.
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 vous êtes peut-être rendu compte 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);
}
}
Ce problème peut être résolu en suivant la convention qui consiste à préfixer les champs par 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 de la référence à une propriété de @Model
dans une vue ASP.NET MVC, vous devez comprendre que le Model
est défini dans votre méthode d'action, lorsque vous retournez
une vue. Lorsque vous retournez 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 -->
Les contrôles WPF sont créés lors de l'appel à InitializeComponent
dans l'ordre où ils apparaissent dans l'arbre visuel. Une NullReferenceException
sera levée dans le cas de contrôles créés trop tôt, avec des gestionnaires d'événements, etc. qui se déclenchent pendant InitializeComponent
et qui font référence à des contrôles créés plus tard.
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>
Ici, comboBox1
est créé avant label1
. Si comboBox1_SelectionChanged
tente 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!!
}
En changeant l'ordre des déclarations dans le XAML (c'est-à-dire en listant label1
avant comboBox1
, en ignorant les questions de philosophie de conception, on pourrait au moins résoudre l'exception NullReferenceException
ici.
as
var myThing = someObject as Thing;
Cette méthode ne lève pas d'exception InvalidCastException mais renvoie un null
lorsque le cast échoue (et lorsque someObject est lui-même null). Il faut donc en tenir compte.
Les versions simples First()
et Single()
lèvent des exceptions quand il n'y a rien. Les versions "OrDefault" ; retournent null dans ce cas. Soyez donc attentifs à cela.
foreach
lève une exception lorsque vous essayez d'itérer une collection nulle. Généralement causé par le résultat inattendu null
des méthodes qui retournent des collections.
List<int> list = null;
foreach(var v in list) { } // exception
Exemple plus réaliste : sélectionner des noeuds dans un document XML. L'erreur sera levée si les noeuds 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 ignorer les valeurs nulles.Si vous vous attendez à ce que la référence soit parfois nulle, vous pouvez vérifier qu'elle est null
avant d'accéder aux membres de l'instance :
void PrintName(Person p) {
if (p != null) {
Console.WriteLine(p.Name);
}
}
null
et fournir une valeur par défaut.L'appel de méthodes dont vous attendez qu'elles retournent une instance peut retourner 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
dans les appels de méthode et lancer une exception personnalisée.Vous pouvez également lancer une exception personnalisée, pour la récupérer dans le code appelant :
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 attraper le problème avant que l'exception ne se produise.Lorsque vous savez, au cours du développement, qu'une méthode peut, mais ne doit jamais, retourner null
, vous pouvez utiliser Debug.Assert()
pour déclencher une exception le plus tôt possible lorsque 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 [n'aboutira pas dans votre build de version] (https://stackoverflow.com/questions/3021538/debug-assert-appears-in-release-mode), elle fera en sorte de relancer la NullReferenceException
lorsque book == null
au moment de l'exécution en mode release.
GetValueOrDefault()
pour les types de valeur nullables afin de fournir une valeur par défaut lorsqu'ils 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].C'est un 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) :On l'appelle aussi parfois l'opérateur de navigation sûre ou Elvis (d'après sa forme). Si l'expression du côté gauche de l'opérateur est nulle, alors le côté droit ne sera pas évalué, et null sera retourné à la place. Cela signifie des cas comme celui-ci :
var title = person.Title.ToUpper();
Si la personne n'a pas de titre, une exception sera levée parce que l'on essaie d'appeler ToUpper
sur une propriété dont la valeur est nulle.
En C# 5 et inférieur, ceci peut être protégé avec :
var title = person.Title == null ? null : person.Title.ToUpper();
Maintenant, la variable title sera nulle au lieu de lever une exception. Le C# 6 introduit une syntaxe plus courte pour cela :
var title = person.Title?.ToUpper();
La variable title sera donc null
, et l'appel à ToUpper
ne sera pas effectué si person.Title
est null
.
Bien sûr, vous devez encore vérifier que title
est nul ou utiliser l'opérateur de condition null avec l'opérateur de fusion null (??
) 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");
Ceci aura l'effet suivant : Si monIntArray est nul, l'expression renvoie null et vous pouvez le vérifier en toute sécurité. Si elle contient un tableau, elle fera la même chose que :
elem = myIntArray[i];
et renvoie le ith élément.
Introduits en C# 8, les contextes null et les types de référence nullables effectuent une analyse statique des variables et fournissent un avertissement au compilateur si une valeur peut être potentiellement nulle ou a été définie comme nulle. Les types de référence nullable permettent aux types d'être explicitement autorisés à être nuls. Le contexte d'annotation nullable et le contexte d'avertissement nullable peuvent être définis pour un projet en utilisant l'élément Nullable dans votre fichier csproj. Cet élément configure la manière dont le compilateur interprète la nullité des types et les avertissements qui sont générés. Les paramètres valides sont les suivants
?
est ajouté au type de la variable.C# supporte les "blocs d'itérateurs" (appelés "générateurs" dans d'autres langages populaires). Les exceptions de déréférencement nul peuvent être particulièrement délicates à déboguer dans les blocs d'itérateurs 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 whatever
résulte en null
alors MakeFrob
sera lancé. 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 mal ? Parce que le bloc itérateur n'est pas réellement exécuté avant le foreach
! L'appel à GetFrobs
renvoie simplement un objet qui, lorsqu'il est itéré, exécute le bloc itérateur.
En écrivant une vérification de nullité comme ceci, vous empêchez la déréférence nulle, mais vous déplacez l'exception d'argument nul au point de l'itération, et non au point de l'appel, et c'est très compliqué à 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();
}
C'est à dire, faire une méthode d'aide privée qui a la logique de bloc de l'itérateur, et une méthode de surface publique qui fait la vérification de la nullité et retourne l'itérateur. Maintenant, lorsque GetFrobs
est appelé, la vérification du null se produit immédiatement, et ensuite GetFrobsForReal
s'exécute lorsque la séquence est itérée.
Si vous examinez la source de référence de LINQ to Objects, vous verrez que cette technique est utilisée partout. Elle est légèrement plus compliquée à écrire, mais elle facilite le débogage des erreurs de nullité. Optimisez votre code pour la commodité de l'appelant, pas pour celle de l'auteur.
C# dispose d'un mode "unsafe" qui, comme son nom l'indique, est extrêmement dangereux car les mécanismes de sécurité normaux qui assurent la sécurité de la mémoire et des types ne sont pas appliqués. Vous ne devriez pas écrire de code non sécurisé si vous n'avez pas une connaissance approfondie du fonctionnement de la mémoire. En mode non sécurisé, vous devez être conscient de deux faits importants :
Cela signifie que la variable en question ne pointe vers rien. Je pourrais générer ceci comme suit :
SqlConnection connection = null;
connection.Open();
L'erreur se produira parce que, bien que j'aie déclaré la variable "connexion", elle ne pointe vers rien. Lorsque j'essaie d'appeler le membre "Ouvrir", il n'y a pas de référence à résoudre, et l'erreur se produit.
Pour éviter cette erreur :
object == null
.L'outil Resharper de JetBrains identifiera chaque endroit de votre code où il y a une possibilité d'erreur de référence nulle, vous permettant ainsi de mettre en place une vérification de nullité. Cette erreur est la source numéro un de bogues, IMHO.
Cela signifie que votre code a utilisé une variable de référence d'objet qui était définie comme nulle (c'est-à-dire qu'elle ne faisait pas référence à une instance d'objet réelle).
Pour éviter cette erreur, les objets qui pourraient être nuls doivent être testés avant d'être utilisés.
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
}