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


Oktatóanyag: A tervezési szándék egyértelműbb kifejezése null értékű és nem null értékű hivatkozástípusokkal

Null értékű hivatkozástípusok ugyanúgy kiegészítik a hivatkozástípusokat, mint a null értékű értéktípusok. Egy változót null értékű hivatkozástípusnak deklarálhat, ha hozzáfűz egy ? a típushoz. A string? például null értékű stringjelö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íg másoknak hiányzik egy érték.

Ebben az oktatóanyagban a következőket sajátíthatja el:

  • Nullálható és nem nullálható referenciatípusok beépítése a tervekbe.
  • Engedélyezze a null értékű hivatkozástípus-ellenőrzéseket a teljes kódbázisban.
  • Írjon olyan kódot, amelyben a fordító kikényszeríti ezeket a tervezési döntéseket.
  • Használja a null értékű referencia funkciót saját terveiben.

Előfeltételek

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.

Opcionális referenciatípusok beépítése a tervekbe

Ebben az oktatóanyagban egy felmérést modellező könyvtá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. Ebben az esetben előfordulhat, hogy a válaszok null.

A mintához írt kód kifejezi ezt a szándékot, és a fordító biztosítja, hogy a szándék megvalósuljon.

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 dotnet new consolehasználatával. 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ázható annotációs környezetben legyen lefordítva. Nyissa meg a .csproj fájlt, és adjon hozzá egy Nullable elemet a PropertyGroup elemhez. Állítsa enableértékre. A C# 11/ .NET 7 előtt létrehozott projektekben a null értékű referenciatípusok funkciót kell választania. 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éssel, előfordulhat, hogy nem tükrözi pontosan az eredeti tervezési szándékot:

<Nullable>enable</Nullable>

Az alkalmazás típusainak megtervezése

Ehhez a felmérési alkalmazáshoz létre kell hoznia az alábbi osztályokat:

  • Egy osztály, amely a kérdések listáját modellozza.
  • Egy osztály, amely a felméréshez felvett személyek listáját modellozza.
  • Egy osztály, amely egy felmérést készítő személy válaszait modellozza.

Ezek a típusok null értékű és nem null értékű hivatkozástípusokat is használnak annak kifejezésére, hogy mely tagokra van szükség, és mely tagok választhatók. A null értékű referenciatípusok egyértelműen kifejezik a tervezés szándékát.

  • 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. Szeretné nyomon követni a megkeresett személyeket, még azokat a válaszadókat is, akik visszautasították a részvételt.
  • Egy kérdésre adott válasz null értékű lehet. A válaszadók elutasíthatnak néhány vagy az összes kérdés megválaszolását.

Lehet, hogy annyira hozzászokott a nullát is megengedő hivatkozástípusok használatához, hogy esetleg figyelmen kívül hagy más lehetőségeket, amikor nem null értékű példányokat deklarálhatna.

  • A kérdések gyűjteménye nem lehet nullértékű.
  • A válaszadók gyűjteménye nem lehet null értékeket tartalmazó.

A kód megírása során láthatja, hogy egy nem nullát elfogadó hivatkozási típus alapértelmezettkénti beállítása a hivatkozásokhoz elkerüli azokat a gyakori hibákat, amelyek null értékű hivatkozásokhoz vezethetnek NullReferenceException. Az oktatóanyag egyik tanulsága, hogy döntéseket hozott 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 buildelt 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 ál-véletlenszerű halmazát hozza létre.
  3. Kapcsolatba lép a válaszadókkal, amíg a befejezett felmérések száma nem éri el a kitűzött célt.
  4. Fontos statisztikákat ír ki a felmérésre adott válaszokról.

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

Az első megírt kód létrehozza a felmérést. Osztályokat ír 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ípussal rendelkezik, 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 public SurveyQuestion osztályt:

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

A fordító minden referenciatípusú változó deklarációját nem nullázható hivatkozási típusként értelmezi az engedélyezett nullálható annotációs környezetben. Az első figyelmeztetést úgy tekintheti meg, hogy tulajdonságokat ad hozzá a kérdés szövegéhez és a kérdés típusához, ahogyan az a következő kódban is látható:

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

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

Mivel nem inicializáltad a QuestionText, a fordító figyelmeztetést ad arról, hogy egy nem-null értékű tulajdonság nincs inicializálva. A terv megkívánja, hogy a kérdésszöveg ne legyen null értékű, ezért hozzáad egy konstruktort, amely inicializálja a kérdésszöveget és a 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 argumentuma is egy nem-null referenciatípus, így a fordító nem ad ki figyelmeztetéseket.

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

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 ellenőrzések, mert a fordító segít kikényszeríteni a null értéktől mentes szerződést: A változót nem null értékűnek nyilvánította. Bár a fordító figyelmeztet a lehetséges null hozzárendelésekre, a futásidő során a null értékek továbbra is előfordulhatnak. Nyilvános API-k esetén érdemes lehet argumentumérvényesítést hozzáadni a nem null értékű referenciatípusokhoz is, mivel előfordulhat, hogy az ügyfélkód nem rendelkezik engedélyezve null értékű hivatkozástípusokkal, vagy szándékosan null értéket ad át.

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

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 az egész projekt egy engedélyezett nullázható annotációs környezetben van, figyelmeztetések jelennek meg, amikor olyan metódusnak adja át null, amely nem nullázható hivatkozástípust vár. Próbálja ki az alábbi sor hozzáadásával a Main-hoz:

surveyRun.AddQuestion(QuestionType.Text, default);

Válaszadók létrehozása és válaszok leké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 kis feladatot is magában foglal:

  1. Hozzon létre egy metódust, amely válaszadó objektumokat hoz létre. Ezek az objektumok a felmérés kitöltésére felkért személyeket jelölik.
  2. Logikát hozhat létre a válaszadóknak feltett kérdések szimulálásához, és válaszokat gyűjthet, vagy észreveheti, hogy a válaszadó nem válaszolt.
  3. Ismételje meg, amíg elegendő válaszadó nem válaszol a felmérésre.

Szüksége van 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 Id tulajdonságot é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 static metódust, amellyel új résztvevőket hozhat létre 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 résztvevők válaszait generálja a felmérésben szereplő kérdésekre. 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á, adjon vissza egy hiányzó (vagy null) választ.
  2. Tegye fel az egyes kérdéseket, és rögzítse a választ. Előfordulhat, hogy minden válasz hiányzik (vagy null).

Adja hozzá a következő kódot a 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árterülete egy Dictionary<int, string>?, amely azt jelzi, hogy null értékű lehet. Az új nyelvi funkcióval deklarálhatja a tervezési szándékot, mind a fordítónak, mind a kód későbbi olvasóinak. Ha valaha dereferálja a surveyResponses, anélkül, hogy először ellenőrizné a null értékét, fordító figyelmeztetést ad. Nem kap figyelmeztetést a AnswerSurvey metódusban, mert a fordító meg tudja állapítani, hogy a surveyResponses változó nem null értékű volt az előző kódban.

A null hiányzó válaszokhoz való használata kiemeli a null értékű referenciatí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 a cél annak biztosítása, hogy az ön által írt kód kifejezze a terv szándékát. A hiányzó értékek a kódban való kifejezéshez szükséges fogalmak. A null érték egyértelmű módja a hiányzó értékek kifejezésének. Az összes null érték eltávolításának megkísérlése a hiányzó értékek nullkifejezésének más módon történő definiálásához vezet.

Ezután meg kell írnia a PerformSurvey metódust a SurveyRun osztályba. 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 válaszadóknak. Figyelje meg, hogy a válaszadókat addig adják hozzá, amíg elegendő hozzájárulást nem kapnak.

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ési válaszok vizsgálata

Az utolsó lépés a felmérés eredményeinek megjelenítése. Kódot ad hozzá számos ön által írt osztályhoz. Ez a kód a null értékű és a nem null értékű hivatkozástípusok megkülönböztetésének értékét mutatja be. Először adja hozzá a következő két kifejezéssel testesített tagot a SurveyResponse osztályhoz:

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

Mivel surveyResponses null értékű hivatkozástípus, 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ázható karakterláncot ad vissza, ezért a hiányzó válasz esetét a null-egyesítő operátorral kell fedeznünk.

Ezután adja hozzá ezt a három kifejezéssel testesített tagot a 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 eltávolítja a ??-át és az azt követő üres karakterláncot, a fordító figyelmezteti önt, hogy a metódus visszaadhatja az null-et. A visszatérési aláírás egy 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");
    }
}

Ebben a kódban nincs szükség null ellenőrzésre, mert úgy tervezte meg a mögöttes interfészeket, hogy azok mindegyike nem null értékű hivatkozástípusokat adjon vissza. A fordító statikus elemzése segít ezeknek a tervezési szerződéseknek a betartásában.

A kód lekérése

A kész oktatóanyag kódját a minták tárházában a csharp/NullableIntroduction mappában szerezheti be.

Kísérletezzen a típusdeklarációk null értékű és nem null értékű hivatkozástípusok közötti módosításával. Lássa, hogyan generál ez különböző figyelmeztetéseket annak érdekében, hogy véletlenül ne hivatkozzon ki egy null-t.

Következő lépések

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