Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Tipos de referencia anulables complementan los tipos de referencia de la misma manera que los tipos de valor anulables complementan los tipos de valor. Se declara una variable como un tipo de referencia anulable añadiendo un ? al tipo. Por ejemplo, string? representa un stringque puede ser nulo. Puede usar estos nuevos tipos para expresar con más claridad la intención de diseño: algunas variables siempre deben tener un valor , mientras que otras podrían faltar un valor.
En este tutorial, aprenderá a:
- Incorpore tipos de referencia anulables y no anulables en sus diseños
- Habilite comprobaciones de tipos de referencia anulables en todo el código.
- Escriba código en el que el compilador aplique esas decisiones de diseño.
- Usar la característica de referencia que acepta valores NULL en sus propios diseños
Prerrequisitos
- La versión más reciente del SDK de .NET
- Editor de Visual Studio Code
- El DevKit de C#
En este tutorial se da por supuesto que está familiarizado con C# y .NET, incluido Visual Studio o la CLI de .NET.
Incorpore tipos de referencia anulables en sus diseños
En este tutorial, creará una biblioteca que modela la ejecución de una encuesta. El código utiliza tanto tipos de referencia nulos como no nulos para representar los conceptos del mundo real. Las preguntas de la encuesta nunca pueden ser nulas. Un encuestado podría preferir no responder a una pregunta. Las respuestas podrían ser null en este caso.
El código que escribe para este ejemplo expresa esa intención y el compilador aplica esa intención.
Creación de la aplicación y habilitación de tipos de referencia que aceptan valores NULL
Cree una aplicación de consola en Visual Studio o desde la línea de comandos mediante dotnet new console. Asigne a la aplicación el nombre NullableIntroduction. Una vez creada la aplicación, debe especificar que todo el proyecto se compile en un contexto de anotación que acepta valores NULL habilitado. Abra el archivo .csproj y agregue un elemento Nullable al elemento PropertyGroup. Establezca su valor en enable. Debe activar la característica de tipos de referencia anulables en los proyectos creados antes de C# 11 / .NET 7. Una vez activada la característica, las declaraciones de variables de referencia existentes se convierten en tipos de referencia que no aceptan valores NULL. Aunque esa decisión ayuda a encontrar problemas en los que es posible que el código existente no tenga comprobaciones null adecuadas, es posible que no refleje con precisión la intención de diseño original:
<Nullable>enable</Nullable>
Diseñar los tipos de la aplicación
Esta aplicación de encuesta requiere la creación de estas clases:
- Una clase que modela la lista de preguntas
- Una clase que modela una lista de personas contactadas para la encuesta
- Clase que modela las respuestas de una persona que realizó la encuesta.
Estos tipos utilizan tanto tipos de referencia anulables como no anulables para indicar qué miembros son obligatorios y cuáles son opcionales. Los tipos de referencia que aceptan valores NULL comunican claramente esa intención de diseño:
- Las preguntas que forman parte de la encuesta nunca pueden ser nulas: no tiene sentido formular una pregunta vacía.
- Los encuestados nunca pueden ser nulos. Quiere realizar un seguimiento de las personas a las que se ha contactado, incluso a los encuestados que rechazaron participar.
- Cualquier respuesta a una pregunta podría ser null. Los encuestados pueden rechazar responder a algunas o todas las preguntas.
Es posible que esté tan acostumbrado a los tipos de referencia que permiten valores null que podría perder otras oportunidades para declarar instancias no anulables.
- La colección de preguntas debe ser no anulable.
- La colección de encuestados debe aceptar valores NULL.
A medida que escribe el código, verá que un tipo de referencia que no acepta valores NULL como valor predeterminado para las referencias evita errores comunes que podrían provocar NullReferenceExceptions. Una lección de este tutorial es que ha tomado decisiones sobre qué variables podrían ser o no podían ser null. El lenguaje no proporcionó sintaxis para expresar esas decisiones. Ahora sí.
La aplicación que compile realiza los pasos siguientes:
- Crea una encuesta y le agrega preguntas.
- Crea un conjunto pseudoaleatorio de encuestados.
- Se pone en contacto con los encuestados hasta que el tamaño de la encuesta completada alcance el número objetivo.
- Escribe estadísticas importantes sobre las respuestas de la encuesta.
Construir la encuesta con tipos de referencia anulables y no anulables
El primer código que escriba crea la encuesta. Tú escribes clases para modelar una pregunta de encuesta y una ejecución de encuesta. La encuesta tiene tres tipos de preguntas, que se distinguen por el formato de la respuesta: Sí/No respuestas, números y respuestas de texto. Cree una clase public SurveyQuestion:
namespace NullableIntroduction
{
public class SurveyQuestion
{
}
}
El compilador interpreta cada declaración de variable de tipo de referencia como un tipo de referencia que no admite un valor NULL para el código en un contexto de anotaciones que admite un valor NULL habilitado. Puede ver la primera advertencia agregando propiedades para el texto de la pregunta y el tipo de pregunta, como se muestra en el código siguiente:
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}
public class SurveyQuestion
{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }
}
}
Dado que no inicializó QuestionText, el compilador emite una advertencia de que no se inicializó una propiedad que no acepta valores NULL. El diseño requiere que el texto de la pregunta no sea NULL, por lo que también debe agregar un constructor para inicializarlo y el valor QuestionType. La definición de clase finalizada tiene el siguiente aspecto:
namespace NullableIntroduction;
public enum QuestionType
{
YesNo,
Number,
Text
}
public class SurveyQuestion
{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }
public SurveyQuestion(QuestionType typeOfQuestion, string text) =>
(TypeOfQuestion, QuestionText) = (typeOfQuestion, text);
}
Al agregar el constructor, se quita la advertencia. El argumento constructor también es un tipo de referencia que no acepta valores NULL, por lo que el compilador no emite ninguna advertencia.
A continuación, cree una clase public denominada SurveyRun. Esta clase contiene una lista de SurveyQuestion objetos y métodos para agregar preguntas a la encuesta, como se muestra en el código siguiente:
using System.Collections.Generic;
namespace NullableIntroduction
{
public class SurveyRun
{
private List<SurveyQuestion> surveyQuestions = new List<SurveyQuestion>();
public void AddQuestion(QuestionType type, string question) =>
AddQuestion(new SurveyQuestion(type, question));
public void AddQuestion(SurveyQuestion surveyQuestion) => surveyQuestions.Add(surveyQuestion);
}
}
Como antes, debe inicializar el objeto de lista en un valor distinto de NULL o el compilador emite una advertencia. No hay comprobaciones nulas en la segunda sobrecarga de AddQuestion porque el compilador ayuda a aplicar el contrato que no acepta valores NULL: ha declarado que la variable no acepta valores NULL. Aunque el compilador advierte sobre posibles asignaciones nulas, los valores NULL en tiempo de ejecución siguen siendo posibles. En el caso de las API públicas, considere la posibilidad de agregar la validación de argumentos incluso para los tipos de referencia que no aceptan valores NULL, ya que es posible que el código de cliente no tenga habilitados tipos de referencia que aceptan valores NULL o podría pasar intencionadamente null.
Cambie a Program.cs en el editor y reemplace el contenido de Main por las siguientes líneas de código:
var surveyRun = new SurveyRun();
surveyRun.AddQuestion(QuestionType.YesNo, "Has your code ever thrown a NullReferenceException?");
surveyRun.AddQuestion(new SurveyQuestion(QuestionType.Number, "How many times (to the nearest 100) has that happened?"));
surveyRun.AddQuestion(QuestionType.Text, "What is your favorite color?");
Dado que todo el proyecto está en un contexto de anotaciones nulas habilitadas, recibes advertencias cuando se pasa null a cualquier método que espera un tipo de referencia no anulable. Pruébelo agregando la siguiente línea a Main:
surveyRun.AddQuestion(QuestionType.Text, default);
Crear encuestados y obtener respuestas a la encuesta
A continuación, escriba el código que genera respuestas a la encuesta. Este proceso implica varias tareas pequeñas:
- Cree un método que genere objetos encuestados. Estos objetos representan a las personas que se les pide que rellenen la encuesta.
- Cree lógica para simular hacer las preguntas a un encuestado y recopilar respuestas o observar que un encuestado no respondió.
- Repita hasta que los encuestados respondan a la encuesta.
Necesita una clase para representar una respuesta de encuesta, así que agréguela ahora. Habilite la compatibilidad con la aceptación de valores NULL. Agregue una propiedad Id y un constructor que lo inicialice, como se muestra en el código siguiente:
namespace NullableIntroduction
{
public class SurveyResponse
{
public int Id { get; }
public SurveyResponse(int id) => Id = id;
}
}
A continuación, agregue un método static para crear nuevos participantes mediante la generación de un identificador aleatorio:
private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());
La responsabilidad principal de esta clase es generar las respuestas de un participante a las preguntas de la encuesta. Esta responsabilidad tiene algunos pasos:
- Solicitar participación en la encuesta. Si la persona no da su consentimiento, devuelva una respuesta ausente (o nula).
- Haga cada pregunta y registre la respuesta. También podría faltar cada respuesta (o null).
Agregue el código siguiente a la clase SurveyResponse:
private Dictionary<int, string>? surveyResponses;
public bool AnswerSurvey(IEnumerable<SurveyQuestion> questions)
{
if (ConsentToSurvey())
{
surveyResponses = new Dictionary<int, string>();
int index = 0;
foreach (var question in questions)
{
var answer = GenerateAnswer(question);
if (answer != null)
{
surveyResponses.Add(index, answer);
}
index++;
}
}
return surveyResponses != null;
}
private bool ConsentToSurvey() => randomGenerator.Next(0, 2) == 1;
private string? GenerateAnswer(SurveyQuestion question)
{
switch (question.TypeOfQuestion)
{
case QuestionType.YesNo:
int n = randomGenerator.Next(-1, 2);
return (n == -1) ? default : (n == 0) ? "No" : "Yes";
case QuestionType.Number:
n = randomGenerator.Next(-30, 101);
return (n < 0) ? default : n.ToString();
case QuestionType.Text:
default:
switch (randomGenerator.Next(0, 5))
{
case 0:
return default;
case 1:
return "Red";
case 2:
return "Green";
case 3:
return "Blue";
}
return "Red. No, Green. Wait.. Blue... AAARGGGGGHHH!";
}
}
El almacenamiento de las respuestas de la encuesta es un Dictionary<int, string>?, lo que indica que podría ser nulo. Está usando la nueva característica de lenguaje para declarar la intención de diseño, tanto para el compilador como para cualquier persona que lea el código más adelante. Si alguna vez desreferencia surveyResponses sin comprobar el null valor primero, recibirá una advertencia del compilador. No recibe una advertencia en el AnswerSurvey método porque el compilador puede determinar que la surveyResponses variable se estableció en un valor no NULL en el código anterior.
El uso de null para las respuestas que faltan resalta un punto clave para trabajar con tipos de referencia que aceptan valores NULL: el objetivo no es quitar todos los valores de null del programa. En su lugar, el objetivo es asegurarse de que el código que escribe expresa la intención del diseño. Los valores que faltan son un concepto necesario para expresar en el código. El null valor es una manera clara de expresar esos valores que faltan. Intentar quitar todos los null valores conduce a definir alguna otra manera de expresar esos valores que faltan sin null.
A continuación, debe escribir el método PerformSurvey en la clase SurveyRun. Agregue el código siguiente en la clase SurveyRun:
private List<SurveyResponse>? respondents;
public void PerformSurvey(int numberOfRespondents)
{
int respondentsConsenting = 0;
respondents = new List<SurveyResponse>();
while (respondentsConsenting < numberOfRespondents)
{
var respondent = SurveyResponse.GetRandomId();
if (respondent.AnswerSurvey(surveyQuestions))
respondentsConsenting++;
respondents.Add(respondent);
}
}
Aquí de nuevo, la elección de un valor NULLable List<SurveyResponse>? indica que la respuesta podría ser NULL. Esto indica que la encuesta aún no se ha dado a ningún encuestado. Tenga en cuenta que se añaden encuestados hasta obtener suficiente consentimiento.
El último paso para ejecutar la encuesta es agregar una llamada para realizar la encuesta al final del método Main:
surveyRun.PerformSurvey(50);
Examen de las respuestas de la encuesta
El último paso es mostrar los resultados de la encuesta. Agregas código a muchas de las clases que escribiste. Este código hace hincapié en el valor de distinguir entre los tipos de referencia que aceptan valores nulos y los que no los aceptan. Comience por agregar los siguientes dos miembros definidos por expresión a la clase SurveyResponse:
public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";
Dado que surveyResponses es un tipo de referencia que acepta valores NULL, las comprobaciones de nulidad son necesarias antes de desreferenciarla. El método Answer devuelve una cadena no nula, por lo que tenemos que cubrir el caso de una respuesta que falta mediante el operador de coalescencia nula.
A continuación, agregue estos tres miembros con forma de expresión a la clase SurveyRun:
public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];
El miembro AllParticipants debe tener en cuenta que la variable respondents podría ser null, pero el valor devuelto no puede ser NULL. Si cambia esa expresión quitando el ?? y la secuencia vacía que sigue, el compilador advierte que el método podría devolver null y que su firma de retorno corresponde a un tipo no anulable.
Por último, agregue el siguiente bucle en la parte inferior del método Main:
foreach (var participant in surveyRun.AllParticipants)
{
Console.WriteLine($"Participant: {participant.Id}:");
if (participant.AnsweredSurvey)
{
for (int i = 0; i < surveyRun.Questions.Count; i++)
{
var answer = participant.Answer(i);
Console.WriteLine($"\t{surveyRun.GetQuestion(i).QuestionText} : {answer}");
}
}
else
{
Console.WriteLine("\tNo responses");
}
}
No necesita ninguna null comprobación en este código porque ha diseñado las interfaces subyacentes de manera que todas devuelvan tipos de referencia no nulos. El análisis estático del compilador ayuda a garantizar que se siguen estos contratos de diseño.
Obtención del código
Puede obtener el código del tutorial terminado en nuestro repositorio de ejemplos de en la carpeta csharp/NullableIntroduction.
Experimente cambiando las declaraciones de tipo entre los tipos de referencia que aceptan valores NULL y los que no aceptan valores NULL. Vea cómo genera diferentes advertencias y asegúrese de no desreferenciar accidentalmente un null.
Pasos siguientes
Aprenda a usar el tipo de referencia que acepta valores NULL al usar Entity Framework: