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.
Tip
¿No está familiarizado con los tipos de referencia que aceptan valores NULL? Lee primero Tipos de referencia anulables. Este tutorial da por hecho que comprende la diferencia entre los tipos de referencia no anulables y anulables, y cómo el compilador realiza el seguimiento del estado de null.
¿Viene de otro idioma? Si has usado los tipos anulables de Kotlin, strictNullChecks de TypeScript o los opcionales de Swift, el modelo conceptual se corresponde directamente. Este ejercicio trata sobre expresar la intención de diseño, no aprender la sintaxis.
En este tutorial, creará una biblioteca pequeña que modela la ejecución de una encuesta. Los datos tienen dos patrones distintos que los tipos de referencia que aceptan valores NULL le permiten distinguir:
- Una pregunta de encuesta siempre debe estar presente. La lista de preguntas y el texto de cada pregunta nunca puede ser
null. - Es posible que falte una respuesta a una pregunta . Los encuestados pueden rechazar responder a algunas o todas las preguntas, y el modelo debe hacer que sea explícito.
Declaras esas reglas con tipos de referencia no anulables y anulables. A continuación, el compilador advierte cada vez que el comportamiento del código no coincide con el diseño.
En este tutorial, usted hará lo siguiente:
- Cree la aplicación.
- Cree las preguntas de la encuesta.
- Cree una encuesta de preguntas.
- Pruebe el requisito not-null.
- Crear tipos de respuesta.
- Crear encuestados
- Genere una respuesta de encuesta.
- Crear un conjunto de respuestas a encuestas.
- Examine los resultados de la encuesta.
Tres clases modelan la encuesta:
-
SurveyQuestion: una pregunta. Se requieren el texto y el tipo de pregunta. -
SurveyRun: la colección de preguntas más la lista de encuestados. -
SurveyResponse: respuestas de un encuestado, que podrían faltar.
Cada tipo usa tipos de referencia no anulables para los valores obligatorios y tipos de referencia anulables para los valores ausentes.
Prerequisites
- La versión más reciente del SDK de .NET
- editor de Visual Studio Code
- El DevKit de C#
En este tutorial se supone que está familiarizado con C# y Visual Studio o la CLI de .NET.
Creación de la aplicación y habilitación de tipos de referencia que aceptan valores NULL
Cree una nueva aplicación de consola denominada NullableIntroduction:
dotnet new console -n NullableIntroduction
cd NullableIntroduction
Crear las preguntas de la encuesta
Agregue un nuevo archivo denominado SurveyQuestion.cs al proyecto y reemplace su contenido por el código siguiente. El texto y el tipo de pregunta no admiten valores NULL, por lo que el constructor debe inicializar ambos:
namespace NullableIntroduction;
public enum QuestionType
{
YesNo,
Number,
Text
}
public class SurveyQuestion(QuestionType typeOfQuestion, string text)
{
public string QuestionText { get; } = text;
public QuestionType TypeOfQuestion { get; } = typeOfQuestion;
}
Los parámetros del constructor son tipos de referencia que no aceptan valores NULL, por lo que el compilador advierte al autor de la llamada si cualquiera de los argumentos podría ser null.
Crear una encuesta de preguntas
A continuación, agregue un nuevo archivo denominado SurveyRun.cs al proyecto y defina una SurveyRun clase para contener la lista de preguntas:
namespace NullableIntroduction;
public class SurveyRun
{
private List<SurveyQuestion> surveyQuestions = [];
public void AddQuestion(QuestionType type, string question) =>
AddQuestion(new SurveyQuestion(type, question));
public void AddQuestion(SurveyQuestion surveyQuestion) =>
surveyQuestions.Add(surveyQuestion);
}
El campo surveyQuestions es un List<SurveyQuestion> no anulable. Usa una expresión de colección para inicializar una lista vacía. Las dos sobrecargas de AddQuestion aceptan parámetros no anulables, por lo que el compilador exige que quienes las llaman no pasen null.
En Program.cs, cree SurveyRun y añada tres preguntas:
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?");
Prueba el requisito de no nulo
Para ver cómo el compilador aplica parámetros que no aceptan valores NULL, intente agregar la siguiente línea y recompilar:
surveyRun.AddQuestion(QuestionType.Text, default);
El compilador emite la advertencia CS8625 porque default se evalúa como null para un tipo de referencia, y AddQuestion espera un string no anulable. Quite la línea antes de continuar.
Tipos de respuestas de compilación
Los encuestados pueden rechazar la encuesta e incluso cuando participan, pueden omitir preguntas individuales. Ambas formas de "faltan" son resultados válidos y el sistema de tipos debe hacer que sean visibles. Ambos formularios se expresan con null.
Agregue un nuevo archivo denominado SurveyResponse.cs al proyecto y defina una SurveyResponse clase. Usa un constructor principal (parámetros declarados en el propio tipo, disponibles en todo el cuerpo) para capturar el valor que siempre se requiere Id:
namespace NullableIntroduction;
public class SurveyResponse(int id)
{
public int Id { get; } = id;
}
Creación de encuestados
Agregue un método de generador estático (un static método que crea y devuelve una nueva instancia del tipo, una alternativa a llamar directamente al constructor) que crea encuestados con un identificador aleatorio:
private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());
Generar una respuesta de encuesta
A continuación, agregue el método que solicita la encuesta a un encuestado. Guarde las respuestas en un diccionario anulable para que el propio tipo indique que el encuestado podría declinar responder:
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 surveyResponses campo es Dictionary<int, string>?. Si desreferencia el campo sin comprobar primero si es null, el compilador emite una advertencia. Dentro de AnswerSurvey, el compilador sabe que surveyResponsesno es nulo inmediatamente después de la expresión new, por lo que el cuerpo del bucle no necesita ninguna comprobación adicional.
Creación de un conjunto de respuestas de encuesta
Agregue un método en que SurveyRun cree una lista de encuestados hasta que haya suficiente consentimiento para participar:
private List<SurveyResponse>? respondents;
public void PerformSurvey(int numberOfRespondents)
{
int respondentsConsenting = 0;
respondents = [];
while (respondentsConsenting < numberOfRespondents)
{
var respondent = SurveyResponse.GetRandomId();
if (respondent.AnswerSurvey(surveyQuestions))
respondentsConsenting++;
respondents.Add(respondent);
}
}
El campo respondents está List<SurveyResponse>?; seguirá null hasta que se ejecute la encuesta.
Llame a PerformSurvey desde Main:
surveyRun.PerformSurvey(50);
Examen de los resultados de la encuesta
Para informar de los resultados, expón algunas funciones auxiliares desde SurveyResponse y SurveyRun. En SurveyResponse, agrega miembros con cuerpo de expresión (miembros definidos con => y una sola expresión en lugar de un bloque { ... }) que controlen el diccionario anulable:
public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";
AnsweredSurvey comprueba el campo comparándolo con null.
Answer usa el ?. operador condicional de null (que se evalúa como null cuando el lado izquierdo es null en lugar de lanzar una excepción) para desreferenciar de forma segura, y el ?? operador de fusión de null (que sustituye el operando derecho cuando el izquierdo es null) para proporcionar una alternativa no nula. El tipo de valor devuelto del método no acepta stringvalores NULL, por lo que los llamadores no necesitan comprobaciones nulas.
En SurveyRun, agregue miembros con forma de expresión que expongan la lista de participantes y preguntas:
public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];
AllParticipants devuelve una secuencia que no acepta valores NULL aunque respondents puede ser null. El ?? operador sustituye Enumerable.Empty<SurveyResponse>() cuando el campo aún no se rellena. Si quita la cláusula ??, el compilador advierte que el método podría devolver null a pesar de tener un tipo de retorno no anulable.
Por último, escriba el informe en la parte inferior de 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");
}
}
Observe que no se necesita ninguna comprobación nula para participant, surveyRun.Questionso surveyRun.GetQuestion(i). Los tipos declaran esos valores como que no aceptan valores NULL, por lo que el compilador los trata como no null en todo el bucle.
Ejecute la aplicación:
dotnet run
La salida es diferente en cada ejecución porque los participantes se generan aleatoriamente, pero cada línea muestra las respuestas de un participante o indica que se negó a responder.
Conclusión
El ejemplo terminado se encuentra en la carpeta csharp/NullableIntroduction del repositorio dotnet/samples . Experimente cambiando los tipos entre anulables y no anulables. Al quitar un ? elemento donde el diseño permite que falten valores, se generan advertencias del compilador que apuntan a cada lugar en el que importa el valor que falta.