Öğretici: Tasarım amacınızı null olabilen ve null olamayan başvuru türleriyle ifade edin

Tip

Null atanabilir başvuru türleri konusunda yeni misiniz? Önce Null atanabilir başvuru türleri konusunu okuyun. Bu öğretici, null olamayan ve null olabilir başvuru tipleri arasındaki farkı ve derleyicinin null durumunu nasıl izlediğini anladığınızı varsayar.

Başka bir dilden mi geliyorsunuz? Kotlin'in null atanabilir türlerini, TypeScript'in strictNullChecks veya Swift'in opsiyonel türlerini kullandıysanız, kavramsal model birebir örtüşür. Buradaki alıştırma, söz dizimini öğrenmekle değil tasarım amacını ifade etmeyle ilgili.

Bu öğreticide, bir anketin yürütülmesini modelleyen küçük bir kitaplık oluşturacaksınız. Verilerde, null atanabilir referans türlerinin ayırt etmenizi sağladığı iki ayrı desen vardır:

  • Bir anket sorusu her zaman mevcut olmalıdır. Soru listesi ve her sorunun metni hiçbir zaman olamaz null.
  • Bir soruya verilen yanıt eksik olabilir. Yanıtlayanlar soruların bazılarını veya tümünü yanıtlamayı reddedebilir ve model bunu açıkça belirtmelidir.

Bu kuralları null atanamayan ve null atanabilen başvuru türleriyle tanımlarsınız. Ardından derleyici, kodun davranışı tasarımla eşleşmediği zaman uyarır.

Bu eğitimde, siz:

  • Uygulamayı oluşturun.
  • Anket sorularını oluşturun.
  • Sorulardan oluşan bir anket oluşturun.
  • Null olmayan gereksinimi test edin.
  • Yanıt türleri oluşturun.
  • Yanıtlayanlar oluşturun.
  • Bir anket yanıtı oluşturun.
  • Bir dizi anket yanıtı oluşturun.
  • Anket sonuçlarını inceleyin.

Anket üç sınıfla modellenir:

  • SurveyQuestion: bir soru. Metin ve soru türü gereklidir.
  • SurveyRun: soru koleksiyonu ve yanıtlayanlar listesi.
  • SurveyResponse: yanıtlayanlardan birinin yanıtları eksik olabilir.

Her tür, gerekli değerler için null atanamayan referans türlerini ve eksik değerler için null atanabilen referans türlerini kullanır.

Prerequisites

Bu öğreticide C# ve Visual Studio veya .NET CLI hakkında bilgi sahibi olduğunuz varsayılır.

Uygulamayı oluştur ve null atanabilir referans türlerini etkinleştir

adlı NullableIntroductionyeni bir konsol uygulaması oluşturun:

dotnet new console -n NullableIntroduction
cd NullableIntroduction

Anket sorularını oluşturma

Projeye adlı SurveyQuestion.cs yeni bir dosya ekleyin ve içeriğini aşağıdaki kodla değiştirin. Metin ve soru türü null olamaz, bu nedenle yapıcı her ikisini de başlatmalıdır:

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;
}

Oluşturucu parametreleri null olamayan başvuru türleridir, bu nedenle iki bağımsız değişkenden biri null olabilir durumdaysa derleyici çağıranı uyarır.

Soru anketi oluşturma

Ardından, projeye adlı SurveyRun.cs yeni bir dosya ekleyin ve soru listesini tutmak için bir SurveyRun sınıf tanımlayın:

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);
}

surveyQuestions alanı, null olamayan bir List<SurveyQuestion> değeridir. Boş bir liste başlatmak için bir koleksiyon ifadesi kullanır. Her iki AddQuestion aşırı yüklemesi de null atanamaz parametreleri kabul eder, bu nedenle derleyici çağıranların null geçirmemesini zorunlu kılar.

Program.cs içinde bir SurveyRun oluşturun ve üç soru ekleyin:

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?");

Null olmayan gereksinimi test etme

Derleyicinin null değer atanamayan parametreleri nasıl zorunlu kılması için aşağıdaki satırı eklemeyi ve yeniden derlemeyi deneyin:

surveyRun.AddQuestion(QuestionType.Text, default);

Derleyici, default bir referans türü için null olarak değerlendirildiği ve AddQuestion null atanamaz bir string beklediği için CS8625 uyarısını verir. Devam etmeden önce satırı kaldırın.

Derleme yanıtı türleri

Yanıtlayanlar ankete katılmayı reddedebilir ve katıldıklarında bile tek tek soruları atlayabilirler. Her iki "eksik" biçimi de geçerli sonuçlardır ve tür sistemi bunları görünür hale getirmelidir. her iki formu da ile nullifade edebilirsiniz.

Projeye adlı SurveyResponse.cs yeni bir dosya ekleyin ve bir SurveyResponse sınıf tanımlayın. Her zaman gerekli olan Id değerini almak için birincil oluşturucu kullanın (parametreler doğrudan tür üzerinde bildirilir ve gövdenin tamamında kullanılabilir):

namespace NullableIntroduction;

public class SurveyResponse(int id)
{
    public int Id { get; } = id;
}

Katılımcılar oluşturun

Rastgele bir kimlikle yanıtlayıcılar oluşturan bir statik fabrika yöntemi ekleyin (türün yeni bir örneğini oluşturan ve döndüren bir static yöntem; oluşturucuyu doğrudan çağırmaya bir alternatiftir):

private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());

Bir anket yanıtı oluşturma

Ardından, anketi yanıtlayana soran yöntemi ekleyin. Yanıtları null atanabilir bir sözlükte depolayın; böylece türün kendisi, katılımcının yanıt vermemeyi tercih edebileceğini ifade eder:

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!";
    }
}

Alanı surveyResponses şeklindedir Dictionary<int, string>?. önce öğesini denetlemeden nullalana başvuruda bulunursanız, derleyici bir uyarı verir. AnswerSurvey içinde, derleyici new ifadesinden hemen sonra surveyResponses öğesinin null olmadığını izler; bu nedenle döngü gövdesi ek bir denetim gerektirmez.

Anket yanıtları kümesi oluşturma

SurveyRun üzerinde, katılım için yeterli sayıda onay alınana kadar yanıtlayıcıların bir listesini oluşturan bir yöntem ekleyin:

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);
    }
}

Bu respondents alan, List<SurveyResponse>?null anket çalıştırana kadardır.

PerformSurvey içinden Main çağırın:

surveyRun.PerformSurvey(50);

Anket sonuçlarını inceleme

Sonuçları raporlamak için, SurveyResponse ve SurveyRun içinden birkaç yardımcıyı dışa açın. SurveyResponse üzerinde, nullable sözlüğü işleyen ifade gövdeli üyeler ekleyin ({ ... } bloğu yerine tek bir ifadeyle ve => kullanılarak tanımlanan üyeler):

public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";

AnsweredSurvey, alanı null ile karşılaştırır. Answer, güvenli bir şekilde başvuruyu çözümlemek için ?.null koşullu işlecini (sol taraf null olduğunda özel durum fırlatmak yerine null olarak değerlendirilir) ve null olmayan bir geri dönüş sağlamak için ??null birleştirme işlecini (sol taraf null olduğunda sağ işleneni kullanır) kullanır. Yöntemin dönüş türü null atanamayan string olduğundan, çağıran kodun null denetimi yapmasına gerek yoktur.

SurveyRun üzerine, katılımcı ve soru listelerini sunan ifade gövdeli üyeler ekleyin:

public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];

respondents null olabilse de, AllParticipants null atanamayan bir sıra döndürür. ?? işleci, alan henüz doldurulmamışsa Enumerable.Empty<SurveyResponse>() yerine geçer. ?? yan tümcesini kaldırırsanız, derleyici null atanamayan bir dönüş türüne rağmen yöntemin null döndürebileceği konusunda uyarır.

Son olarak, raporu Main öğesinin altına yazın:

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");
    }
}

, participantveya surveyRun.Questionsiçin surveyRun.GetQuestion(i)null denetimine gerek olmadığına dikkat edin. Türler bu değerleri null atanamayan olarak bildirir, bu nedenle derleyici bunları döngü boyunca null olmayan olarak ele alır.

Uygulamayı çalıştırın:

dotnet run

Katılımcılar rastgele oluşturulduğundan çıktı her çalıştırmada farklıdır, ancak her satır ya bir katılımcının yanıtlarını verir ya da yanıtlamayı reddettiğini belirtir.

Sonuç

Tamamlanmış örnek, dotnet/samples deposunun csharp/NullableIntroduction klasöründedir. Null atanabilir ve null atanamayan türler arasında değişiklik yaparak denemeler yapın. Tasarımın eksik değerlere izin verdiği bir ? konumun kaldırılması, eksik değerin önemli olduğu her yere işaret eden derleyici uyarıları üretir.