Resolución de advertencias sobre los valores NULL

Tip

¿No está familiarizado con los tipos de referencia que aceptan valores NULL? Lea primero Tipos de referencia que aceptan valores NULL para comprender las anotaciones y el análisis del estado de nulabilidad. En este artículo se supone que ve advertencias en un proyecto en el que está habilitada la característica.

¿Busca un código de error específico del compilador? El artículo Resolver advertencias que aceptan valores NULL cataloga todas las advertencias CS86xx con la técnica coincidente.

Cuando se habilitan tipos de referencia que aceptan valores NULL, el compilador emite advertencias en todas partes donde el comportamiento del código no coincide con sus anotaciones. La mayoría de las advertencias se dividen en un pequeño conjunto de patrones. Una vez que reconozca el patrón, la corrección suele ser una de las cinco técnicas:

  • Agregue una comprobación nula.
  • Agregue o quite una ? anotación o ! .
  • Agregue un atributo que describa el contrato NULL.
  • Inicialice las variables correctamente.
  • Compruebe la configuración del proyecto.

En este artículo se explica cada técnica con un ejemplo representativo. El objetivo no es silenciar las advertencias. Es hacer que la intención de control null del código sea explícita para que el compilador alcance las mismas conclusiones que haga.

Estado nulo: qué sigue el compilador

Antes de examinar las técnicas, ayuda a saber cómo el compilador realiza un seguimiento de posibles infracciones de estado NULL. A medida que lee el código, el compilador realiza un seguimiento del estado NULL de cada expresión: su análisis sobre si la expresión podría estar null en ese punto del código. El estado NULL es uno de los dos valores:

  • not-null — el compilador puede demostrar que la expresión no es null aquí. Puede usarlo de forma segura sin comprobación.
  • maybe-null : el compilador no puede descartar null. El uso de la expresión sin comprobarlo genera una advertencia.

El estado de null de una variable cambia a medida que el compilador analiza el código. Un método que podría devolver null genera un resultado tal vez nulo . Una comprobación de if (x is not null) reduce x a no nulo dentro del bloque if. Las advertencias que ves son del compilador, que te indica que ha determinado que una expresión está en un estado posiblemente nulo y que estás a punto de usarla como si fuera no nulo. Cada técnica del resto de este artículo es una manera diferente de proporcionar al compilador la información que necesita para asegurarse de que una expresión no sea null antes de usarla.

Adición de una comprobación nula

La advertencia más común es la posible desreferencia de null. El compilador ha seguido el estado NULL de una variable a maybe-null y ha visto la variable usada sin una comprobación:

public static int LengthOfMessageUnsafe(string? message)
{
    // Warning CS8602: dereference of a possibly null reference.
    return message.Length;
}

La corrección suele ser una cláusula de guarda. Una cláusula de guarda es una comprobación al principio de un método o bloque que devuelve o lanza una excepción si una entrada no es válida. Solo la ruta segura continúa. Una vez que se ejecuta la comprobación, el compilador actualiza el estado NULL de la variable a not-null en la ruta de acceso segura:

public static int DereferenceFixed(string? message)
{
    if (message is null)
    {
        return 0;
    }

    // No warning: the compiler knows message is not-null on this path.
    return message.Length;
}

Coincidencia de patrones (expresiones como is null o is { } que prueban la forma de un valor), ??e ??= incluyen comprobaciones nulas:

public static int NullOperatorsFix(string? message)
{
    // ?. evaluates to null if message is null; ?? supplies the fallback value.
    int length = message?.Length ?? 0;

    // Pattern matching narrows the type on the matching branch.
    if (message is { Length: > 0 })
    {
        length = message.Length;
    }

    return length;
}

El patrón { Length: > 0 } de propiedad coincide solo cuando message no es null y su Length propiedad es mayor que cero, por lo que el compilador trata message como no null dentro del if bloque. Una prueba is not null más sencilla produce la misma reducción del estado a null sin inspeccionar ninguna propiedad.

Para obtener un recorrido detallado de los operadores, consulte Operadores NULL.

Ajustar anotaciones

El compilador también le advierte cuando el código asigna una expresión maybe-null a una variable que no acepta valores NULL. Esa advertencia significa una de estas dos cosas:

  • La variable debe permitir valores NULL. En ese caso, agregue un ? al tipo .
  • La expresión nunca genera un valor NULL. Anota la API que la generó.
public static void AssignmentWarning()
{
    // Warning CS8600: converting null literal or possible null value to non-nullable type.
    string name = Lookup("nobody");
    Console.WriteLine(name);
}

Si Lookup devuelve legítimamente null, cambie el sitio de llamada para aceptar el valor que falta:

public static void AssignmentFixed()
{
    string? name = Lookup("somebody");
    if (name is not null)
    {
        Console.WriteLine(name);
    }
}

Si Lookup nunca devuelve null, cambie su firma para devolver un tipo de referencia que no acepta valores NULL. Escenarios en los que el estado NULL del valor devuelto depende de la entrada, consulte la sección siguiente sobre los atributos de análisis NULL.

Use el operador anulador de valores null ! solo cuando pueda garantizar que un valor no es null, pero no pueda expresar esa garantía en el sistema de tipos. Cada ! es un lugar en el que el compilador ya no puede protegerlo, por lo que prefiere agregar una comprobación o anotar la API de origen.

Adición de un atributo de análisis null

A veces, la solución adecuada no está en el punto de llamada. La firma de un método no refleja con suficiente precisión la relación entre sus parámetros de entrada y sus valores de salida, y el compilador emite advertencias dentro de un código que por lo demás es seguro:

public static bool IsPresent(string? text) =>
    !string.IsNullOrEmpty(text);

public static void CallerWithoutAttribute(string? text)
{
    if (IsPresent(text))
    {
        // Warning CS8602: dereference of a possibly null reference.
        // The signature doesn't tell the compiler text is not-null here.
        Console.WriteLine(text.Length);
    }
}

El cuerpo de IsPresent demuestra que el argumento no es nulo cuando el método devuelve true, pero la firma no lo dice. Agregue un atributo de análisis que acepta valores NULL para que el contrato forme parte de la API:

public static bool AttributedIsPresent([NotNullWhen(true)] string? text) =>
    !string.IsNullOrEmpty(text);

public static void CallerWithAttribute(string? text)
{
    if (AttributedIsPresent(text))
    {
        // No warning: the attribute tells the compiler text is not-null.
        Console.WriteLine(text.Length);
    }
}

Entre los atributos comunes se incluyen:

La lista completa se encuentra en atributos de análisis estático que aceptan valores NULL.

Inicializar miembros no anulables

Una advertencia de constructor significa un campo, una propiedad o una propiedad automática que no acepta valores NULL (una propiedad que usa el campo de respaldo generado por el compilador, como public string Name { get; set; }) sale del constructor sin tener asignado un valor distinto de NULL:

public class PersonUninitialized
{
    // Warning CS8618: Non-nullable property 'Name' is uninitialized.
    public string Name { get; set; }
}

Tiene varias maneras de abordarlo. Elija el que mejor coincida con la intención de diseño.

Exigir el valor como argumento del constructor. Use un constructor principal (parámetros declarados en el propio tipo, disponibles en todo el cuerpo) o un constructor normal que inicialice la propiedad :

public class PersonInjected(string name)
{
    public string Name { get; } = name;
}

Cree la propiedad required. Quien realiza la llamada debe inicializarlo mediante un inicializador de objeto (la sintaxis { Property = value } que sigue a new):

public class PersonRequired
{
    public required string Name { get; init; }
}

Inicialice con un valor predeterminado. Cuando el tipo tiene un valor vacío significativo, inicialice en la declaración:

public class PersonInitialized
{
    public string Name { get; set; } = "John Doe";
}

Tip

Elija esta técnica solo cuando el tipo tenga un valor predeterminado realmente bueno: uno que sea una instancia válida y totalmente funcional para que los autores de las llamadas consuman. Entre los ejemplos se incluyen colecciones vacías. No invente un valor centinela (un valor de relleno como String.Empty, "N/A", "unknown" o -1 que se trata como «ningún valor») para sustituir a null: silencia la advertencia, pero todo el código que lo llama tiene que conocer y comprobar ese valor centinela, y el sistema de tipos no puede ayudar. Cuando no existe ningún buen valor predeterminado, convierta la propiedad en null en su lugar.

Convierta la propiedad en nullable. Cuando el valor realmente pueda faltar, cambie el tipo a anulable:

public class PersonOptional
{
    public string? Name { get; set; }
}

Si un método auxiliar inicializa el miembro, anote el asistente con MemberNotNullAttribute para que el compilador pueda acreditar llamadas a él.

Comprobación de la configuración del proyecto

Los nuevos proyectos de C# habilitan los tipos de referencia que aceptan valores NULL de forma predeterminada, por lo que la mayoría del código que escribe o lee ya tiene activada la característica. Por lo general, no es necesario configurar nada. Si tiene curiosidad por si un proyecto lo tiene habilitado o si necesita cambiar la configuración, busque el <Nullable> elemento en :.csproj

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

Los valores admitidos comunes son enable (el valor predeterminado para los nuevos proyectos) y disable. Si falta este elemento, el proyecto usa cualquier valor predeterminado que establezcan el SDK y el marco de destino.

Si necesita habilitar la nulabilidad solo en parte de un archivo con las directivas #nullable, o usar los modos parciales warnings y annotations al migrar una base de código existente, consulte Estrategias de migración de nulabilidad.

Dónde ir a continuación

Cuando una advertencia no se ajusta a ninguno de estos patrones, el artículo de referencia Resolución de advertencias de nulabilidad indica la técnica para cada advertencia CS86xx que emite el compilador.

Para planear una migración que habilita progresivamente los tipos de referencia que aceptan valores NULL en un código base existente, consulte Estrategias de migración que aceptan valores NULL.