Recordtypen maken

Records zijn typen die gebruikmaken van gelijkheid op basis van waarden. C# 10 voegt recordstructs toe, zodat u records kunt definiëren als waardetypen. Twee variabelen van een recordtype zijn gelijk als de recordtypedefinities identiek zijn en als voor elk veld de waarden in beide records gelijk zijn. Twee variabelen van een klassetype zijn gelijk als de objecten waarnaar wordt verwezen hetzelfde klassetype zijn en de variabelen verwijzen naar hetzelfde object. Gelijkheid op basis van waarde impliceert andere mogelijkheden die u waarschijnlijk in recordtypen wilt gebruiken. De compiler genereert veel van deze leden wanneer u een record in plaats van een class. De compiler genereert dezelfde methoden voor record struct typen.

In deze zelfstudie leert u het volgende:

  • Bepaal of u de record wijzigingsfunctie aan een class type toevoegt.
  • Recordtypen en positionele recordtypen declareren.
  • Vervang uw methoden voor door compiler gegenereerde methoden in records.

Vereisten

U moet uw computer instellen om .NET 6 of hoger uit te voeren, inclusief de C# 10 of hoger compiler. De C# 10-compiler is beschikbaar vanaf Visual Studio 2022 of de .NET 6 SDK.

Kenmerken van records

U definieert een record door een type met het record trefwoord te declareren, een class of struct declaratie te wijzigen. U kunt desgewenst het class trefwoord weglaten om een record class. Een record volgt op waarde gebaseerde gelijkheidssemantiek. Om waardesemantiek af te dwingen, genereert de compiler verschillende methoden voor uw recordtype (zowel voor record class typen als record struct typen):

Records bieden ook een onderdrukking van Object.ToString(). De compilersynthetiseert methoden voor het weergeven van records met behulp van Object.ToString(). U verkent deze leden tijdens het schrijven van de code voor deze zelfstudie. Records ondersteunen with expressies om niet-destructieve mutatie van records mogelijk te maken.

U kunt positionele records ook declareren met behulp van een beknoptere syntaxis. De compiler synthetiseert meer methoden voor u wanneer u positionele records declareert:

  • Een primaire constructor waarvan de parameters overeenkomen met de positionele parameters in de recorddeclaratie.
  • Openbare eigenschappen voor elke parameter van een primaire constructor. Deze eigenschappen zijn alleen init-only voor record class typen en readonly record struct typen. Voor record struct typen zijn ze lezen/schrijven.
  • Een Deconstruct methode voor het extraheren van eigenschappen uit de record.

Temperatuurgegevens bouwen

Gegevens en statistieken zijn een van de scenario's waarin u records wilt gebruiken. Voor deze zelfstudie bouwt u een toepassing die dagen berekent voor verschillende toepassingen. Gradendagen zijn een meting van warmte (of gebrek aan warmte) gedurende een periode van dagen, weken of maanden. Gradendagen volgen en voorspellen energiegebruik. Meer warme dagen betekent meer airconditioning en meer koudere dagen betekent meer ovengebruik. Gradendagen helpen bij het beheren van plantenpopulaties en correleren met de plantengroei naarmate de seizoenen veranderen. Gradendagen helpen dierenmigraties bij te houden voor soorten die reizen om aan het klimaat te voldoen.

De formule is gebaseerd op de gemiddelde temperatuur op een bepaalde dag en een basislijntemperatuur. Als u dagen in de loop van de tijd wilt berekenen, hebt u de hoge en lage temperatuur elke dag nodig gedurende een bepaalde periode. Laten we beginnen met het maken van een nieuwe toepassing. Maak een nieuwe consoletoepassing. Maak een nieuw recordtype in een nieuw bestand met de naam DailyTemperature.cs:

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

Met de voorgaande code wordt een positionele record gedefinieerd. De DailyTemperature record is een readonly record struct, omdat u deze niet van plan bent over te nemen en deze moet onveranderbaar zijn. De HighTemp eigenschappen zijn LowTempalleen init-eigenschappen, wat betekent dat ze kunnen worden ingesteld in de constructor of met behulp van een initialisatiefunctie voor eigenschappen. Als u wilt dat de positionele parameters lezen/schrijven zijn, declareert u een record struct in plaats van een readonly record struct. Het DailyTemperature type heeft ook een primaire constructor met twee parameters die overeenkomen met de twee eigenschappen. U gebruikt de primaire constructor om een DailyTemperature record te initialiseren. Met de volgende code worden verschillende DailyTemperature records gemaakt en geïnitialiseerd. De eerste gebruikt benoemde parameters om de HighTemp en LowTempte verduidelijken. De resterende initializers gebruiken positionele parameters om de HighTemp en 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) 
];

U kunt uw eigen eigenschappen of methoden toevoegen aan records, inclusief positionele records. U moet de gemiddelde temperatuur voor elke dag berekenen. U kunt die eigenschap toevoegen aan de DailyTemperature record:

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

Laten we ervoor zorgen dat u deze gegevens kunt gebruiken. Voeg de volgende code toe aan uw Main methode:

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

Voer uw toepassing uit en u ziet uitvoer die lijkt op de volgende weergave (verschillende rijen verwijderd voor ruimte):

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 }

De voorgaande code toont de uitvoer van de onderdrukking van ToString de synthetiseren door de compiler. Als u liever andere tekst gebruikt, kunt u uw eigen versie schrijven ToString , zodat de compiler geen versie voor u kan synthetiseren.

Aantal dagen berekenen

Als u gradendagen wilt berekenen, neemt u het verschil op van een basislijntemperatuur en de gemiddelde temperatuur op een bepaalde dag. Als u de warmte in de loop van de tijd wilt meten, negeert u dagen waarin de gemiddelde temperatuur onder de basislijn ligt. Als u koude in de loop van de tijd wilt meten, negeert u dagen waarbij de gemiddelde temperatuur boven de basislijn ligt. De Vs gebruikt bijvoorbeeld 65F als basis voor zowel verwarmings- als koeldagen. Dat is de temperatuur waar geen verwarming of koeling nodig is. Als een dag een gemiddelde temperatuur van 70F heeft, is die dag vijf koeldagen en nul verwarming graden dagen. Als de gemiddelde temperatuur daarentegen 55F is, is die dag 10 verwarmingsgraden en 0 koeldagen.

U kunt deze formules uitdrukken als een kleine hiërarchie van recordtypen: een abstract dagtype en twee concrete typen voor verwarmingsdagen en koeldagen. Deze typen kunnen ook positionele records zijn. Ze nemen een basislijntemperatuur en een reeks dagelijkse temperatuurrecords als argumenten voor de primaire constructor:

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

De abstracte DegreeDays record is de gedeelde basisklasse voor zowel de als CoolingDegreeDays de HeatingDegreeDays records. De primaire constructordeclaraties voor de afgeleide records laten zien hoe u initialisatie van basisrecords beheert. Uw afgeleide record declareert parameters voor alle parameters in de primaire constructor van de basisrecord. De basisrecord declareert en initialiseert deze eigenschappen. De afgeleide record verbergt ze niet, maar maakt en initialiseert alleen eigenschappen voor parameters die niet zijn gedeclareerd in de basisrecord. In dit voorbeeld voegen de afgeleide records geen nieuwe primaire constructorparameters toe. Test uw code door de volgende code toe te voegen aan uw Main methode:

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

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

U krijgt uitvoer zoals in de volgende weergave:

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

Gesynthetiseerde compilermethoden definiëren

Uw code berekent het juiste aantal dagen voor verwarming en koeling gedurende die periode. Maar in dit voorbeeld ziet u waarom u mogelijk enkele van de gesynthetiseerde methoden voor records wilt vervangen. U kunt uw eigen versie van een van de compiler-gesynthetiseerde methoden declareren in een recordtype, behalve de kloonmethode. De kloonmethode heeft een door compiler gegenereerde naam en u kunt geen andere implementatie opgeven. Deze gesynthetiseerde methoden omvatten een kopieerconstructor, de leden van de System.IEquatable<T> interface, gelijkheids- en ongelijkheidstests, en GetHashCode(). Voor dit doel gaat u synthetiseren PrintMembers. U kunt ook uw eigen ToStringdeclareren, maar PrintMembers biedt een betere optie voor overnamescenario's. Als u uw eigen versie van een gesynthetiseerde methode wilt opgeven, moet de handtekening overeenkomen met de gesynthetiseerde methode.

Het TempRecords element in de console-uitvoer is niet nuttig. Het type wordt weergegeven, maar niets anders. U kunt dit gedrag wijzigen door uw eigen implementatie van de gesynthetiseerde PrintMembers methode te bieden. De handtekening is afhankelijk van modifiers die zijn toegepast op de record declaratie:

  • Als een recordtype sealed, of een record struct, de handtekening is private bool PrintMembers(StringBuilder builder);
  • Als een recordtype niet sealed is en afgeleid is van object (dat wil zeggen dat het geen basisrecord declareert), is de handtekening protected virtual bool PrintMembers(StringBuilder builder);
  • Als een recordtype niet sealed is en is afgeleid van een andere record, is de handtekening protected override bool PrintMembers(StringBuilder builder);

Deze regels zijn het gemakkelijkst te begrijpen door het doel van PrintMembers. PrintMembers voegt informatie over elke eigenschap in een recordtype toe aan een tekenreeks. Voor het contract zijn basisrecords vereist om hun leden aan de weergave toe te voegen en ervan uit te gaan dat afgeleide leden hun leden toevoegen. Elk recordtype synthetiseert een ToString onderdrukking die lijkt op het volgende voorbeeld voor HeatingDegreeDays:

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

U declareert een PrintMembers methode in de DegreeDays record die het type verzameling niet afdrukt:

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

De handtekening declareert een virtual protected methode die overeenkomt met de versie van de compiler. Maak u geen zorgen als u de toegangsrechten verkeerd krijgt; de taal dwingt de juiste handtekening af. Als u de juiste wijzigingsfuncties voor een gesynthetiseerde methode vergeet, geeft de compiler waarschuwingen of fouten op die u helpen de juiste handtekening te krijgen.

In C# 10 en hoger kunt u de ToString methode declareren als sealed in een recordtype. Hiermee voorkomt u dat afgeleide records een nieuwe implementatie bieden. Afgeleide records bevatten nog steeds de PrintMembers onderdrukking. U zou het ToString runtimetype van de record niet willen weergeven. In het voorgaande voorbeeld verliest u de informatie over waar de record dagen voor verwarming of koeling meet.

Niet-destructieve mutatie

De gesynthetiseerde leden in een positionele recordklasse wijzigen de status van de record niet. Het doel is dat u gemakkelijker onveranderbare records kunt maken. Houd er rekening mee dat u een readonly record struct onveranderbare recordstruct maakt. Kijk nogmaals naar de voorgaande declaraties voor HeatingDegreeDays en CoolingDegreeDays. De leden die zijn toegevoegd, voeren berekeningen uit op de waarden voor de record, maar muteer de status niet. Positionele records maken het gemakkelijker voor u om onveranderbare referentietypen te maken.

Het maken van onveranderbare referentietypen betekent dat u een niet-destructieve mutatie wilt gebruiken. U maakt nieuwe recordexemplaren die vergelijkbaar zijn met bestaande recordexemplaren met behulp van with expressies. Deze expressies zijn een kopieerconstructie met extra toewijzingen waarmee de kopie wordt gewijzigd. Het resultaat is een nieuw recordexemplaren waarin elke eigenschap is gekopieerd uit de bestaande record en eventueel gewijzigd. De oorspronkelijke record is ongewijzigd.

Laten we een aantal functies toevoegen aan uw programma waarmee expressies worden gedemonstreert with . Laten we eerst een nieuwe record maken voor het berekenen van toenemende graden dagen met behulp van dezelfde gegevens. Groeiende gradendagen gebruiken doorgaans 41F als basislijn en meet temperaturen boven de basislijn. Als u dezelfde gegevens wilt gebruiken, kunt u een nieuwe record maken die vergelijkbaar is met de coolingDegreeDays, maar met een andere basistemperatuur:

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

U kunt het aantal berekende graden vergelijken met de getallen die zijn gegenereerd met een hogere basislijntemperatuur. Records zijn referentietypen en deze kopieën zijn ondiepe kopieën. De matrix voor de gegevens wordt niet gekopieerd, maar beide records verwijzen naar dezelfde gegevens. Dat is een voordeel in een ander scenario. Voor groeiende graden dagen is het handig om het totaal voor de afgelopen vijf dagen bij te houden. U kunt nieuwe records met verschillende brongegevens maken met behulp van with expressies. Met de volgende code wordt een verzameling van deze accumulaties gebouwd en worden vervolgens de waarden weergegeven:

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

U kunt ook expressies gebruiken with om kopieën van records te maken. Geef geen eigenschappen op tussen de accolades voor de with expressie. Dit betekent dat u een kopie maakt en geen eigenschappen wijzigt:

var growingDegreeDaysCopy = growingDegreeDays with { };

Voer de voltooide toepassing uit om de resultaten te bekijken.

Samenvatting

In deze zelfstudie zijn verschillende aspecten van records getoond. Records bieden beknopte syntaxis voor typen waarbij het fundamentele gebruik gegevens opslaat. Voor objectgeoriënteerde klassen definieert het fundamentele gebruik verantwoordelijkheden. Deze zelfstudie is gericht op positionele records, waar u een beknopte syntaxis kunt gebruiken om de eigenschappen voor een record te declareren. De compiler synthetiseert verschillende leden van de record voor het kopiëren en vergelijken van records. U kunt alle andere leden toevoegen die u nodig hebt voor uw recordtypen. U kunt onveranderbare recordtypen maken die weten dat geen van de door de compiler gegenereerde leden de status muteert. En with expressies maken het gemakkelijk om non-destructieve mutatie te ondersteunen.

Records voegen een andere manier toe om typen te definiëren. U gebruikt class definities om objectgeoriënteerde hiërarchieën te maken die zich richten op de verantwoordelijkheden en het gedrag van objecten. U maakt struct typen voor gegevensstructuren die gegevens opslaan en klein genoeg zijn om efficiënt te kopiëren. U maakt record typen wanneer u gelijkheid en vergelijking op basis van waarden wilt, geen waarden wilt kopiëren en referentievariabelen wilt gebruiken. U maakt record struct typen wanneer u de functies van records wilt gebruiken voor een type dat klein genoeg is om efficiënt te kopiëren.

Meer informatie over records vindt u in het naslagartikel voor de C#-taal voor het recordtype en de voorgestelde recordtypespecificatie en recordstructspecificatie.