Descripción de la nulabilidad

Completado

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 es null porque se declaró el tipo de referencia string, pero no se realizó ninguna asignación.
  • A second se le asigna string.Empty cuando se declara. El objeto nunca tuvo una asignación null.
  • third es 0 a pesar de no estar asignado. Es struct (tipo de valor) y tiene un valor default de 0.
  • date no está inicializado, pero su valor default 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 es null porque el tipo de valor que acepta valores NULL no está inicializado.
  • A second se le asigna null cuando se declara.
  • third es null como el valor de default para Nullable<int> es null.
  • fourth es 0 como la expresión new() llama al constructor Nullable<int> y int es 0 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 es null, porque está asignado definitivamente.
  • second nunca debe ser null, aunque inicialmente sea null. La evaluación de second antes de asignar un valor genera una advertencia del compilador, ya que no se inicializa.
  • third puede ser null. Por ejemplo, podría apuntar a System.String, pero podría apuntar a null. Todas estas variaciones son aceptables. El compilador le ayuda a advertirle si desreferencia third 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 desreferenciar null.
  • annotations: el compilador no realiza análisis nulos ni emite advertencias cuando el código puede desreferenciar null, 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:

  1. La línea FooBar fooBar = null; tiene una advertencia en la asignación null: Advertencia de C# CS8600: Convertir un literal NULL o un valor NULL posible en un tipo que no acepta valores NULL.

    Captura de pantalla de una advertencia de C# CS8600: Convertir un literal NULL o un valor NULL posible en un tipo que no acepta valores NULL.

  2. La línea _ = fooBar.ToString(); también tiene una advertencia. Esta vez, al compilador le preocupa que fooBar pueda ser NULL: Advertencia de C# CS8602: Desreferencia de una referencia posiblemente nula.

    Captura de pantalla de una 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.