Descripción de la nulabilidad
Si es un desarrollador de .NET, lo más probable es que haya encontrado System.NullReferenceException. Esto se produce en tiempo de ejecución cuando se desreferencia null
; es decir, cuando se evalúa una variable en tiempo de ejecución, pero la variable hace referencia a null
. Esta excepción es, con diferencia, la excepción más común dentro del ecosistema de .NET. El creador de null
(Tony Hoare) se refiere a null
como el "error de miles de millones de dólares".
En el ejemplo siguiente, la variable FooBar
se asigna a null
y se desreferencia inmediatamente, lo que revela el problema:
// Declare variable and assign it as null.
FooBar fooBar = null;
// Dereference variable by calling ToString.
// This will throw a NullReferenceException.
_ = fooBar.ToString();
// The FooBar type definition.
record FooBar(int Id, string Name);
El problema es mucho más difícil de detectar como desarrollador cuando el tamaño y la complejidad de las aplicaciones aumentan. Detectar posibles errores como este es un trabajo de herramientas, y el compilador de C# está aquí para ayudar.
Definición de la seguridad de valores NULL
El término seguridad de valores NULL define un conjunto de características específicas de los tipos que aceptan valores NULL que ayudan a reducir el número de posibles ocurrencias de NullReferenceException
.
Teniendo en cuenta el ejemplo de FooBar
anterior, NullReferenceException
se podría evitar comprobando si la variable fooBar
era null
antes de desreferenciarla:
// Declare variable and assign it as null.
FooBar fooBar = null;
// Check for null
if (fooBar is not null)
{
_ = fooBar.ToString();
}
// The FooBar type definition for example.
record FooBar(int Id, string Name);
Para ayudar a identificar escenarios como este, el compilador puede deducir la intención del código y aplicar el comportamiento deseado. Sin embargo, esto sucede solo cuando se habilita un contexto que acepta valores NULL. Antes de analizar el contexto que acepta valores NULL, vamos a describir los posibles tipos que aceptan valores NULL.
Tipos que aceptan valores NULL
Antes de C# 2.0, solo los tipos de referencia aceptaban valores NULL. Los tipos de valor, como int
o DateTime
, no se null
. Si estos tipos se inicializan sin un valor, se revertirán a su valor default
. En el caso de int
, es 0
. Para DateTime
, es DateTime.MinValue
.
Los tipos de referencia de los que se crea una instancia sin valores iniciales funcionan de forma diferente. El valor default
de todos los tipos de referencia es null
.
Considere el siguiente fragmento de C#:
string first; // first is null
string second = string.Empty // second is not null, instead it's an empty string ""
int third; // third is 0 because int is a value type
DateTime date; // date is DateTime.MinValue
En el ejemplo anterior:
first
esnull
porque se declaró el tipo de referenciastring
, pero no se realizó ninguna asignación.- A
second
se le asignastring.Empty
cuando se declara. El objeto nunca tuvo una asignaciónnull
. third
es0
a pesar de no estar asignado. Esstruct
(tipo de valor) y tiene un valordefault
de0
.date
no está inicializado, pero su valordefault
es System.DateTime.MinValue.
A partir de C# 2.0, podría definir tipos de valor que aceptan valores NULL mediante Nullable<T>
, o T?
para abreviar. Esto permite que los tipos de valor acepten valores NULL. Considere el siguiente fragmento de C#:
int? first; // first is implicitly null (uninitialized)
int? second = null; // second is explicitly null
int? third = default; // third is null as the default value for Nullable<Int32> is null
int? fourth = new(); // fourth is 0, since new calls the nullable constructor
En el ejemplo anterior:
first
esnull
porque el tipo de valor que acepta valores NULL no está inicializado.- A
second
se le asignanull
cuando se declara. third
esnull
como el valor dedefault
paraNullable<int>
esnull
.fourth
es0
como la expresiónnew()
llama al constructorNullable<int>
yint
es0
de forma predeterminada.
C# 8.0 introdujo tipos de referencia que aceptan valores NULL, donde puede expresar su intención de que un tipo de referencia pueda ser null
o de que siempre sea no-null
. Es posible que esté pensando: "Creía que todos los tipos de referencia aceptan valores NULL". No está equivocado y eso es lo que sucede. Esta característica le permite expresar su intención, que el compilador intenta aplicar. La misma sintaxis de T?
expresa que un tipo de referencia está pensado para aceptar valores NULL.
Considere el siguiente fragmento de C#:
#nullable enable
string first = string.Empty;
string second;
string? third;
Dado el ejemplo anterior, el compilador deduce su intención de la siguiente manera:
first
nunca esnull
, porque está asignado definitivamente.second
nunca debe sernull
, aunque inicialmente seanull
. La evaluación desecond
antes de asignar un valor genera una advertencia del compilador, ya que no se inicializa.third
puede sernull
. Por ejemplo, podría apuntar aSystem.String
, pero podría apuntar anull
. Todas estas variaciones son aceptables. El compilador le ayuda a advertirle si desreferenciathird
sin comprobar primero que no es NULL.
Importante
Para usar la característica de tipos de referencia que aceptan valores NULL como se mostró anteriormente, debe estar dentro de un contexto que acepta valores NULL. Esto se detalla en la sección siguiente.
Contexto que admite un valor NULL
Los contextos que aceptan valores NULL permiten un control preciso sobre cómo interpreta el compilador las variables de tipos de referencia. Hay cuatro contextos posibles que aceptan valores NULL:
disable
: el compilador se comporta de forma similar a C# 7.3 y versiones anteriores.enable
: el compilador habilita todo el análisis de referencias nulas y todas las características del lenguaje.warnings
: el compilador realiza todos los análisis nulos y emite advertencias cuando el código puede desreferenciarnull
.annotations
: el compilador no realiza análisis nulos ni emite advertencias cuando el código puede desreferenciarnull
, pero todavía puede anotar el código mediante tipos de referencia que aceptan valores NULL?
y operadores que aceptan valores NULL (!
).
Este módulo tiene como ámbito los contextos disable
y enable
que aceptan valores NULL. Para obtener más información, consulta Tipos de referencia que aceptan valores NULL: contextos que aceptan valores NULL.
Habilitación de tipos de referencia que aceptan valores NULL
En el archivo de proyecto de C# (.csproj), agregue un nodo secundario <Nullable>
al elemento <Project>
, o bien anexe a un <PropertyGroup>
existente. Esto aplicará el contexto enable
que acepta valores NULL a todo el proyecto.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Omitted for brevity -->
</Project>
Como alternativa, puede establecer el ámbito del contexto que acepta valores NULL en un archivo de C# mediante una directiva del compilador.
#nullable enable
La directiva del compilador de C# anterior es funcionalmente equivalente a la configuración del proyecto, pero tiene como ámbito el archivo en el que reside. Para obtener más información, vea Tipos de referencia que aceptan valores NULL: contextos que aceptan valores NULL (docs).
Importante
El contexto que acepta valores NULL está habilitado en el archivo .csproj de forma predeterminada en todas las plantillas de proyecto de C# a partir de .NET 6.0 y posterior.
Cuando se habilita el contexto que acepta valores NULL, se obtienen nuevas advertencias. Tenga en cuenta el ejemplo de FooBar
anterior; tiene dos advertencias cuando se analiza en un contexto que admite un valor NULL:
La línea
FooBar fooBar = null;
tiene una advertencia en la asignaciónnull
: Advertencia de C# CS8600: Convertir un literal NULL o un valor NULL posible en un tipo que no acepta valores NULL.La línea
_ = fooBar.ToString();
también tiene una advertencia. Esta vez, al compilador le preocupa quefooBar
pueda ser NULL: Advertencia de C# CS8602: Desreferencia de una referencia posiblemente nula.
Importante
No hay ninguna seguridad de valores NULL garantizada, aunque reaccione y elimine todas las advertencias. Hay algunos escenarios limitados que pasarán el análisis del compilador pero darán como resultado un entorno de ejecución NullReferenceException
.
Resumen
En esta unidad, ha aprendido a habilitar un contexto que acepta valores NULL en C# para ayudar a protegerse contra NullReferenceException
. En la siguiente unidad, aprenderá más sobre cómo expresar explícitamente la intención en un contexto que acepta valores NULL.