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 class
helyett. 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 aclass
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 class
lé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 Object.Equals(Object)felülbírálása.
- Egy olyan virtuális
Equals
metódus, amelynek paramétere a rekordtípus. - A Object.GetHashCode()felülbírálása.
- A
operator ==
ésoperator !=
metódusai. - A rekordtípusok implementálják System.IEquatable<T>.
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
- 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-only
record class
ésreadonly 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 struct
helyett. 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
sealed
vagyrecord struct
, az aláírásprivate bool PrintMembers(StringBuilder builder);
- Ha egy rekordtípus nem
sealed
, ésobject
származik (vagyis nem deklarál alaprekordot), az aláírásprotected virtual bool PrintMembers(StringBuilder builder);
- Ha egy rekordtípus nem
sealed
, és egy másik rekordból származik, az aláírásprotected override bool PrintMembers(StringBuilder builder);
Ezeket a szabályokat legkönnyebben a PrintMembers
cé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 HeatingDegreeDays
pé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 CoolingDegreeDays
elő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.