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


Rekordtípusok létrehozása

A rekordok olyan típusok, amelyek értékalapú egyenlőséget használnak. A rekordokat referenciatípusként vagy értéktípusként is definiálhatja. Egy rekordtípus két változója egyenlő, ha a rekordtípus-definíciók azonosak, és ha minden mező esetében a két rekord értéke egyenlő. Egy osztálytípus két változója egyenlő, ha a hivatkozott objektumok azonos osztálytípusúak, és a változók ugyanarra az objektumra hivatkoznak. Az értékalapú egyenlőség olyan egyéb képességeket is magában foglal, amelyeket valószínűleg a rekordtípusokban szeretne használni. A fordító sok ilyen tagot generál, amikor Ön egy record-t deklarál egy classhelyett. A fordító ugyanazokat a metódusokat hozza létre record struct típusok esetében.

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

  • Döntse el, hogy hozzáadja-e a record módosítót a class típushoz.
  • Deklarálja a rekordtípusokat és a pozíciórekordtípusokat.
  • Cserélje le a rekordokban a fordító által generált metódusokat a saját módszereire.

Előfeltételek

Be kell állítania a gépet a .NET 6-os vagy újabb verziójának futtatására. A C#-fordító a Visual Studio 2022 vagy a .NET SDKkeretén belül érhető el.

A rekordok jellemzői

Egy rekordot úgy definiálhat, hogy deklarál egy típust a record kulcsszóval, módosít egy class vagy struct deklarációt. Ha szeretné, kihagyhatja a class kulcsszót egy record classlétrehozásához. Egy rekord az értékalapú egyenlőség szemantikáját követi. Az értékszemantika kényszerítéséhez a fordító számos metódust hoz létre a rekordtípushoz (record class és record struct típusok esetén is):

A rekordok a Object.ToString()felülbírálását is biztosítják. A fordító a rekordok Object.ToString()használatával történő megjelenítésének módszereit szintetizálja. Ezeket a tagokat az oktatóanyag kódjának megírása során ismerheti meg. A rekordok támogatják a with kifejezéseket a rekordok nem destruktív mutációjának engedélyezéséhez.

Tömörebb szintaxissal pozíciórekordokat is deklarálhat. A fordító több metódust szintetizál, amikor pozíciós rekordokat deklarál:

  • Elsődleges konstruktor, amelynek paraméterei megegyeznek a rekorddeklarációban szereplő pozícióparaméterekkel.
  • Nyilvános tulajdonságok egy elsődleges konstruktor minden paraméteréhez. Ezek a tulajdonságok csak init-onlyrecord class és readonly record struct típusok esetében. record struct típusok olvasható-írható.
  • A rekord tulajdonságainak kinyerésére Deconstruct metódus.

Hőmérsékletadatok összeállítása

Az adatok és statisztikák azon forgatókönyvek közé tartoznak, amelyekben rekordokat szeretne használni. Ebben az oktatóanyagban egy olyan alkalmazást hoz létre, amely foknapokat számít ki különböző célokra . Foknapok a hőmennyiség mérőszáma (vagy hiánya) egy adott időszakra, legyen az napok, hetek vagy hónapok. A foknapok nyomon követik és előrejelzik az energiafelhasználást. A melegebb napok több légkondicionálót jelentenek, a hidegebb napok pedig több kemencehasználatot jelentenek. A foknapok segítenek a növénypopulációk kezelésében, és korrelálnak a növények növekedésével, ahogy az évszakok változnak. A foknapok segítenek nyomon követni az éghajlathoz igazodó fajok állatmigrálását.

A képlet az adott napon mért középhőmérsékleten és az alaphőmérsékleten alapul. A foknapok számításához szükség lesz a napi legmagasabb és legalacsonyabb hőmérsékletekre egy adott időszakban. Kezdjük egy új alkalmazás létrehozásával. Hozzon létre egy új konzolalkalmazást. Hozzon létre egy új rekordtípust egy "DailyTemperature.cs" nevű új fájlban:

public readonly record struct DailyTemperature(double HighTemp, double LowTemp);

Az előző kód egy pozíciórekordot határoz meg. A DailyTemperature rekord egy readonly record struct, mert nem szándékozik tőle örökölni, és annak módosíthatatlannak kell lennie. A HighTemp és a LowTemp tulajdonságok csaktulajdonságokat inicializáló tulajdonságok, ami azt jelenti, hogy a konstruktorban vagy egy tulajdonság inicializáló használatával állíthatók be. Ha azt szeretné, hogy a pozícióparaméterek írhatók és olvashatók legyenek, akkor record struct-t deklaráljon readonly record structhelyett. A DailyTemperature típus egy elsődleges konstruktor is rendelkezik, amely két paraméterrel rendelkezik, amelyek megfelelnek a két tulajdonságnak. Az elsődleges konstruktor használatával inicializálhat egy DailyTemperature rekordot. Az alábbi kód több DailyTemperature rekordot hoz létre és inicializál. Az első névvel ellátott paraméterekkel tisztázza a HighTemp és a LowTemp. A többi inicializáló pozícióparaméterekkel inicializálja a HighTemp és LowTemp:

private static DailyTemperature[] data = [
    new DailyTemperature(HighTemp: 57, LowTemp: 30), 
    new DailyTemperature(60, 35),
    new DailyTemperature(63, 33),
    new DailyTemperature(68, 29),
    new DailyTemperature(72, 47),
    new DailyTemperature(75, 55),
    new DailyTemperature(77, 55),
    new DailyTemperature(72, 58),
    new DailyTemperature(70, 47),
    new DailyTemperature(77, 59),
    new DailyTemperature(85, 65),
    new DailyTemperature(87, 65),
    new DailyTemperature(85, 72),
    new DailyTemperature(83, 68),
    new DailyTemperature(77, 65),
    new DailyTemperature(72, 58),
    new DailyTemperature(77, 55),
    new DailyTemperature(76, 53),
    new DailyTemperature(80, 60),
    new DailyTemperature(85, 66) 
];

Saját tulajdonságokat vagy metódusokat adhat hozzá a rekordokhoz, beleértve a pozíciórekordokat is. Minden napra ki kell számítania a középhőmérsékletet. Ezt a tulajdonságot hozzáadhatja a DailyTemperature rekordhoz:

public readonly record struct DailyTemperature(double HighTemp, double LowTemp)
{
    public double Mean => (HighTemp + LowTemp) / 2.0;
}

Győződjön meg arról, hogy használhatja ezeket az adatokat. Adja hozzá a következő kódot a Main metódushoz:

foreach (var item in data)
    Console.WriteLine(item);

Futtassa az alkalmazást, és a következőhöz hasonló kimenet jelenik meg (több sor el van távolítva a szóközhöz):

DailyTemperature { HighTemp = 57, LowTemp = 30, Mean = 43.5 }
DailyTemperature { HighTemp = 60, LowTemp = 35, Mean = 47.5 }


DailyTemperature { HighTemp = 80, LowTemp = 60, Mean = 70 }
DailyTemperature { HighTemp = 85, LowTemp = 66, Mean = 75.5 }

Az előzőleg ismertetett kód a fordító által szintetizált ToString felülbírálásának kimenetét mutatja. Ha más szöveget szeretne, megírhatja a ToString saját verzióját, amely megakadályozza, hogy a fordító szintetizáljon egy verziót.

Foknapok kiszámítása

A fokos napok kiszámításakor az alaphőmérséklet és az adott napon mért középhőmérséklet közötti különbséget kell megállapítani. A hő időalapú méréséhez minden olyan napot elvet, amikor a középhőmérséklet az alapérték alatt van. A hideg időbeli méréséhez elvet minden olyan napot, amikor a középhőmérséklet meghaladja az alapértéket. Az Usa például 65 F-et használ a fűtési és hűtési fok napjainak alapjaként. Ez az a hőmérséklet, ahol nincs szükség fűtésre vagy hűtésre. Ha egy nap középhőmérséklete 70 Fahrenheit-fok, akkor ez a nap öt hűtési foknapi és nulla fűtési foknapi. Ezzel szemben, ha a középhőmérséklet 55 F, akkor az a nap 10 fűtési foknap és 0 hűtési foknap.

Ezeket a képleteket a rekordtípusok kis hierarchiájaként lehet kifejezni: egy absztrakt foknap típust és két konkrét típust a fűtési foknapok és a hűtési foknapok számára. Ezek a típusok pozíciórekordok is lehetnek. Az alaphőmérsékletet és a napi hőmérsékleti rekordok sorozatát veszik fel argumentumként az elsődleges konstruktor számára:

public abstract record DegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords);

public sealed record HeatingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)
    : DegreeDays(BaseTemperature, TempRecords)
{
    public double DegreeDays => TempRecords.Where(s => s.Mean < BaseTemperature).Sum(s => BaseTemperature - s.Mean);
}

public sealed record CoolingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)
    : DegreeDays(BaseTemperature, TempRecords)
{
    public double DegreeDays => TempRecords.Where(s => s.Mean > BaseTemperature).Sum(s => s.Mean - BaseTemperature);
}

Az absztrakt DegreeDays rekord a HeatingDegreeDays és CoolingDegreeDays rekordok közös alaposztálya. A származtatott rekordok elsődleges konstruktor-deklarációi bemutatják az alaprekord inicializálásának kezelését. A származtatott rekord az alaprekord elsődleges konstruktorának összes paraméteréhez deklarálja a paramétereket. Az alaprekord deklarálja és inicializálja ezeket a tulajdonságokat. A származtatott rekord nem rejti el őket, csak az alaprekordban nem deklarált paraméterek tulajdonságait hozza létre és inicializálja. Ebben a példában a származtatott rekordok nem adnak hozzá új elsődleges konstruktorparamétereket. A kód teszteléséhez adja hozzá a következő kódot a Main metódushoz:

var heatingDegreeDays = new HeatingDegreeDays(65, data);
Console.WriteLine(heatingDegreeDays);

var coolingDegreeDays = new CoolingDegreeDays(65, data);
Console.WriteLine(coolingDegreeDays);

A kimenet az alábbihoz hasonlóan jelenik meg:

HeatingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 85 }
CoolingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 71.5 }

Fordító-szintetizált metódusok definiálása

A kód kiszámítja a fűtési és hűtési fok napjainak megfelelő számát az adott időszakban. Ez a példa azonban azt mutatja be, hogy miért érdemes lecserélni néhány szintetizált metódust a rekordokra. A fordító által szintetizált metódusok bármelyikének saját verzióját rekordtípusban deklarálhatja a klónozási módszer kivételével. A klónozási módszer fordító által generált névvel rendelkezik, és nem adhat meg másik implementációt. Ezek a szintetizált módszerek közé tartozik a másolási konstruktor, a System.IEquatable<T> felület tagjai, az egyenlőségi és egyenlőtlenségi tesztek, valamint a GetHashCode(). Ebből a célból szintetizálja a(z) PrintMembers-t. Saját ToString-t is deklarálhat, de az öröklési forgatókönyvekhez a PrintMembers jobb lehetőséget kínál. A szintetizált metódus saját verziójának megadásához az aláírásnak meg kell egyeznie a szintetizált metódussal.

A konzolkimenet TempRecords eleme nem hasznos. Megjeleníti a típust, de semmi mást. Ezt a viselkedést a szintetizált PrintMembers metódus saját implementációjának biztosításával módosíthatja. Az aláírás a record deklarációra alkalmazott módosítóktól függ:

  • Ha egy rekordtípus sealedvagy record struct, az aláírás private bool PrintMembers(StringBuilder builder);
  • Ha egy rekordtípus nem sealed, és object származik (vagyis nem deklarál alaprekordot), az aláírás protected virtual bool PrintMembers(StringBuilder builder);
  • Ha egy rekordtípus nem sealed, és egy másik rekordból származik, az aláírás protected override bool PrintMembers(StringBuilder builder);

Ezeket a szabályokat legkönnyebben a PrintMemberscéljának megértésével lehet megérteni. PrintMembers egy rekordtípus egyes tulajdonságairól ad hozzá adatokat egy sztringhez. A szerződés megköveteli, hogy az alap rekordok hozzáadják tagjaikat a kijelzőhöz, és feltételezi, hogy a származtatott rekordok is hozzáadják saját tagjaikat. Minden rekordtípus szintetizál egy ToString felülírást, amely hasonlóan néz ki az alábbi HeatingDegreeDayspéldához.

public override string ToString()
{
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.Append("HeatingDegreeDays");
    stringBuilder.Append(" { ");
    if (PrintMembers(stringBuilder))
    {
        stringBuilder.Append(" ");
    }
    stringBuilder.Append("}");
    return stringBuilder.ToString();
}

Deklarál egy PrintMembers metódust a DegreeDays rekordban, amely nem nyomtatja ki a gyűjtemény típusát:

protected virtual bool PrintMembers(StringBuilder stringBuilder)
{
    stringBuilder.Append($"BaseTemperature = {BaseTemperature}");
    return true;
}

Az aláírás egy virtual protected metódust deklarál, amely megfelel a fordító verziójának. Ne aggódjon, ha rosszul kapja meg a tartozékokat; a nyelv kikényszeríti a megfelelő aláírást. Ha elfelejti a szintetizált metódusok megfelelő módosítóit, a fordító figyelmeztetéseket vagy hibákat ad ki, amelyek segítenek a megfelelő aláírás beszerzésében.

A ToString metódust rekordtípusban sealed deklarálhatja. Ez megakadályozza, hogy a származtatott rekordok új implementációt biztosítsanak. A származtatott rekordok továbbra is tartalmazzák a PrintMembers felülírást. Ha nem akarná, hogy a rekord futásidejű típusa megjelenjen, lezárná ToString. Az előző példában elveszítené az adatokat arról, hogy a rekord hol mérte a fűtési vagy hűtési fok napjait.

Nem romboló mutáció

A pozíciórekordosztály szintetizált tagjai nem módosítják a rekord állapotát. A cél az, hogy könnyebben hozzon létre nem módosítható rekordokat. Ne feledje, hogy a readonly record struct deklarálásával egy nem módosítható rekordstruktúrát hoz létre. Tekintse meg újra az HeatingDegreeDays és CoolingDegreeDayselőző deklarációit. A hozzáadott elemek számításokat végeznek a rekord értékein, de nem változtatják meg az állapotot. A helymeghatározó rekordok megkönnyítik a nem módosítható referenciatípusok létrehozását.

Nem módosítható referenciatípusok létrehozása azt jelenti, hogy nempusztító módosítást szeretne használni. A meglévő rekordpéldányokhoz hasonló új rekordpéldányokat hozhat létre with kifejezésekhasználatával. Ezek a kifejezések olyan másolási szerkezetek, amelyek további hozzárendelésekkel módosítják a másolatot. Az eredmény egy új rekordpéldány, ahol minden tulajdonság ki lett másolva a meglévő rekordból, és opcionálisan módosult. Az eredeti rekord változatlan.

Adjunk hozzá néhány olyan funkciót a programhoz, amelyek bemutatják with kifejezéseket. Először hozzunk létre egy új rekordot a növekvő fokok napjainak kiszámításához ugyanazokkal az adatokkal. Növekvő fok napjai általában 41 F-et használnak alapkonfigurációként, és az alapkonfiguráció feletti hőmérsékletet mérik. Ugyanezen adatok használatához létrehozhat egy új rekordot, amely hasonló a coolingDegreeDays, de más alaphőmérséklettel:

// Growing degree days measure warming to determine plant growing rates
var growingDegreeDays = coolingDegreeDays with { BaseTemperature = 41 };
Console.WriteLine(growingDegreeDays);

Összehasonlíthatja a kiszámított fokok számát a magasabb alaphőmérséklettel létrehozott számokkal. Ne feledje, hogy a rekordok referenciatípusok, és ezek a másolatok felszíni másolatok. Az adatok tömbje nem másolódik, de mindkét rekord ugyanarra az adatra hivatkozik. Ez a tény előnyt jelent egy másik forgatókönyvben. A növekvő foknapok esetében hasznos nyomon követni az előző öt nap összesített értékét. Új rekordokat hozhat létre különböző forrásadatokkal with kifejezések használatával. Az alábbi kód összeállítja a felhalmozások gyűjteményét, majd megjeleníti az értékeket:

// showing moving accumulation of 5 days using range syntax
List<CoolingDegreeDays> movingAccumulation = new();
int rangeSize = (data.Length > 5) ? 5 : data.Length;
for (int start = 0; start < data.Length - rangeSize; start++)
{
    var fiveDayTotal = growingDegreeDays with { TempRecords = data[start..(start + rangeSize)] };
    movingAccumulation.Add(fiveDayTotal);
}
Console.WriteLine();
Console.WriteLine("Total degree days in the last five days");
foreach(var item in movingAccumulation)
{
    Console.WriteLine(item);
}

A rekordok másolatainak létrehozásához with kifejezéseket is használhat. Ne adjon meg tulajdonságokat a with kifejezés zárójelei között. Ez azt jelenti, hogy hozzon létre egy másolatot, és ne módosítsa a tulajdonságokat:

var growingDegreeDaysCopy = growingDegreeDays with { };

Futtassa a kész alkalmazást az eredmények megtekintéséhez.

Összefoglalás

Ez az oktatóanyag a rekordok számos aspektusát mutatta be. A rekordok tömör szintaxist biztosítanak az olyan típusokhoz, ahol az alapvető használat az adatok tárolása. Az objektumorientált osztályok esetében az alapvető használat a felelősségek meghatározása. Ez az oktatóanyag pozíciórekordokraösszpontosított, ahol tömör szintaxissal deklarálhatja egy rekord tulajdonságait. A fordító a rekord több tagját szintetizálja a rekordok másolásához és összehasonlításához. A rekordtípusaihoz minden szükséges további tagot hozzáadhat. Nem módosítható rekordtípusokat hozhat létre, tudva, hogy a fordító által létrehozott tagok egyike sem mutálná az állapotot. with kifejezések megkönnyítik a nem romboló mutáció támogatását.

A rekordok egy másik módszert is hozzáadnak a típusok definiálásához. A class definíciókkal objektumorientált hierarchiákat hozhat létre, amelyek az objektumok felelősségére és viselkedésére összpontosítanak. Olyan struct típusú adatstruktúrákat hozhat létre, amelyek adatokat tárolnak, és elég kicsik a hatékony másoláshoz. Akkor hoz létre record típusokat, ha értékalapú egyenlőséget és összehasonlítást szeretne, nem szeretne értékeket másolni, és referenciaváltozókat szeretne használni. record struct típusok akkor hozhatók létre, ha egy olyan típus rekordjainak funkcióit szeretné használni, amelyek elég kicsik a hatékony másoláshoz.

A rekordokról a C# nyelvi referenciacikkében tájékozódhat a rekordtípus, valamint a javasolt rekordtípus-specifikációról és rekordstruktúra-specifikációról.