Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

19 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 23 de septiembre de 2025
Inspector examinando con lupa un código C#

Hace algún tiempo hablamos sobre el atributo [MemberNotNullWhen] y vimos cómo se podía usar para mejorar la seguridad frente a valores nulos en nuestras aplicaciones.

Como recordaréis, este atributo lo aplicábamos sobre miembros booleanos para asegurar que otro miembro de su misma clase no contendrá null, dependiendo del valor (true o false) del primero. El ejemplo que lo ilustraba era el siguiente:

public class Result<T>
{
    [MemberNotNullWhen(true, nameof(Value))]  // Si Success es true, Value no es null
    [MemberNotNullWhen(false, nameof(Error))] // Si Success es false, Error no es null
    public bool Success { get; init; }

    public string? Error { get; init; }
    public T? Value { get; init; }

    // Constructores, factorías, etc
}

Pero también comentamos que existían muchos otros atributos que el compilador empleaba para afinar en la detección de posibles problemas similares en el código. Al usarlos, el compilador comprende mejor las intenciones de nuestro código respecto a la utilización de nulos, y puede generar advertencias que impiden posteriores errores en tiempo de ejecución, pero sólo cuando son estrictamente necesarias.

En este nuevo post, vamos a echar un vistazo a los siguientes:

  • [NotNullWhen]
  • [NotNullIfNotNull]
  • [MemberNotNull]

[NotNullWhen]: el parámetro out no será nulo cuando el método devuelva un valor específico

Este atributo se aplica a parámetros de salida (out) de un método booleano, y se utiliza para indicar que el valor del parámetro no será nulo dependiendo del valor de retorno del método.

Un ejemplo típico lo encontramos con métodos como TryXXX(), que devuelven un valor booleano que indica si la operación fue exitosa o no, y disponen de un parámetro out que contiene el resultado de la operación. En este caso, el atributo se aplicaría al parámetro out para indicar que su valor no será nulo si el método devuelve true.

Veamos un ejemplo sencillo. El siguiente método TryGetFriend() intenta obtener un amigo a partir de su identificador. Si el amigo existe, devuelve true y el parámetro out contendrá el objeto Friend. Si no existe, devuelve false y el parámetro out será nulo.

bool TryGetFriend(int id, [NotNullWhen(true)] out Friend? value)
{
    if (id == 1)
    {
        value = new Friend("John", 30);
        return true;
    }

    value = null;
    return false;
}

record Friend(string Name, int Age);

Ahora, si intentamos usar el método TryGetFriend() de la siguiente manera, veremos que el compilador nos advierte de que el valor de value puede ser nulo, y nos sugiere usar una declaración if para comprobarlo, o un acceso condicional (?.) para evitar un NullReferenceException:

if (TryGetFriend(1, out var friend))
{
    Console.WriteLine(friend.Name); // CS8602: Dereference of a possibly null reference
}

Es obvio; sin darle más pistas, el compilador no puede saber si friend contendrá un valor nulo en ese punto, porque desconoce que el hecho de que la llamada al método retorne true implica que el valor de friend no será nulo.

Sin embargo, si aplicamos el atributo [NotNullWhen(true)] al parámetro out, el compilador ya tendrá la certeza de que el valor de friend no será nulo si la llamada al método devuelve true, y no generará la advertencia:

bool TryGetFriend(int id, [NotNullWhen(true)] out Friend? value)
{
    ...
}

[NotNullIfNotNull]: el método no devolverá un valor nulo si el parámetro indicado no lo es

Este atributo se aplica sobre el retorno de un método, indicando que éste no será nulo cuando el parámetro cuyo nombre especificamos tampoco lo sea. Por ejemplo, observad el siguiente método ToUpper() que convierte una cadena a mayúsculas:

string? ToUpper(string? inputString) 
{
    return inputString?.ToUpper();
}

El método devolverá un valor nulo si se le envía un nulo, pero retornará una cadena válida en caso contrario. Sin embargo, por su firma, el compilador no puede determinar la relación entre la nulabilidad del parámetro entrante y el valor de retorno. Por lo tanto, si usamos el método de la siguiente manera obtendremos la advertencia de posible acceso a una referencia nula:

var str = ToUpper("Hello world");
Console.WriteLine(str.Length); // CS8602: Dereference of a possibly null reference

Apliquemos el atributo [NotNullIfNotNull] al retorno del método de la siguiente manera:

[return:NotNullIfNotNull(nameof(inputString))]
string? ToUpper2(string? inputString) 
{
    return inputString?.ToUpper();
}

Hecho ese cambio, el compilador ya sabrá que el valor de retorno no será nulo si el parámetro tampoco lo es, y generará la advertencia sólo cuando sea estrictamente necesaria:

var str1 = ToUpper("Hello world");
Console.WriteLine(str1.Length); // Ok, sin advertencias

var str2 = ToUpper(null);
Console.WriteLine(str2.Length); // CS8602: Dereference of a possibly null reference

[MemberNotNull]: el miembro no será nulo después de ejecutar un código

Con este atributo podemos indicar los miembros de una clase que no serán nulos después de invocar un método o acceder a una propiedad.

Observad el siguiente ejemplo. La clase Friend tiene una propiedad Name que puede ser nula, y un método Initialize() que la inicializa. Sin embargo, el compilador no puede saber que después de llamar a Initialize(), la propiedad Name no será nula.

Por tanto, ante la duda, el compilador nos advierte de que la propiedad Name podría ser nula:

var f = new Friend();
Console.WriteLine(f.Name.Length); // CS8602: Dereference of a possibly null reference

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

    public void Initialize()
    {
        Name = "Anonymous";
    }
}

Para indicarle al compilador que la propiedad Name no será nula después de ejecutar el método Initialize(), aplicamos el atributo [MemberNotNull] a la propiedad:

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

    [MemberNotNull(nameof(Name))]
    public void Initialize()
    {
        Name = "Anonymous";
    }
}

De esta forma, tras la llamada a Initialize(), el compilador no generará la advertencia:

var f = new Friend();
// Todavía no podemos asegurar que Name no sea nulo:
Console.WriteLine(f.Name.Length); // CS8602: Dereference of a possibly null reference

f.Initialize();
Console.WriteLine(f.Name.Length); // Ok, porque seguro que Name no es nulo

Por último, es importante destacar que el atributo [MemberNotNull] no se puede utilizar en constructores, y permite especificar varios miembros separados por comas, como en el siguiente ejemplo:

[MemberNotNull(nameof(Name), nameof(Address))]

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!