Vlastnosti

Vlastnosti jsou občany první třídy v jazyce C#. Jazyk definuje syntaxi, která vývojářům umožňuje psát kód, který přesně vyjadřuje záměr návrhu.

Vlastnosti se chovají jako pole při přístupu. Na rozdíl od polí jsou však vlastnosti implementovány s přístupovými objekty, které definují příkazy spuštěné při přístupu nebo přiřazení vlastnosti.

Syntaxe vlastností

Syntaxe vlastností je přirozené rozšíření polí. Pole definuje umístění úložiště:

public class Person
{
    public string? FirstName;

    // Omitted for brevity.
}

Definice vlastnosti obsahuje deklarace pro get objekt a set přístup, který načte a přiřadí hodnotu této vlastnosti:

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

    // Omitted for brevity.
}

Syntaxe uvedená výše je syntaxe automatické vlastnosti . Kompilátor vygeneruje umístění úložiště pro pole, které zálohuje vlastnost. Kompilátor také implementuje tělo get a set přístupové objekty.

Někdy je nutné inicializovat vlastnost na jinou hodnotu než výchozí pro její typ. Jazyk C# to povolí nastavením hodnoty za pravou závorkou vlastnosti. Místo toho můžete dát přednost počáteční hodnotě vlastnosti FirstName jako prázdný řetězec, nikoli null. To byste zadali, jak je znázorněno níže:

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

    // Omitted for brevity.
}

Specifická inicializace je nejužitečnější pro vlastnosti jen pro čtení, jak uvidíte dále v tomto článku.

Úložiště můžete definovat také sami, jak je znázorněno níže:

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

    // Omitted for brevity.
}

Pokud je implementace vlastnosti jedním výrazem, můžete pro getter nebo setter použít členy s body výrazu:

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

    // Omitted for brevity.
}

Tato zjednodušená syntaxe se použije tam, kde je to možné v tomto článku.

Výše uvedená definice vlastnosti je vlastnost pro čtení i zápis. Všimněte si klíčového slova value v přístupovém objektu set. Příslušenství set má vždy jeden parametr s názvem value. Přistupovací get objekt musí vracet hodnotu, která je konvertibilní na typ vlastnosti (string v tomto příkladu).

To jsou základy syntaxe. Existuje mnoho různých variant, které podporují různé designové idiomy. Pojďme se podívat a seznámit se s možnostmi syntaxe pro každou z nich.

Ověřování

Výše uvedené příklady ukázaly jeden z nejjednodušších případů definice vlastnosti: vlastnost pro čtení i zápis bez ověření. Napsáním požadovaného kódu v objektech get a set přístupových objektech můžete vytvořit mnoho různých scénářů.

Do přístupového objektu set můžete napsat kód, který zajistí, že hodnoty reprezentované vlastností jsou vždy platné. Předpokládejme například, že jedno pravidlo pro Person třídu znamená, že název nemůže být prázdný nebo prázdný. To byste napsali takto:

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.
}

Předchozí příklad lze zjednodušit pomocí výrazu throw jako součást ověřování setter vlastnosti:

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.
}

Výše uvedený příklad vynucuje pravidlo, které nesmí být prázdné nebo prázdné. Pokud vývojář napíše

hero.FirstName = "";

Toto zadání vyvolá výjimku ArgumentException. Vzhledem k tomu, že přístupové objekty sady vlastností musí mít návratový typ void, hlásíte chyby v přístupovém objektu sady vyvoláním výjimky.

Stejnou syntaxi můžete ve vašem scénáři rozšířit na cokoli potřebného. Můžete zkontrolovat vztahy mezi různými vlastnostmi nebo ověřit vůči jakýmkoli externím podmínkám. Všechny platné příkazy jazyka C# jsou platné v přístupovém objektu vlastnosti.

Řízení přístupu

Do tohoto okamžiku jsou všechny definice vlastností, které jste viděli, vlastnosti čtení a zápisu s veřejnými přístupovými objekty. To není jediná platná přístupnost pro vlastnosti. Můžete vytvořit vlastnosti jen pro čtení nebo nastavit různé přístupnosti pro sadu a získat přístupové objekty. Předpokládejme, že vaše Person třída by měla povolit pouze změnu hodnoty FirstName vlastnosti z jiných metod v této třídě. Přístup k sadě private můžete nastavit tak, aby byl přístup k dispozici místo public:

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

    // Omitted for brevity.
}

Nyní lze k FirstName vlastnosti přistupovat z libovolného kódu, ale lze ji přiřadit pouze z jiného Person kódu ve třídě.

K sadě nebo získání přístupových objektů můžete přidat libovolný modifikátor omezujícího přístupu. Každý modifikátor přístupu, který umístíte na jednotlivé příslušenství, musí být omezenější než modifikátor přístupu v definici vlastnosti. Výše uvedené je právní, protože vlastnost FirstName je public, ale sada příslušenství je private. Nelze deklarovat private vlastnost s příslušenstvím public . Deklarace vlastností lze také deklarovat protected, internal, , protected internalnebo dokonce private.

Je také legální umístit více omezující modifikátor na get příslušenství. Můžete public mít například vlastnost, ale omezit přístup k privateobjektu get . Tento scénář se v praxi provádí jen zřídka.

Jen pro čtení

Můžete také omezit úpravy vlastnosti tak, aby ji bylo možné nastavit pouze v konstruktoru. Třídu můžete upravit Person následujícím způsobem:

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

    public string FirstName { get; }

    // Omitted for brevity.
}

Pouze inicializační

Předchozí příklad vyžaduje, aby volající používali konstruktor, který obsahuje FirstName parametr. Volající nemůžou k přiřazení hodnoty vlastnosti použít inicializátory objektů. Chcete-li podporovat inicializátory, můžete příslušenství nastavit set jako přístup init , jak je znázorněno v následujícím kódu:

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

    public string? FirstName { get; init; }

    // Omitted for brevity.
}

Předchozí příklad umožňuje volajícímu Person vytvořit pomocí výchozího konstruktoru, i když tento kód nenastaví FirstName vlastnost. Počínaje jazykem C# 11 můžete vyžadovat , aby volající nastavili tuto vlastnost:

public class Person
{
    public Person() { }

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

    public required string FirstName { get; init; }

    // Omitted for brevity.
}

Předchozí kód vytvoří do třídy dva doplňky Person . Nejprve deklarace FirstName vlastnosti zahrnuje required modifikátor. To znamená, že jakýkoli kód, který vytvoří novou Person , musí nastavit tuto vlastnost. Za druhé, konstruktor, který přebírá firstName parametr má System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute atribut. Tento atribut informuje kompilátor, že tento konstruktor nastaví všechnyrequired členy.

Důležité

Nezaměňujte required s nenulovou hodnotou. Je platné nastavit required vlastnost na null hodnotu nebo default. Pokud je typ nenulový, například string v těchto příkladech, kompilátor vydá upozornění.

Volající musí buď použít konstruktor s SetsRequiredMembers nebo nastavit FirstName vlastnost pomocí inicializátoru objektů, jak je znázorněno v následujícím kódu:

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

Vypočítané vlastnosti

Vlastnost nemusí jednoduše vracet hodnotu pole člena. Můžete vytvořit vlastnosti, které vrátí vypočítanou hodnotu. Pojďme objekt rozšířit Person tak, aby vrátil celý název vypočítaný zřetězením křestní jména a příjmení:

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

    public string? LastName { get; set; }

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

Výše uvedený příklad používá funkci interpolace řetězců k vytvoření formátovaného řetězce pro celý název.

Můžete také použít člena s výrazem, který poskytuje stručnější způsob, jak vytvořit vypočítanou FullName vlastnost:

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

    public string? LastName { get; set; }

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

Členové typu výrazy používají syntaxi výrazu lambda k definování metod, které obsahují jeden výraz. Tento výraz vrátí celé jméno objektu osoby.

Vyhodnocené vlastnosti uložené v mezipaměti

Koncept počítané vlastnosti můžete kombinovat s úložištěm a vytvořit vyhodnocenou vlastnost uloženou v mezipaměti. Můžete například aktualizovat FullName vlastnost tak, aby formátování řetězce proběhlo jenom při prvním přístupu:

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

Výše uvedený kód ale obsahuje chybu. Pokud kód aktualizuje hodnotu vlastnosti FirstName nebo hodnoty LastName , je dříve vyhodnocené fullName pole neplatné. set Upravíte přístupové objekty FirstName a LastName vlastnost tak, aby fullName se pole vypočítalo znovu:

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

Tato konečná verze vyhodnocuje FullName vlastnost pouze v případě potřeby. Pokud je dříve počítaná verze platná, použije se. Pokud změna jiného stavu zneplatní dříve počítanou verzi, přepočítá se. Vývojáři, kteří používají tuto třídu, nemusí znát podrobnosti implementace. Žádný z těchto vnitřních změn nemá vliv na použití objektu Person. To je klíčový důvod použití vlastností k zveřejnění datových členů objektu.

Připojení atributů k automaticky implementovaným vlastnostem

Atributy pole lze připojit k kompilátoru vygenerovanému záložnímu poli v automaticky implementovaných vlastnostech. Představte si například revizi Person třídy, která přidává jedinečnou celočíselnou Id vlastnost. Vlastnost napíšete Id pomocí automaticky implementované vlastnosti, ale váš návrh nevolá zachování Id vlastnosti. Lze NonSerializedAttribute je připojit pouze k polím, nikoli vlastnostem. Pomocí specifikátoru atributu NonSerializedAttribute můžete připojit k záložnímu poli vlastnosti Idfield: , jak je znázorněno v následujícím příkladu:

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

Tato technika funguje pro všechny atributy, které připojíte k backing pole u automaticky implementované vlastnosti.

Implementace INotifyPropertyChanged

Posledním scénářem, kdy potřebujete napsat kód v přístupovém objektu vlastnosti, je podporovat INotifyPropertyChanged rozhraní používané k upozorňovat klienty datové vazby, že se hodnota změnila. Když se změní hodnota vlastnosti, objekt vyvolá INotifyPropertyChanged.PropertyChanged událost označující změnu. Knihovny datových vazeb zase aktualizují prvky zobrazení na základě této změny. Následující kód ukazuje, jak byste implementovali INotifyPropertyChanged vlastnost FirstName této třídy osob.

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

Operátor ?. se nazývá podmíněný operátor null. Před vyhodnocením pravé strany operátoru zkontroluje nulový odkaz. Konečným výsledkem je, že pokud k události nejsou žádní odběratelé PropertyChanged , kód pro vyvolání události se nespustí. V takovém případě by to vyhodilo NullReferenceException bez této kontroly. Další informace najdete na webu events. Tento příklad také používá nový nameof operátor k převodu ze symbolu názvu vlastnosti na jeho textové vyjádření. Použití nameof může snížit chyby, kdy jste nesprávně zadali název vlastnosti.

Implementace je příkladem případu, INotifyPropertyChanged ve kterém můžete napsat kód ve svých přístupových objektech pro podporu potřebných scénářů.

Sečtením

Vlastnosti jsou formou inteligentních polí ve třídě nebo objektu. Zvnějšku objektu se zobrazí jako pole v objektu. Vlastnosti je však možné implementovat pomocí celé palety funkcí jazyka C#. Můžete zadat ověřování, různé přístupnosti, opožděné vyhodnocení nebo jakékoli požadavky, které vaše scénáře potřebují.