Megosztás a következőn keresztül:


Oktatóanyag: A tervezési szándék egyértelműbb kifejezése nullable és nem nullable referenciatípusokkal

A nullable reference types complement reference types ugyanúgy, mint a nullable value types complement value types . A változók nullázható hivatkozási típussá való deklarálásához ? fűzze hozzá a változót a típushoz. Például egy string? null értékű stringértéket jelöl. Ezekkel az új típusokkal pontosabban kifejezheti a tervezési szándékot: egyes változóknak mindig értéknek kell lenniük, másoknak pedig hiányzik egy érték.

Ebből az oktatóanyagból az alábbiakat sajátíthatja el:

  • Nullable és non-nullable referenciatípusok beépítése a tervekbe
  • Nullable reference type checks (Nullable reference type checks) engedélyezése a kódban.
  • Írjon olyan kódot, amelyben a fordító kikényszeríti ezeket a tervezési döntéseket.
  • A nullable reference funkció használata a saját terveiben

Előfeltételek

Be kell állítania a gépet a .NET futtatására, beleértve a C#-fordítót is. A C# fordító a Visual Studio 2022-ben vagy a .NET SDK-val érhető el.

Ez az oktatóanyag feltételezi, hogy ismeri a C# és a .NET használatát, beleértve a Visual Studiót vagy a .NET CLI-t is.

Nullable reference types in your designs

Ebben az oktatóanyagban egy felmérést futtató kódtárat fog létrehozni. A kód null értékű hivatkozástípusokat és nem nullértékű hivatkozástípusokat is használ a valós fogalmak megjelenítéséhez. A felmérésre vonatkozó kérdések soha nem lehetnek null értékűek. Előfordulhat, hogy egy válaszadó inkább nem válaszol egy kérdésre. A válaszok ebben az esetben lehetnek null .

A mintához megírt kód kifejezi ezt a szándékot, és a fordító kényszeríti ezt a szándékot.

Az alkalmazás létrehozása és null értékű hivatkozástípusok engedélyezése

Hozzon létre egy új konzolalkalmazást a Visual Studióban vagy a parancssorból a használatával dotnet new console. Nevezze el az alkalmazást NullableIntroduction. Az alkalmazás létrehozása után meg kell adnia, hogy a teljes projekt egy engedélyezett null értékű jegyzetkörnyezetben legyen lefordítva. Nyissa meg a .csproj fájlt, és adjon hozzá egy Nullable elemet az PropertyGroup elemhez. Állítsa az értékét enable értékűre. A C# 11-nél korábbi projektekben a nullázható referenciatípusok funkció mellett kell döntenie. Ennek az az oka, hogy a funkció bekapcsolása után a meglévő referenciaváltozó-deklarációk nem null értékű referenciatípusokká válnak. Bár ez a döntés segít megtalálni azokat a problémákat, amelyekben a meglévő kód nem rendelkezik megfelelő null-ellenőrzésekkel, előfordulhat, hogy nem tükrözi pontosan az eredeti tervezési szándékot:

<Nullable>enable</Nullable>

A .NET 6 előtt az új projektek nem tartalmazzák az Nullable elemet. A .NET 6-tól kezdődően az új projektek tartalmazzák a <Nullable>enable</Nullable> projektfájl elemét.

Az alkalmazás típusainak megtervezése

Ehhez a felmérési alkalmazáshoz több osztályt kell létrehozni:

  • Egy osztály, amely modelleli a kérdések listáját.
  • Egy osztály, amely modelleli a felméréshez kapcsolatba lépő személyek listáját.
  • Egy osztály, amely egy felmérést végző személy válaszait modellezi.

Ezek a típusok null értékű és nem nullértékű hivatkozástípusokat is használnak annak megállapításához, hogy mely tagokra van szükség, és mely tagok választhatók. A nullértékű hivatkozástípusok egyértelműen közlik ezt a tervezési szándékot:

  • A felmérés részét képező kérdések soha nem lehetnek null értékűek: Nincs értelme üres kérdést feltenni.
  • A válaszadók soha nem lehetnek null értékűek. Érdemes nyomon követni azokat a személyeket, akikkel felvette a kapcsolatot, még a részvételt elutasító válaszadókat is.
  • Egy kérdésre adott válasz null értékű lehet. A válaszadók elutasíthatják néhány vagy az összes kérdés megválaszolását.

Ha C# nyelven programozott, akkor lehet, hogy annyira megszokta, hogy olyan típusú hivatkozásokat használ, amelyek lehetővé teszik null azokat az értékeket, amelyeket esetleg kihagyott más lehetőségekkel a nem null értékű példányok deklarálásához:

  • A kérdések gyűjteményének nem lehet null értékű.
  • A válaszadók gyűjteményének nem lehet null értékű.

A kód megírása során látni fogja, hogy a nem null értékű hivatkozási típusok a hivatkozások alapértelmezett típusaként elkerülik azokat a gyakori hibákat, amelyek s-hez vezethetnek NullReferenceException. Az oktatóanyag egyik tanulsága, hogy ön hozott döntéseket arról, hogy mely változók lehetnek vagy nem.null A nyelv nem adott meg szintaxist ezeknek a döntéseknek a kifejezéséhez. Most már igen.

A létrehozni kívánt alkalmazás a következő lépéseket hajtja végre:

  1. Létrehoz egy felmérést, és kérdéseket ad hozzá.
  2. A felmérés válaszadóinak pszeudo-véletlenszerű halmazát hozza létre.
  3. A válaszadók mindaddig kapcsolatba lépnek a válaszadókkal, amíg a befejezett felmérés mérete el nem éri a célszámot.
  4. Fontos statisztikákat ír ki a felmérésre adott válaszokról.

A felmérés létrehozása nullable és nem nullable referenciatípusokkal

Az első megírni kívánt kód létrehozza a felmérést. Osztályokat fog írni egy felmérési kérdés modellezéséhez és egy felmérés futtatásához. A felmérés háromféle kérdést tartalmazhat, amelyeket a válasz formátuma különböztet meg: Igen/Nem válaszok, számválaszok és szöveges válaszok. Hozzon létre egy osztályt public SurveyQuestion :

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

A fordító minden referenciatípus változódeklarációját nem null értékű referenciatípusként értelmezi a kódhoz egy engedélyezett nullértékű jegyzetkörnyezetben. Az első figyelmeztetést úgy tekintheti meg, ha tulajdonságokat ad hozzá a kérdés szövegéhez és a kérdés típusához, ahogy az a következő kódban látható:

namespace NullableIntroduction
{
    public enum QuestionType
    {
        YesNo,
        Number,
        Text
    }

    public class SurveyQuestion
    {
        public string QuestionText { get; }
        public QuestionType TypeOfQuestion { get; }
    }
}

Mivel még nem inicializálta az inicializálást QuestionText, a fordító figyelmeztetést ad ki arról, hogy a nem null értékű tulajdonság nincs inicializálva. A tervezéshez a kérdés szövegének nem null értékűnek kell lennie, ezért egy konstruktort is fel kell vennie az inicializáláshoz, valamint az QuestionType értéket is. A kész osztálydefiníció a következő kódhoz hasonlóan néz ki:

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

A konstruktor hozzáadása eltávolítja a figyelmeztetést. A konstruktor argumentum szintén nem null értékű hivatkozástípus, így a fordító nem ad ki figyelmeztetéseket.

Ezután hozzon létre egy nevű SurveyRunosztálytpublic. Ez az osztály az objektumok és metódusok listáját SurveyQuestion tartalmazza a felméréshez kérdések hozzáadásához, az alábbi kódban látható módon:

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

A korábbiakhoz hasonlóan a listaobjektumot nem null értékűre kell inicializálnia, vagy a fordító figyelmeztetést ad ki. A második túlterhelésben AddQuestion nincsenek null értékű ellenőrzések, mert nincs rájuk szükség: A változót nem null értékűnek nyilvánította. Az értéke nem lehet null.

Váltson a Program.cs fájlra a szerkesztőben, és cserélje le a tartalmát Main a következő kódsorra:

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

Mivel a teljes projekt engedélyezve van a null értékű széljegyzetek környezetében, figyelmeztetések jelennek meg, amikor olyan metódusnak ad át null , amely nem null értékű hivatkozástípust vár. Próbálja ki a következő sor hozzáadásával:Main

surveyRun.AddQuestion(QuestionType.Text, default);

Válaszadók létrehozása és válaszok kérése a felmérésre

Ezután írja meg azt a kódot, amely válaszokat hoz létre a felmérésre. Ez a folyamat több kisebb feladatot is magában foglal:

  1. Hozzon létre egy metódust, amely válaszadó objektumokat hoz létre. Ezek a felmérés kitöltésére felkért személyeket jelölik.
  2. A logikát úgy hozhatja létre, hogy szimulálja a válaszadóknak feltett kérdéseket, és válaszokat gyűjtsen, vagy észrevehesse, hogy a válaszadó nem válaszolt.
  3. Ismételje meg a műveletet, amíg elegendő válaszadó nem válaszolt a felmérésre.

Szüksége lesz egy osztályra a felmérésre adott válasz megjelenítéséhez, ezért most adja hozzá. Null értékű támogatás engedélyezése. Adjon hozzá egy tulajdonságot Id és egy konstruktort, amely inicializálja azt az alábbi kódban látható módon:

namespace NullableIntroduction
{
    public class SurveyResponse
    {
        public int Id { get; }

        public SurveyResponse(int id) => Id = id;
    }
}

Ezután adjon hozzá egy metódust static , amellyel új résztvevőket hozhat létre egy véletlenszerű azonosító létrehozásával:

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

Ennek az osztálynak a fő feladata, hogy a felmérésben szereplő kérdésekre adott válaszokat generálja a résztvevőknek. Ennek a felelősségnek néhány lépése van:

  1. Kérjen részvételt a felmérésben. Ha a személy nem járul hozzá, hiányzó (vagy null) választ ad vissza.
  2. Tegyen fel minden kérdést, és jegyezze fel a választ. Előfordulhat, hogy minden válasz hiányzik (vagy null).

Adja hozzá a következő kódot az SurveyResponse osztályhoz:

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

A felmérésre adott válaszok tárolója egy Dictionary<int, string>?, amely azt jelzi, hogy null értékű lehet. Az új nyelvi funkcióval deklarálja a tervezési szándékot mind a fordítónak, mind a kód későbbi olvasóinak. Ha az érték ellenőrzése nélkül halasztja el a null dedukciótsurveyResponses, egy fordítói figyelmeztetést kap. Nem kap figyelmeztetést a AnswerSurvey metódusban, mert a fordító meg tudja állapítani, hogy a surveyResponses változó a fenti nem null értékre lett állítva.

A hiányzó válaszok használata null kiemeli a nullértékű hivatkozástípusok használatának egyik kulcspontját: nem az a cél, hogy az összes null értéket eltávolítsa a programból. Ehelyett az a cél, hogy a megírt kód kifejezze a terv szándékát. A hiányzó értékek a kódban való kifejezéshez szükségesek. Az null érték egyértelmű módot ad a hiányzó értékek kifejezésére. Az összes null érték eltávolításának megkísérlése csak a hiányzó értékek kifejezésének más módon történő definiálásához vezet a nélkül null.

Ezután meg kell írnia a metódust PerformSurvey a SurveyRun osztályban. Adja hozzá a következő kódot a SurveyRun osztályhoz:

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

Itt is a null értékű List<SurveyResponse>? válasz azt jelzi, hogy a válasz null értékű lehet. Ez azt jelzi, hogy a felmérést még nem adták meg a válaszadóknak. Figyelje meg, hogy a válaszadók mindaddig hozzáadódnak, amíg elegendően nem járultak hozzá.

A felmérés futtatásának utolsó lépése egy hívás hozzáadása a felmérés végrehajtásához a Main metódus végén:

surveyRun.PerformSurvey(50);

Felmérésre adott válaszok vizsgálata

Az utolsó lépés a felmérés eredményeinek megjelenítése. Kódot fog hozzáadni számos megírt osztályhoz. Ez a kód bemutatja a nullable és a nem nullable referenciatípusok megkülönböztetésének értékét. Először adja hozzá a következő két kifejezéstestű tagot az SurveyResponse osztályhoz:

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

Mivel surveyResponses a hivatkozás null értékű, a hivatkozás megszüntetése előtt null értékű ellenőrzésekre van szükség. A Answer metódus egy nem null értékű sztringet ad vissza, ezért a null-coalescing operátorral kell lefednünk a hiányzó válasz esetét.

Ezután adja hozzá ezt a három kifejezéstestű tagot az SurveyRun osztályhoz:

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

A AllParticipants tagnak figyelembe kell vennie, hogy a respondents változó lehet null, de a visszatérési érték nem lehet null. Ha a kifejezést a és az ?? azt követő üres sorozat eltávolításával módosítja, a fordító figyelmezteti, hogy a metódus visszatérhet null , a visszatérési aláírása pedig nem null értékű típust ad vissza.

Végül adja hozzá a következő hurkot a Main metódus aljá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");
    }
}

Nincs szükség ellenőrzésekre null ebben a kódban, mert úgy tervezte meg a mögöttes interfészeket, hogy azok mindegyike ne null értékű hivatkozástípusokat ad vissza.

A kód letöltése

A kész oktatóanyag kódját a csharp/NullableIntroduction mappában található mintaadattárból szerezheti be.

Kísérletezzen a típusdeklarációk nullable és nem nullable referenciatípusok közötti módosításával. Tekintse meg, hogy ez hogyan generál különböző figyelmeztetéseket, hogy biztosan ne halasztsa el véletlenül a -t null.

Következő lépések

Megtudhatja, hogyan használhat null értékű hivatkozástípust az Entity Framework használatakor: