En un proyecto C # 8 con tipos de referencia anulables habilitados, tengo el siguiente código que creo que debería darme una advertencia sobre una posible desreferencia nula, pero no lo hace:
public class ExampleClassMember
{
public int Value { get; }
}
public struct ExampleStruct
{
public ExampleClassMember Member { get; }
}
public class Program
{
public static void Main(string[] args)
{
var instance = new ExampleStruct();
Console.WriteLine(instance.Member.Value); // expected warning here about possible null dereference
}
}
Cuando instance
se inicializa con el constructor predeterminado, instance.Member
se establece en el valor predeterminado de ExampleClassMember
, que es null
. Por lo tanto, instance.Member.Value
lanzará un NullReferenceException
en tiempo de ejecución. Como entiendo la detección de nulabilidad de C # 8, debería recibir una advertencia del compilador sobre esta posibilidad, pero no lo hago; ¿porqué es eso?
c#
nullable
c#-8.0
nullable-reference-types
DylanSp
fuente
fuente
ExampleStruct
destruct
aclass
.Nullable
Respuestas:
Tenga en cuenta que no hay razón para que haya una advertencia en la llamada
Console.WriteLine()
. La propiedad de tipo de referencia no es un tipo anulable, por lo que no es necesario que el compilador advierta que puede ser nulo.Podría argumentar que el compilador debería advertir sobre la referencia en
struct
sí misma. Eso me parecería razonable. Pero no lo hace. Esto parece ser un vacío legal, causado por la inicialización predeterminada para los tipos de valor, es decir, siempre debe haber un constructor predeterminado (sin parámetros), que siempre pone a cero todos los campos (nulos para los campos de tipo de referencia, ceros para los tipos numéricos, etc.) )Lo llamo un vacío legal, porque en teoría los valores de referencia no anulables siempre deberían ser no nulos. Duh :)
Este vacío parece abordarse en este artículo del blog: Introducción de tipos de referencia anulables en C #
En otras palabras, sí, esto es un vacío legal, pero no, no es un error. Los diseñadores de lenguaje lo saben, pero han optado por dejar este escenario fuera de las advertencias, porque no sería práctico hacerlo de otra manera dada la forma en que
struct
funciona la inicialización.Tenga en cuenta que esto también está de acuerdo con la filosofía más amplia detrás de la función. Del mismo artículo:
También tenga en cuenta que este mismo problema existe con las matrices de tipos de referencia nominalmente no anulables (por ejemplo
string[]
). Cuando crea la matriz, todos los valores de referencia sonnull
y, sin embargo, esto es legal y no generará ninguna advertencia.Demasiado para explicar por qué las cosas son como son. Entonces la pregunta es, ¿qué hacer al respecto? Eso es mucho más subjetivo, y no creo que haya una respuesta correcta o incorrecta. Dicho eso ...
Yo personalmente trataría mis
struct
tipos caso por caso. Para aquellos en los que la intención es en realidad un tipo de referencia anulable, aplicaría la?
anotación. De lo contrario, no lo haría.Técnicamente, cada valor de referencia individual en a
struct
debe ser "anulable", es decir, incluir la?
anotación anulable con el nombre del tipo. Pero al igual que con muchas características similares (como async / await en C # oconst
en C ++), esto tiene un aspecto "infeccioso", ya que deberá anular esa anotación más adelante (con la!
anotación) o incluir una comprobación nula explícita , o solo asigne ese valor a otra variable de tipo de referencia anulable.Para mí, esto anula mucho el propósito de habilitar tipos de referencia anulables. Dado que tales miembros de
struct
tipos requerirán un manejo de casos especiales en algún momento de todos modos, y dado que la única forma de manejarlo de manera segura y al mismo tiempo poder usar tipos de referencia no anulables es poner cheques nulos en todos los lugares donde usestruct
, creo que es una opción de implementación razonable aceptar que cuando el código se inicializastruct
, es responsabilidad del código hacerlo correctamente y asegurarse de que el miembro del tipo de referencia no anulable se inicialice de hecho a un valor no nulo.Esto se puede ayudar proporcionando un medio de inicialización "oficial", como un constructor no predeterminado (es decir, uno con parámetros) o un método de fábrica. Siempre existirá el riesgo de usar el constructor predeterminado, o ningún constructor (como en las asignaciones de matrices), pero al proporcionar un medio conveniente para inicializar
struct
correctamente, esto fomentará el código que lo usa para evitar referencias nulas en variables anulables.Dicho esto, si lo que quiere es 100% de seguridad con respecto a los tipos de referencia anulables, entonces claramente el enfoque correcto para ese objetivo en particular es anotar siempre cada miembro de tipo de referencia en un
struct
con?
. Esto significa que cada campo y cada propiedad implementada automáticamente, junto con cualquier método o captador de propiedades que devuelva directamente dichos valores o el producto de dichos valores. Entonces, el código de consumo deberá incluir comprobaciones nulas o el operador que perdona los valores nulos en cada punto donde dichos valores se copien en variables no anulables.fuente
A la luz de la excelente respuesta de @ peter-duniho, parece que a partir de octubre de 2019 es mejor marcar a todos los miembros que no sean de valor como una referencia anulable.
fuente