Tengo un código y cuando se ejecuta, lanza una NullReferenceException
, diciendo:
`Referencia de objeto no establecida a una instancia de un objeto.
¿Qué significa esto y qué puedo hacer para solucionar este error?
Estás intentando usar algo que es null
(o Nada
en VB.NET). Esto significa que, o bien lo pones a null
, o bien nunca lo pones a nada.
Como cualquier otra cosa, null
se pasa de mano en mano. Si es null
en el método "A", puede ser que el método "B" haya pasado un null
al método "A".
El término "nulo" puede tener diferentes significados:
NullReferenceException
.null
para indicar que no hay ningún valor almacenado en ella, por ejemplo int? a = null;
donde el signo de interrogación indica que está permitido almacenar null en la variable a
. Puedes comprobarlo con si (a.HasValue) {...}
o con si (a==null) {...}
. Las variables anulables, como a
este ejemplo, permiten acceder al valor a través de a.Value
explícitamente, o simplemente como normal a través de a
. a.Value
lanza una InvalidOperationException
en lugar de una NullReferenceException
si a
es null
- debes hacer la comprobación de antemano, es decir, si tienes otra variable on-nullable int b;
entonces debes hacer asignaciones como if (a.HasValue) { b = a.Value; }
o más corto if (a != null) { b = a; }
.
El resto de este artículo entra en más detalle y muestra errores que muchos programadores suelen cometer y que pueden llevar a una NullReferenceException
.El tiempo de ejecución que lanza una NullReferenceException
siempre significa lo mismo: estás intentando usar una referencia, y la referencia no está inicializada (o estuvo una vez inicializada, pero ya no lo está).
Esto significa que la referencia es "nula", y no se puede acceder a los miembros (como los métodos) a través de una referencia "nula". El caso más simple:
string foo = null;
foo.ToUpper();
Esto lanzará una NullReferenceException
en la segunda línea porque no puedes llamar al método de instancia ToUpper()
en una referencia string
que apunta a null
.
¿Cómo puedes encontrar el origen de una NullReferenceException
? Aparte de mirar la excepción en sí, que será lanzada exactamente en el lugar donde se produce, se aplican las reglas generales de depuración en Visual Studio: colocar puntos de interrupción estratégicos e inspeccionar sus variables, ya sea pasando el ratón por encima de sus nombres, abriendo una ventana de (Quick)Watch o utilizando los diversos paneles de depuración como Locals y Autos.
Si quieres averiguar dónde está o no está la referencia, haz clic con el botón derecho del ratón sobre su nombre y selecciona "Buscar todas las referencias". A continuación, puede colocar un punto de interrupción en cada ubicación encontrada y ejecutar su programa con el depurador conectado. Cada vez que el depurador se rompe en un punto de interrupción, tienes que determinar si esperas que la referencia no sea nula, inspeccionar la variable y verificar que apunta a una instancia cuando lo esperas.
Siguiendo el flujo del programa de esta manera, puedes encontrar el lugar donde la instancia no debería ser nula, y por qué no está correctamente establecida.
Algunos escenarios comunes donde la excepción puede ser lanzada:
ref1.ref2.ref3.member
Si ref1 o ref2 o ref3 es nulo, entonces obtendrás una NullReferenceException
. Si quieres resolver el problema, entonces averigua cuál es nulo reescribiendo la expresión a su equivalente más simple:
var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member
En concreto, en HttpContext.Current.User.Identity.Name
, el HttpContext.Current
podría ser null, o la propiedad User
podría ser null, o la propiedad Identity
podría ser 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 quiere evitar la referencia nula del objeto hijo (Persona), puede inicializarla en el constructor del objeto padre (Libro).
Lo mismo se aplica a los inicializadores de objetos anidados:
Book b1 = new Book { Author = { Age = 45 } };
Esto se traduce en
Book b1 = new Book();
b1.Author.Age = 45;
Aunque se utiliza la palabra clave new
, sólo crea una nueva instancia de Book
, pero no una nueva instancia de Persona
, por lo que la propiedad Author
sigue siendo null
.
public class Person {
public ICollection<Book> Books { get; set; }
}
public class Book {
public string Title { get; set; }
}
Los inicializadores de colecciones anidadas se comportan igual:
Person p1 = new Person {
Books = {
new Book { Title = "Title1" },
new Book { Title = "Title2" },
}
};
Esto se traduce en
Person p1 = new Person();
p1.Books.Add(new Book { Title = "Title1" });
p1.Books.Add(new Book { Title = "Title2" });
El nuevo Persona
sólo crea una instancia de Persona
, pero la colección Libros
sigue siendo nula
. La sintaxis del inicializador de la colección no crea una colección
para p1.Books
, sólo se traduce en las sentencias 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 nombraste los campos de forma diferente a los locales, podrías haberte dado cuenta de que nunca inicializaste el campo.
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);
}
}
Esto puede resolverse siguiendo la convención de anteponer un guión bajo a los campos:
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 la excepción se produce al hacer referencia a una propiedad de @Model
en una vista de ASP.NET MVC, debe entender que el Model
se establece en su método de acción, cuando retorna
una vista. Cuando devuelves un modelo vacío (o una propiedad del modelo) desde tu controlador, la excepción se produce cuando las vistas acceden a él:
// 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 -->
Los controles WPF se crean durante la llamada a InitializeComponent
en el orden en que aparecen en el árbol visual. Se producirá una NullReferenceException
en el caso de los controles creados anticipadamente con manejadores de eventos, etc. que se disparan durante InitializeComponent
y que hacen referencia a controles creados tardíamente.
Por ejemplo:
<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>
En este caso, comboBox1
se crea antes que label1
. Si comboBox1_SelectionChanged
intenta hacer referencia a label1
, todavía no se habrá creado.
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
label1.Content = comboBox1.SelectedIndex.ToString(); // NullReference here!!
}
Cambiando el orden de las declaraciones en la XAML (es decir, listando label1
antes de comboBox1
, ignorando cuestiones de filosofía de diseño, al menos se resolvería la NullReferenceException
aquí.
as
var myThing = someObject as Thing;
Esto no lanza una InvalidCastException pero devuelve un null
cuando el casting falla (y cuando someObject es en sí mismo null). Así que ten en cuenta esto.
Las versiones simples First()
y Single()
lanzan excepciones cuando no hay nada. Las versiones "OrDefault" devuelven null en ese caso. Así que tenlo en cuenta.
foreach
lanza cuando se intenta iterar una colección nula. Normalmente es causado por el resultado inesperado null
de los métodos que devuelven colecciones.
List<int> list = null;
foreach(var v in list) { } // exception
Ejemplo más realista - seleccionar nodos de un documento XML. Lanzará si no se encuentran los nodos pero la depuración inicial muestra que todas las propiedades son válidas:
foreach (var node in myData.MyXml.DocumentNode.SelectNodes("//Data"))
null
e ignorar los valores nulos.Si esperas que la referencia a veces sea nula, puedes comprobar si es null
antes de acceder a los miembros de la instancia:
void PrintName(Person p) {
if (p != null) {
Console.WriteLine(p.Name);
}
}
null
y proporcionar un valor por defecto.Las llamadas a métodos que se espera que devuelvan una instancia pueden devolver null
, por ejemplo cuando no se encuentra el objeto buscado. Puede elegir devolver un valor por defecto cuando este sea el caso:
string GetCategory(Book b) {
if (b == null)
return "Unknown";
return b.Category;
}
null
en las llamadas a métodos y lanzar una excepción personalizada.También puede lanzar una excepción personalizada, sólo para atraparla en el código de llamada:
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 un valor nunca debe ser null
, para atrapar el problema antes de que ocurra la excepción.Cuando se sabe durante el desarrollo que un método puede, pero nunca debe devolver null
, se puede utilizar Debug.Assert()
para romper tan pronto como sea posible cuando se produce:
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.
}
Aunque esta comprobación no acabará en tu build de release, haciendo que vuelva a lanzar la NullReferenceException
cuando book == null
en tiempo de ejecución en modo release.
GetValueOrDefault()
para los tipos de valores anulables para proporcionar un valor por defecto cuando son 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#] o If()
[VB].La abreviatura para proporcionar un valor por defecto cuando se encuentra un null
:
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;
}
?.
o ?[x]
para arrays (disponible en C# 6 y VB.NET 14):A veces también se le llama operador de navegación segura o Elvis (por su forma). Si la expresión del lado izquierdo del operador es null, entonces el lado derecho no se evaluará, y en su lugar se devolverá null. Es decir, casos como éste:
var title = person.Title.ToUpper();
Si la persona no tiene un título, esto lanzará una excepción porque está tratando de llamar a ToUpper
en una propiedad con un valor nulo.
En C# 5 e inferior, esto puede ser protegido con:
var title = person.Title == null ? null : person.Title.ToUpper();
Ahora la variable title será nula en lugar de lanzar una excepción. C# 6 introduce una sintaxis más corta para esto:
var title = person.Title?.ToUpper();
Esto hará que la variable title sea null
, y la llamada a ToUpper
no se hará si persona.Title
es null
.
Por supuesto, todavía tiene que comprobar si título
es nulo o utilizar el operador de condición nula junto con el operador de coalescencia de nulos (??
) para proporcionar un valor por defecto:
// 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;
Asimismo, para las matrices se puede utilizar ?[i]
de la siguiente manera:
int[] myIntArray=null;
var i=5;
int? elem = myIntArray?[i];
if (!elem.HasValue) Console.WriteLine("No value");
Esto hará lo siguiente: Si miIntArray es nulo, la expresión devuelve null y se puede comprobar con seguridad. Si contiene un array, hará lo mismo que:
elem = myIntArray[i];
y devuelve el iésimo elemento.
Introducido en C# 8, los contextos nulos y los tipos de referencia anulables realizan un análisis estático de las variables y proporcionan una advertencia del compilador si un valor puede ser potencialmente nulo o ha sido establecido como nulo. Los tipos de referencia anulables permiten que los tipos sean explícitamente nulos. El contexto de anotación anulable y el contexto de advertencia anulable pueden establecerse para un proyecto utilizando el elemento Nullable en su archivo csproj. Este elemento configura cómo el compilador interpreta la anulabilidad de los tipos y qué advertencias se generan. Los ajustes válidos son
?
al tipo de la variable.C# soporta "bloques iteradores" (llamados "generadores" en otros lenguajes populares). Las excepciones de dereferencia nula pueden ser particularmente difíciles de depurar en los bloques iteradores debido a la ejecución diferida:
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 lo que sea
resulta en null
entonces MakeFrob
lanzará. Ahora, usted podría pensar que lo correcto es esto:
// 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();
}
¿Por qué es incorrecto? Porque el bloque iterador no se ejecuta hasta el foreach
. La llamada a GetFrobs
simplemente devuelve un objeto que cuando sea iterado ejecutará el bloque iterador.
Escribiendo una comprobación de nulos como ésta se evita la desreferencia de nulos, pero se traslada la excepción de argumento nulo al punto de la iteración, no al punto de la llamada, y eso es muy confuso de depurar.
El arreglo correcto es:
// 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();
}
Es decir, hacer un método helper privado que tenga la lógica de bloqueo del iterador, y un método público de superficie que haga la comprobación de nulos y devuelva el iterador. Ahora, cuando se llama a GetFrobs
, la comprobación de nulos ocurre inmediatamente, y luego GetFrobsForReal
se ejecuta cuando se itera la secuencia.
Si examina el código de referencia de LINQ to Objects verá que esta técnica se utiliza en todo momento. Es un poco más complicado de escribir, pero facilita la depuración de errores de nulidad. Optimiza tu código para la conveniencia del que llama, no para la conveniencia del autor.
C# tiene un modo "inseguro" que es, como su nombre indica, extremadamente peligroso porque los mecanismos normales de seguridad que proporcionan seguridad de memoria y seguridad de tipo no se aplican. No deberías escribir código inseguro a menos que tengas un conocimiento profundo de cómo funciona la memoria. En el modo inseguro, deberías ser consciente de dos hechos importantes:
Significa que la variable en cuestión no apunta a nada. Podría generar esto así:
SqlConnection connection = null;
connection.Open();
Eso arrojará el error porque aunque he declarado la variable "conexión
", no apunta a nada. Cuando intento llamar al miembro "Open
", no hay ninguna referencia que resolver, y dará el error.
Para evitar este error:
object == null
.La herramienta Resharper de JetBrains identificará cada lugar de tu código que tenga la posibilidad de un error de referencia nula, permitiéndote poner una comprobación de nulo. Este error es la fuente número uno de bugs, IMHO.
Significa que su código utilizó una variable de referencia a un objeto que se estableció como nula (es decir, no hizo referencia a una instancia real del objeto).
Para evitar el error, los objetos que podrían ser nulos deben ser comprobados antes de ser utilizados.
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
}