Expresión de la intención
En la unidad anterior, ha aprendido cómo el compilador de C# puede realizar análisis estáticos para ayudar a protegerse contra NullReferenceException
. También ha aprendido a habilitar un contexto que acepta valores NULL. En esta unidad, aprenderá más sobre cómo expresar explícitamente la intención en un contexto que acepta valores NULL.
Declaración de variables
Con un contexto que acepta valores NULL habilitado, tiene más visibilidad sobre cómo el compilador ve el código. Puede actuar sobre las advertencias generadas a partir de un contexto que acepta valores NULL y, al hacerlo, se definen explícitamente sus intenciones. Por ejemplo, vamos a seguir examinando el código FooBar
y a examinar la declaración y la asignación:
// Define as nullable
FooBar? fooBar = null;
Tenga en cuenta el ?
agregado a FooBar
. Esto indica al compilador que se pretende explícitamente que fooBar
acepte valores NULL. Si no pretende que fooBar
admita valores NULL, pero quiere evitar la advertencia, tenga en cuenta lo siguiente:
// Define as non-nullable, but tell compiler to ignore warning
// Same as FooBar fooBar = default!;
FooBar fooBar = null!;
En este ejemplo se agrega el operador que admite valores NULL (!
) a null
, que indica al compilador que va a inicializar explícitamente esta variable como NULL. El compilador no emitirá advertencias sobre esta referencia como NULL.
Un procedimiento recomendado es asignar valores no null
a las variables que no aceptan valores NULL cuando se declaran, si es posible:
// Define as non-nullable, assign using 'new' keyword
FooBar fooBar = new(Id: 1, Name: "Foo");
Operadores
Como se describe en la unidad anterior, C# define varios operadores para expresar su intención en relación a tipos de referencia que aceptan valores NULL.
Operador que acepta valores NULL (!
)
En la sección anterior se presentó el operador que acepta valores NULL (!
). Indica al compilador que ignore la advertencia CS8600. Esta es una manera de transmitir al compilador que sabe lo que está haciendo, pero incluye la advertencia de que realmente debe saber lo que está haciendo.
Al inicializar tipos que no aceptan valores NULL mientras se habilita un contexto que acepta valores NULL, es posible que tenga que pedir la aceptación explícitamente al compilador. Por ejemplo, considere el siguiente código:
#nullable enable
using System.Collections.Generic;
var fooList = new List<FooBar>
{
new(Id: 1, Name: "Foo"),
new(Id: 2, Name: "Bar")
};
FooBar fooBar = fooList.Find(f => f.Name == "Bar");
// The FooBar type definition for example.
record FooBar(int Id, string Name);
En el ejemplo anterior, FooBar fooBar = fooList.Find(f => f.Name == "Bar");
genera una advertencia CS8600 porque Find
podría devolver null
. Este posible null
se asignaría a fooBar
, que no acepta valores NULL en este contexto. Sin embargo, en este ejemplo inventado, sabemos que Find
nunca devolverá null
como se ha escrito. Puede expresar esta intención al compilador con el operador que acepta valores NULL:
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!;
Observe !
al final de fooList.Find(f => f.Name == "Bar")
. Esto indica al compilador que sabe que el objeto devuelto por el método Find
podría ser null
, y habría ningún problema.
El operador que admite valores NULL también se puede aplicar a un objeto insertado antes de una evaluación de propiedad o de una llamada de método. Considere otro ejemplo inventado:
List<FooBar>? fooList = FooListFactory.GetFooList();
// Declare variable and assign it as null.
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!; // generates warning
static class FooListFactory
{
public static List<FooBar>? GetFooList() =>
new List<FooBar>
{
new(Id: 1, Name: "Foo"),
new(Id: 2, Name: "Bar")
};
}
// The FooBar type definition for example.
record FooBar(int Id, string Name);
En el ejemplo anterior:
GetFooList
es un método estático que devuelve un tipo que acepta valores NULL,List<FooBar>?
.- A
fooList
se le asigna el valor devuelto porGetFooList
. - El compilador genera una advertencia en
fooList.Find(f => f.Name == "Bar");
porque el valor asignado afooList
podría sernull
. - Suponiendo que
fooList
no esnull
,Find
podría devolvernull
, pero sabemos que no, por lo que se aplica el operador que acepta valores NULL.
Puede aplicar el operador que acepta valores NULL a fooList
para deshabilitar la advertencia:
FooBar fooBar = fooList!.Find(f => f.Name == "Bar")!;
Nota:
Debe usar el operador que admite valores NULL con criterio. Usarlo simplemente para descartar una advertencia significa que le está indicando al compilador que no le ayude a detectar posibles contratiempos de valores NULL. Úselo con moderación y solo cuando esté seguro.
Para obtener más información, consulte ! (permite valores NULL) (referencia de C#).
Operador de fusión de NULL (??
)
Al trabajar con tipos que aceptan valores NULL, es posible que tenga que evaluar si actualmente son null
y realizar determinadas acciones. Por ejemplo, cuando a un tipo que acepta valores NULL se le ha asignado null
o no está inicializado, es posible que tenga que asignarle un valor distinto de NULL. Aquí es donde resulta útil el operador de fusión de NULL (??
).
Considere el ejemplo siguiente:
public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
salesTax ??= DefaultStateSalesTax.Value;
// Safely use salesTax object.
}
En el código de C# anterior:
- El parámetro
salesTax
se define como un elementoIStateSalesTax
que acepta valores NULL. - Dentro del cuerpo del método, se asigna
salesTax
condicionalmente mediante el operador de fusión de NULL.- Esto garantiza que si
salesTax
se ha pasado comonull
, entonces tendrá un valor.
- Esto garantiza que si
Sugerencia
Esto es funcionalmente equivalente al siguiente código de C#:
public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
if (salesTax is null)
{
salesTax = DefaultStateSalesTax.Value;
}
// Safely use salesTax object.
}
Este es un ejemplo de otra expresión común de C# donde el operador de fusión de NULL puede ser útil:
public sealed class Wrapper<T> where T : new()
{
private T _source;
// If given a source, wrap it. Otherwise, wrap a new source:
public Wrapper(T source = null) => _source = source ?? new T();
}
El código de C# anterior:
- Define una clase contenedora genérica, donde el parámetro de tipo genérico está restringido a
new()
. - El constructor acepta un parámetro
T source
con el valor predeterminadonull
. - El elemento
_source
encapsulado se inicializa condicionalmente ennew T()
.
Para obtener más información, consulte Operadores ?? y ??= (referencia de C#).
Operador condicional NULL (?.
)
Al trabajar con tipos que aceptan valores NULL, es posible que tenga que realizar acciones condicionalmente en función del estado de un objeto null
. Por ejemplo, en la unidad anterior, el registro FooBar
se usó para mostrar NullReferenceException
mediante la desreferenciación de null
. Esto se produjo cuando se llamó a ToString
. Considere este mismo ejemplo, pero ahora aplicando el operador condicional NULL:
using System;
// Declare variable and assign it as null.
FooBar fooBar = null;
// Conditionally dereference variable.
var str = fooBar?.ToString();
Console.Write(str);
// The FooBar type definition.
record FooBar(int Id, string Name);
El código de C# anterior:
- Desreferencia condicionalmente
fooBar
, asignando el resultado deToString
a la variablestr
.- La variable
str
es de tipostring?
(cadena que acepta valores NULL).
- La variable
- Escribe el valor de
str
en la salida estándar, que no es nada. - La llamada a
Console.Write(null)
es válida, por lo que no hay ninguna advertencia. - Recibiría una advertencia si llamara a
Console.Write(str.Length)
, porque podría desreferenciar NULL.
Sugerencia
Esto es funcionalmente equivalente al siguiente código de C#:
using System;
// Declare variable and assign it as null.
FooBar fooBar = null;
// Conditionally dereference variable.
string str = (fooBar is not null) ? fooBar.ToString() : default;
Console.Write(str);
// The FooBar type definition.
record FooBar(int Id, string Name);
Puede combinar el operador para expresar mejor su intención. Por ejemplo, podría encadenar los operadores ?.
y ??
:
FooBar fooBar = null;
var str = fooBar?.ToString() ?? "unknown";
Console.Write(str); // output: unknown
Para obtener más información, consulte Operadores ?. y ?[] (condicional NULL).
Resumen
En esta unidad, ha aprendido a expresar la intención de nulabilidad en el código. En la siguiente unidad, aplicará lo que ha aprendido a un proyecto existente.