Eigenschappen

Eigenschappen zijn eersteklas burgers in C#. De taal definieert syntaxis waarmee ontwikkelaars code kunnen schrijven die hun ontwerpintentie nauwkeurig uitdrukt.

Eigenschappen gedragen zich als velden wanneer ze worden geopend. In tegenstelling tot velden worden eigenschappen echter geïmplementeerd met accessors die de instructies definiëren die worden uitgevoerd wanneer een eigenschap wordt geopend of toegewezen.

Syntaxis van eigenschap

De syntaxis voor eigenschappen is een natuurlijke uitbreiding op velden. Een veld definieert een opslaglocatie:

public class Person
{
    public string? FirstName;

    // Omitted for brevity.
}

Een eigenschapsdefinitie bevat declaraties voor een get en set accessor die de waarde van die eigenschap ophaalt en toewijst:

public class Person
{
    public string? FirstName { get; set; }

    // Omitted for brevity.
}

De bovenstaande syntaxis is de syntaxis van de automatische eigenschap . De compiler genereert de opslaglocatie voor het veld waarop een back-up van de eigenschap wordt gemaakt. De compiler implementeert ook de hoofdtekst van de get en set accessors.

Soms moet u een eigenschap initialiseren op een andere waarde dan de standaardwaarde voor het type. C# schakelt dit in door een waarde in te stellen na de accolade sluiten voor de eigenschap. Mogelijk geeft u de voorkeur aan de oorspronkelijke waarde voor de FirstName eigenschap als de lege tekenreeks in plaats nullvan . U geeft dit op zoals hieronder wordt weergegeven:

public class Person
{
    public string FirstName { get; set; } = string.Empty;

    // Omitted for brevity.
}

Specifieke initialisatie is het handigst voor alleen-lezen eigenschappen, zoals verderop in dit artikel wordt weergegeven.

U kunt de opslag ook zelf definiëren, zoals hieronder wordt weergegeven:

public class Person
{
    public string? FirstName
    {
        get { return _firstName; }
        set { _firstName = value; }
    }
    private string? _firstName;

    // Omitted for brevity.
}

Wanneer een eigenschapimplementatie één expressie is, kunt u expressie-bodyied leden gebruiken voor de getter of setter:

public class Person
{
    public string? FirstName
    {
        get => _firstName;
        set => _firstName = value;
    }
    private string? _firstName;

    // Omitted for brevity.
}

Deze vereenvoudigde syntaxis wordt waar van toepassing in dit artikel gebruikt.

De bovenstaande eigenschapsdefinitie is een eigenschap lezen/schrijven. Let op het trefwoord value in de set accessor. De set accessor heeft altijd één parameter met de naam value. De get accessor moet een waarde retourneren die converteert naar het type eigenschap (string in dit voorbeeld).

Dat zijn de basisbeginselen van de syntaxis. Er zijn veel verschillende variaties die verschillende ontwerpidiomen ondersteunen. Laten we eens kijken en de syntaxisopties voor elk van deze opties bekijken.

Validatie

In de bovenstaande voorbeelden is een van de eenvoudigste gevallen van eigenschapsdefinitie getoond: een eigenschap voor lezen/schrijven zonder validatie. Door de gewenste code in de get en set accessors te schrijven, kunt u veel verschillende scenario's maken.

U kunt code schrijven in de set accessor om ervoor te zorgen dat de waarden die door een eigenschap worden vertegenwoordigd, altijd geldig zijn. Stel dat één regel voor de Person klasse is dat de naam geen lege spatie of spatie mag zijn. U schrijft dit als volgt:

public class Person
{
    public string? FirstName
    {
        get => _firstName;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("First name must not be blank");
            _firstName = value;
        }
    }
    private string? _firstName;

    // Omitted for brevity.
}

Het voorgaande voorbeeld kan worden vereenvoudigd met behulp van een throw expressie als onderdeel van de validatie van de eigenschapssetter:

public class Person
{
    public string? FirstName
    {
        get => _firstName;
        set => _firstName = (!string.IsNullOrWhiteSpace(value)) ? value : throw new ArgumentException("First name must not be blank");
    }
    private string? _firstName;

    // Omitted for brevity.
}

In het bovenstaande voorbeeld wordt de regel afgedwongen dat de voornaam niet leeg of wit mag zijn. Als een ontwikkelaar schrijft

hero.FirstName = "";

Die opdracht gooit een ArgumentException. Omdat een toegangsfunctie voor eigenschappensets een ongeldig retourtype moet hebben, meldt u fouten in de settoegangsfunctie door een uitzondering te genereren.

U kunt dezelfde syntaxis uitbreiden naar alles wat nodig is in uw scenario. U kunt de relaties tussen verschillende eigenschappen controleren of valideren op basis van externe voorwaarden. Geldige C#-instructies zijn geldig in een eigenschapstoegangsor.

Toegangsbeheer

Tot nu toe zijn alle eigenschapsdefinities die u hebt gezien lees-/schrijfeigenschappen met openbare toegangsrechten. Dat is niet de enige geldige toegankelijkheid voor eigenschappen. U kunt alleen-lezeneigenschappen maken of andere toegankelijkheid geven aan de set en accessors ophalen. Stel dat uw Person klasse alleen het wijzigen van de waarde van de FirstName eigenschap van andere methoden in die klasse moet inschakelen. U kunt de toegankelijkheid van de settoegangsfunctie private geven in plaats van public:

public class Person
{
    public string? FirstName { get; private set; }

    // Omitted for brevity.
}

FirstName De eigenschap kan nu worden geopend vanuit elke code, maar kan alleen worden toegewezen vanuit andere code in de Person klasse.

U kunt elke beperkende toegangsaanpassing toevoegen aan de set of get accessors. Elke toegangsaanpassing die u op de afzonderlijke toegangsfunctie plaatst, moet beperkter zijn dan de toegangsaanpassing in de eigenschapsdefinitie. Het bovenstaande is legaal omdat de FirstName eigenschap is public, maar de set accessor is private. U kunt een private eigenschap met een public accessor niet declareren. Eigenschapsdeclaraties kunnen ook worden gedeclareerd protected, internal, protected internalof zelfs private.

Het is ook legaal om de meer beperkende wijziging op de get toegangsfunctie te plaatsen. U kunt bijvoorbeeld een public eigenschap hebben, maar de get toegangsfunctie beperken tot private. Dat scenario wordt in de praktijk zelden uitgevoerd.

Alleen-lezen

U kunt ook wijzigingen in een eigenschap beperken, zodat deze alleen in een constructor kan worden ingesteld. U kunt de Person klasse als volgt wijzigen:

public class Person
{
    public Person(string firstName) => FirstName = firstName;

    public string FirstName { get; }

    // Omitted for brevity.
}

Alleen init

In het voorgaande voorbeeld moeten bellers de constructor gebruiken die de FirstName parameter bevat. Bellers kunnen geen object-initializers gebruiken om een waarde toe te wijzen aan de eigenschap. Als u initialisatieprogramma's wilt ondersteunen, kunt u de set accessor een init toegangsfunctie maken, zoals wordt weergegeven in de volgende code:

public class Person
{
    public Person() { }
    public Person(string firstName) => FirstName = firstName;

    public string? FirstName { get; init; }

    // Omitted for brevity.
}

In het voorgaande voorbeeld kan een aanroeper een Person aanroeper maken met behulp van de standaardconstructor, zelfs als die code de FirstName eigenschap niet instelt. Vanaf C# 11 kunt u vereisen dat bellers die eigenschap instellen:

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName) => FirstName = firstName;

    public required string FirstName { get; init; }

    // Omitted for brevity.
}

Met de voorgaande code worden twee toevoegingen aan de Person klasse uitgevoerd. Eerst bevat de FirstName eigenschapsdeclaratie de required wijzigingsfunctie. Dit betekent dat elke code die een nieuwe Person maakt, deze eigenschap moet instellen. Ten tweede heeft de constructor die een firstName parameter gebruikt het System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute kenmerk. Dit kenmerk informeert de compiler dat met deze constructor alle leden wordenrequired ingesteld.

Belangrijk

Verwar required niet met niet-nullable. Het is geldig om een required eigenschap in te stellen op null of default. Als het type niet null-compatibel is, zoals string in deze voorbeelden, geeft de compiler een waarschuwing.

Bellers moeten de constructor gebruiken met SetsRequiredMembers of de FirstName eigenschap instellen met behulp van een object-initialisatiefunctie, zoals wordt weergegeven in de volgende code:

var person = new VersionNinePoint2.Person("John");
person = new VersionNinePoint2.Person{ FirstName = "John"};
// Error CS9035: Required member `Person.FirstName` must be set:
//person = new VersionNinePoint2.Person();

Berekende eigenschappen

Een eigenschap hoeft niet alleen de waarde van een lidveld te retourneren. U kunt eigenschappen maken die een berekende waarde retourneren. Laten we het Person object uitbreiden om de volledige naam te retourneren, berekend door de voor- en achternamen samen te voegen:

public class Person
{
    public string? FirstName { get; set; }

    public string? LastName { get; set; }

    public string FullName { get { return $"{FirstName} {LastName}"; } }
}

In het bovenstaande voorbeeld wordt de functie tekenreeksinterpolatie gebruikt om de opgemaakte tekenreeks voor de volledige naam te maken.

U kunt ook een expressie-lichaamslid gebruiken, dat een meer beknopte manier biedt om de berekende FullName eigenschap te maken:

public class Person
{
    public string? FirstName { get; set; }

    public string? LastName { get; set; }

    public string FullName => $"{FirstName} {LastName}";
}

Expressieleden gebruiken de syntaxis van de lambda-expressie om methoden te definiëren die één expressie bevatten. Deze expressie retourneert hier de volledige naam voor het persoonsobject.

Geëvalueerde eigenschappen in cache

U kunt het concept van een berekende eigenschap combineren met opslag en een geëvalueerde eigenschap in de cache maken. U kunt bijvoorbeeld de FullName eigenschap bijwerken, zodat de tekenreeksopmaak alleen is opgetreden wanneer deze voor het eerst werd geopend:

public class Person
{
    public string? FirstName { get; set; }

    public string? LastName { get; set; }

    private string? _fullName;
    public string FullName
    {
        get
        {
            if (_fullName is null)
                _fullName = $"{FirstName} {LastName}";
            return _fullName;
        }
    }
}

De bovenstaande code bevat echter een bug. Als code de waarde van de FirstName of LastName eigenschap bijwerken, is het eerder geëvalueerde fullName veld ongeldig. U wijzigt de set accessors van de FirstName en LastName eigenschap zodat het fullName veld opnieuw wordt berekend:

public class Person
{
    private string? _firstName;
    public string? FirstName
    {
        get => _firstName;
        set
        {
            _firstName = value;
            _fullName = null;
        }
    }

    private string? _lastName;
    public string? LastName
    {
        get => _lastName;
        set
        {
            _lastName = value;
            _fullName = null;
        }
    }

    private string? _fullName;
    public string FullName
    {
        get
        {
            if (_fullName is null)
                _fullName = $"{FirstName} {LastName}";
            return _fullName;
        }
    }
}

Deze definitieve versie evalueert de FullName eigenschap alleen wanneer dat nodig is. Als de eerder berekende versie geldig is, wordt deze gebruikt. Als een andere statuswijziging de eerder berekende versie ongeldig maakt, wordt deze opnieuw berekend. Ontwikkelaars die deze klasse gebruiken, hoeven de details van de implementatie niet te weten. Geen van deze interne wijzigingen heeft invloed op het gebruik van het object Person. Dit is de belangrijkste reden voor het gebruik van eigenschappen om gegevensleden van een object beschikbaar te maken.

Kenmerken koppelen aan automatisch geïmplementeerde eigenschappen

Veldkenmerken kunnen worden gekoppeld aan het gegenereerde backingveld van de compiler in automatisch geïmplementeerde eigenschappen. Denk bijvoorbeeld aan een revisie van de Person klasse waarmee een unieke geheel getaleigenschap Id wordt toegevoegd. U schrijft de Id eigenschap met behulp van een automatisch geïmplementeerde eigenschap, maar uw ontwerp roept niet op om de Id eigenschap persistent te maken. De NonSerializedAttribute kan alleen worden gekoppeld aan velden, niet aan eigenschappen. U kunt het NonSerializedAttribute aan het backingveld voor de Id eigenschap koppelen met behulp van de field: aanduiding voor het kenmerk, zoals wordt weergegeven in het volgende voorbeeld:

public class Person
{
    public string? FirstName { get; set; }

    public string? LastName { get; set; }

    [field:NonSerialized]
    public int Id { get; set; }

    public string FullName => $"{FirstName} {LastName}";
}

Deze techniek werkt voor elk kenmerk dat u aan het backingveld op de automatisch geïmplementeerde eigenschap koppelt.

INotifyPropertyChanged implementeren

Een laatste scenario waarin u code in een eigenschapstoegangsor moet schrijven, is door de INotifyPropertyChanged interface te ondersteunen die wordt gebruikt om clients van gegevensbindingen op de hoogte te stellen dat een waarde is gewijzigd. Wanneer de waarde van een eigenschap wordt gewijzigd, wordt de INotifyPropertyChanged.PropertyChanged gebeurtenis gegenereerd om de wijziging aan te geven. De gegevensbindingsbibliotheken werken op hun beurt weergave-elementen bij op basis van die wijziging. De onderstaande code laat zien hoe u zou implementeren INotifyPropertyChanged voor de FirstName eigenschap van deze persoonsklasse.

public class Person : INotifyPropertyChanged
{
    public string? FirstName
    {
        get => _firstName;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("First name must not be blank");
            if (value != _firstName)
            {
                _firstName = value;
                PropertyChanged?.Invoke(this,
                    new PropertyChangedEventArgs(nameof(FirstName)));
            }
        }
    }
    private string? _firstName;

    public event PropertyChangedEventHandler? PropertyChanged;
}

De ?. operator wordt de voorwaardelijke operator null genoemd. Er wordt gecontroleerd op een null-verwijzing voordat de rechterkant van de operator wordt geëvalueerd. Het eindresultaat is dat als er geen abonnees van de PropertyChanged gebeurtenis zijn, de code voor het genereren van de gebeurtenis niet wordt uitgevoerd. Het zou een NullReferenceException zonder deze controle in dat geval gooien. Zie events voor meer informatie. In dit voorbeeld wordt ook de nieuwe nameof operator gebruikt om te converteren van het eigenschapsnaamsymbool naar de tekstweergave. Met dit nameof hulpprogramma kunt u fouten verminderen waarbij u de naam van de eigenschap verkeerd hebt getypt.

Nogmaals, de implementatie INotifyPropertyChanged is een voorbeeld van een geval waarin u code in uw accessors kunt schrijven om de scenario's te ondersteunen die u nodig hebt.

Samenvatting

Eigenschappen zijn een vorm van slimme velden in een klasse of object. Van buiten het object worden ze weergegeven als velden in het object. Eigenschappen kunnen echter worden geïmplementeerd met behulp van het volledige palet van C#-functionaliteit. U kunt validatie, verschillende toegankelijkheid, luie evaluatie of eventuele vereisten bieden die uw scenario's nodig hebben.