Sdílet prostřednictvím


15 tříd

15.1 Obecné

Třída je datová struktura, která může obsahovat datové členy (konstanty a pole), členy funkce (metody, vlastnosti, události, indexery, operátory, konstruktory instancí, finalizátory a statické konstruktory) a vnořené typy. Typy tříd podporují dědičnost, mechanismus, kdy odvozená třída může rozšířit a specializovat základní třídu.

15.2 Deklarace třídy

15.2.1 Obecné

Class_declaration je type_declaration (§14.7), která deklaruje novou třídu.

class_declaration
    : attributes? class_modifier* 'partial'? 'class' identifier
        type_parameter_list? class_base? type_parameter_constraints_clause*
        class_body ';'?
    ;

Class_declaration se skládá z volitelné sady atributů (§22), po níž následuje volitelná sada class_modifiers (§15.2.2), za kterým následuje volitelný partial modifikátor (§15.2.7), za kterým následuje klíčové slovo class a identifikátor, který pojmenuje třídu, po ní následuje nepovinný type_parameter_list (§15.2.3), za nímž následuje volitelná specifikace class_base (§15.2.4)), po níž následuje volitelná sada type_parameter_constraints_clause s (§15.2.5), za kterou následuje class_body (§15.2.6), případněnásleduje středník.

Prohlášení o třídě nesmí poskytovat type_parameter_constraints_clauses, pokud nenabídá ani type_parameter_list.

Deklarace třídy, která poskytuje type_parameter_list je obecná deklarace třídy. Kromě toho každá třída vnořená uvnitř obecné deklarace třídy nebo obecná deklarace struktury je sama o sobě obecnou deklarací třídy, protože argumenty typu pro obsahující typ musí být zadány k vytvoření konstruovaného typu (§8.4).

15.2.2 Modifikátory tříd

15.2.2.1 Obecné

Class_declaration může volitelně obsahovat posloupnost modifikátorů tříd:

class_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'abstract'
    | 'sealed'
    | 'static'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Jedná se o chybu v době kompilace, aby se stejný modifikátor zobrazoval vícekrát v deklaraci třídy.

Modifikátor new je povolen u vnořených tříd. Určuje, že třída skryje zděděný člen se stejným názvem, jak je popsáno v §15.3.5. Jedná se o chybu v době kompilace, new která se má modifikátor objevit v deklaraci třídy, která není vnořenou deklarací třídy.

publicModifikátory , , protectedinternala private řídí přístupnost třídy. V závislosti na kontextu, ve kterém se deklarace třídy vyskytuje, mohou být některé z těchto modifikátorů povoleny (§7.5.2).

Pokud prohlášení o částečném typu (§15.2.7) zahrnuje specifikaci přístupnosti (prostřednictvím public, protected, internala private modifikátorů), musí tato specifikace souhlasit se všemi ostatními částmi, které obsahují specifikaci přístupnosti. Pokud žádná část částečného typu neobsahuje specifikaci přístupnosti, je typ uveden vhodnou výchozí přístupnost (§7.5.2).

Modifikátory abstract, sealeda static modifikátory jsou popsány v následujících podklidách.

15.2.2.2 Abstraktní třídy

abstract Modifikátor se používá k označení, že třída není úplná a že je určena pouze jako základní třída. Abstraktní třída se liší od abstraktní třídy následujícími způsoby:

  • Abstraktní třídu nelze vytvořit přímo a jedná se o chybu v době kompilace pro použití operátoru new v abstraktní třídě. I když je možné mít proměnné a hodnoty, jejichž typy kompilátor-čas jsou abstraktní, takové proměnné a hodnoty budou nutně buď buď být null nebo obsahují odkazy na instance ne abstraktních tříd odvozených z abstraktních typů.
  • Abstraktní třída je povolena (ale nevyžaduje) obsahovat abstraktní členy.
  • Abstraktní třídu nelze zapečetit.

Je-li abstraktní třída odvozena z abstraktní třídy, třída, která není abstraktní, zahrnuje skutečné implementace všech zděděných abstraktních členů, čímž tyto abstraktní členy přepíše.

Příklad: V následujícím kódu

abstract class A
{
    public abstract void F();
}

abstract class B : A
{
    public void G() {}
}

class C : B
{
    public override void F()
    {
        // Actual implementation of F
    }
}

abstraktní třída A zavádí abstraktní metodu F. Třída B zavádí další metodu G, ale protože neposkytuje implementaci F, B musí být deklarována také abstraktní. Třída C přepisuje F a poskytuje skutečnou implementaci. Vzhledem k tomu, že neexistují žádné abstraktní členy C, C je povoleno (ale nevyžaduje) být ne abstrakt.

end example

Pokud jedna nebo více částí částečné deklarace typu (§15.2.7) třídy zahrnuje abstract modifikátor, třída je abstraktní. V opačném případě třída není abstraktní.

15.2.2.3 Zapečetěné třídy

Modifikátor sealed se používá k zabránění odvození z třídy. K chybě v době kompilace dochází, pokud je zapečetěná třída určena jako základní třída jiné třídy.

Zapečetěná třída nemůže být také abstraktní třídou.

Poznámka: sealed Modifikátor se primárně používá k prevenci nechtěných odvození, ale také umožňuje určité optimalizace za běhu. Konkrétně, protože zapečetěná třída je známo, že nikdy nemají žádné odvozené třídy, je možné transformovat volání členů virtuální funkce na zapečetěné instance třídy na ne-virtuální vyvolání. koncová poznámka

Pokud jedna nebo více částí částečné deklarace typu (§15.2.7) třídy zahrnuje sealed modifikátor, je třída zapečetěna. Jinak je třída nezapečetěná.

15.2.2.4 Statické třídy

15.2.2.4.1 Obecné

static Modifikátor se používá k označení třídy deklarované jako statická třída. Statická třída nesmí být vytvořena, nesmí být použita jako typ a musí obsahovat pouze statické členy. Pouze statická třída může obsahovat deklarace rozšiřujících metod (§15.6.10).

Deklarace statické třídy podléhá následujícím omezením:

  • Statická třída nesmí obsahovat sealed ani abstract modifikátor. (Vzhledem k tomu, že statickou třídu nelze vytvořit instanci ani odvodit, chová se, jako by byla zapečetěná i abstraktní.)
  • Statická třída nesmí obsahovat specifikaci class_base (§15.2.4) a nesmí explicitně určit základní třídu ani seznam implementovaných rozhraní. Statická třída implicitně dědí z typu object.
  • Statická třída musí obsahovat pouze statické členy (§15.3.8).

    Poznámka: Všechny konstanty a vnořené typy jsou klasifikovány jako statické členy. koncová poznámka

  • Statická třída nesmí mít členy s protected, private protectedani protected internal deklarovanou přístupnost.

Jedná se o chybu v době kompilace, která porušuje některá z těchto omezení.

Statická třída nemá žádné konstruktory instance. Nelze deklarovat konstruktor instance ve statické třídě a pro statickou třídu není k dispozici žádný výchozí konstruktor instance (§15.11.5).

Členy statické třídy nejsou automaticky statické a deklarace členů musí explicitně obsahovat static modifikátor (s výjimkou konstant a vnořených typů). Pokud je třída vnořena do statické vnější třídy, vnořená třída není statickou třídou, pokud explicitně neobsahuje static modifikátor.

Pokud jedna nebo více částí částečné deklarace typu (§15.2.7) třídy obsahuje static modifikátor, je třída statická. Jinak není třída statická.

15.2.2.4.2 Odkazování na statické typy tříd

Namespace_or_type_name (§7.8) je povoleno odkazovat na statickou třídu, pokud

  • Namespace_or_type_name je T ve namespace_or_type_name formuláře T.I, nebo
  • Název namespace_or_type je T v typeof_expression (§12.8.18) formuláře typeof(T).

Primary_expression (§12.8) je povoleno odkazovat na statickou třídu, pokud

  • Primary_expression je E v member_access (§12.8.7) formuláře E.I.

V jakémkoli jiném kontextu se jedná o chybu v době kompilace odkazování na statickou třídu.

Poznámka: Jedná se například o chybu pro statickou třídu, která se má použít jako základní třída, základní typ (§15.3.7) člena, argument obecného typu nebo omezení parametru typu. Stejně tak nelze statickou třídu použít v typu pole, nový výraz, výraz přetypování, výraz je výraz, výraz jako výraz, sizeof výraz nebo výchozí výraz hodnoty. koncová poznámka

15.2.3 Parametry typu

Parametr typu je jednoduchý identifikátor, který označuje zástupný symbol pro argument typu zadaný k vytvoření vytvořeného typu. Argument typu (§8.4.2) je podle constrastu typ, který se při vytvoření vytvořeného typu nahradí parametrem typu.

type_parameter_list
    : '<' type_parameters '>'
    ;

type_parameters
    : attributes? type_parameter
    | type_parameters ',' attributes? type_parameter
    ;

type_parameter je definován v §8.5.

Každý parametr typu v deklaraci třídy definuje název v prostoru deklarace (§7.3) dané třídy. Proto nemůže mít stejný název jako jiný parametr typu této třídy nebo člen deklarovaný v této třídě. Parametr typu nemůže mít stejný název jako samotný typ.

Dvě částečné deklarace obecného typu (ve stejném programu) přispívají ke stejnému nevázaným obecným typům, pokud mají stejný plně kvalifikovaný název (který zahrnuje generic_dimension_specifier (§12.8.18) pro počet parametrů typu) (§7.8.3). Dvě takové částečné deklarace typu musí určovat stejný název pro každý parametr typu v pořadí.

Základní specifikace třídy 15.2.4

15.2.4.1 Obecné

Deklarace třídy může obsahovat specifikaci class_base , která definuje přímou základní třídu třídy a rozhraní (§18) přímo implementovanou třídou.

class_base
    : ':' class_type
    | ':' interface_type_list
    | ':' class_type ',' interface_type_list
    ;

interface_type_list
    : interface_type (',' interface_type)*
    ;

15.2.4.2 Základní třídy

Pokud je class_type součástí class_base, určuje přímou základní třídu deklarované třídy. Pokud deklarace třídy, která není částečná, nemá žádnou class_base nebo pokud class_base uvádí pouze typy rozhraní, předpokládá se, že přímá základní třída je object. Pokud deklarace částečné třídy obsahuje specifikaci základní třídy, bude tato specifikace základní třídy odkazovat na stejný typ jako všechny ostatní části tohoto částečného typu, které obsahují specifikaci základní třídy. Pokud žádná část částečné třídy neobsahuje specifikaci základní třídy, základní třída je object. Třída dědí členy ze své přímé základní třídy, jak je popsáno v §15.3.4.

Příklad: V následujícím kódu

class A {}
class B : A {}

Třída A je řečeno, že je přímá základní třída B, a B je řečeno, že je odvozena z A. Vzhledem k tomu A , že explicitně nezadává přímou základní třídu, její přímá základní třída je implicitně object.

end example

Pro konstruovaný typ třídy, včetně vnořeného typu deklarovaného v rámci obecné deklarace typu (§15.3.9.7), je-li základní třída určena v deklaraci obecné třídy, základní třída konstruovaného typu je získána nahrazením každé type_parameter v deklaraci základní třídy, odpovídající type_argument konstruovaného typu.

Příklad: Při deklaraci obecné třídy

class B<U,V> {...}
class G<T> : B<string,T[]> {...}

základní třída vytvořeného typu G<int> by byla B<string,int[]>.

end example

Základní třídou zadanou v deklaraci třídy může být konstruovaný typ třídy (§8.4). Základní třída nemůže být parametrem typu sám (§8.5), i když může zahrnovat parametry typu, které jsou v oboru.

Příklad:

class Base<T> {}

// Valid, non-constructed class with constructed base class
class Extend1 : Base<int> {}

// Error, type parameter used as base class
class Extend2<V> : V {}

// Valid, type parameter used as type argument for base class
class Extend3<V> : Base<V> {}

end example

Přímá základní třída typu třídy musí být alespoň tak přístupná jako samotný typ třídy (§7.5.5). Jedná se například o chybu v době kompilace, která má veřejnou třídu odvodit z privátní nebo interní třídy.

Přímá základní třída typu třídy nesmí být žádným z následujících typů: System.Array, System.Delegate, System.EnumSystem.ValueType ani dynamic typu. Obecné prohlášení třídy navíc nesmí být používáno System.Attribute jako přímá nebo nepřímá základní třída (§22.2.1).

Při určování významu přímé specifikace A základní třídy třídy Bje přímá základní třída B dočasně považována objectza , což zajišťuje, že význam specifikace základní třídy nemůže rekurzivně záviset na sobě.

Příklad: Následující:

class X<T>
{
    public class Y{}
}

class Z : X<Z.Y> {}

je chybná, protože ve specifikaci X<Z.Y> základní třídy je přímá základní třída Z považována za object, a proto (podle pravidel §7.8) Z není považována za člena Y.

end example

Základní třídy třídy jsou přímou základní třídou a jejími základními třídami. Jinými slovy, sada základních tříd je tranzitivní uzavření přímé relace základní třídy.

Příklad: V následujícím příkladu:

class A {...}
class B<T> : A {...}
class C<T> : B<IComparable<T>> {...}
class D<T> : C<T[]> {...}

základní třídy D<int> jsou C<int[]>, B<IComparable<int[]>>, Aa object.

end example

Kromě třídy objectmá každá třída přesně jednu přímou základní třídu. Třída object nemá žádnou přímou základní třídu a je nejvyšší základní třídou všech ostatních tříd.

Jedná se o chybu v době kompilace, kdy třída závisí na sobě. Pro účely tohoto pravidla třída přímo závisí na své přímé základní třídě (pokud existuje) a přímo závisí na nejbližší nadřazené třídě, ve které je vnořena (pokud existuje). Vzhledem k této definici je kompletní sada tříd, na kterých třída závisí, tranzitivní uzavření přímo závisí na relaci.

Příklad: Příklad

class A : A {}

je chybná, protože třída závisí na sobě. Stejně tak příklad

class A : B {}
class B : C {}
class C : A {}

je chybná, protože třídy jsou cyklicky závislé na sobě. Nakonec příklad

class A : B.C {}
class B : A
{
    public class C {}
}

výsledkem chyby v době kompilace, protože A závisí na B.C (její přímé základní třídě), která závisí na B (její bezprostředně uzavřená třída), která cyklicky závisí na A.

end example

Třída nezávisí na třídách, které jsou vnořené do ní.

Příklad: V následujícím kódu

class A
{
    class B : A {}
}

Bzávisí na A (protože A je její přímá základní třída i její okamžitě uzavřená třída), ale A nezávisí na (protože B se nejedná o B základní třídu ani ohraničující třídu A). Proto je příklad platný.

end example

Není možné odvodit z zapečetěné třídy.

Příklad: V následujícím kódu

sealed class A {}
class B : A {} // Error, cannot derive from a sealed class

Třída B je chybná, protože se pokouší odvodit z zapečetěné třídy A.

end example

15.2.4.3 Implementace rozhraní

Specifikace class_base může obsahovat seznam typů rozhraní, v takovém případě se říká, že třída implementuje dané typy rozhraní. Pro konstruovaný typ třídy, včetně vnořeného typu deklarovaného v rámci obecné deklarace typu (§15.3.9.7), získá každý typ implementovaného rozhraní nahrazením každého type_parameter v daném rozhraní odpovídající type_argument konstruovaného typu.

Sada rozhraní pro typ deklarovaný ve více částech (§15.2.7) je sjednocení rozhraní uvedených v každé části. Konkrétní rozhraní lze na každé části pojmenovat pouze jednou, ale více částí může pojmenovat stejná základní rozhraní. Existuje pouze jedna implementace každého člena každého daného rozhraní.

Příklad: V následujícím příkladu:

partial class C : IA, IB {...}
partial class C : IC {...}
partial class C : IA, IB {...}

sada základních rozhraní pro třídu C je IA, IBa IC.

end example

Každá část obvykle poskytuje implementaci rozhraní deklarovaných v této části; to však není požadavek. Část může poskytnout implementaci pro rozhraní deklarované v jiné části.

Příklad:

partial class X
{
    int IComparable.CompareTo(object o) {...}
}

partial class X : IComparable
{
    ...
}

end example

Základní rozhraní zadaná v deklaraci třídy mohou být vytvořena typy rozhraní (§8.4, §18.2). Základní rozhraní nemůže být parametr typu sám, i když může zahrnovat parametry typu, které jsou v oboru.

Příklad: Následující kód ukazuje, jak může třída implementovat a rozšířit konstruované typy:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

end example

Implementace rozhraní jsou popsány dále v §18.6.

15.2.5 Omezení parametru typu

Deklarace obecného typu a metody mohou volitelně zadat omezení parametru typu zahrnutím type_parameter_constraints_clauses.

type_parameter_constraints_clauses
    : type_parameter_constraints_clause
    | type_parameter_constraints_clauses type_parameter_constraints_clause
    ;

type_parameter_constraints_clause
    : 'where' type_parameter ':' type_parameter_constraints
    ;

type_parameter_constraints
    : primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
    | secondary_constraints (',' constructor_constraint)?
    | constructor_constraint
    ;

primary_constraint
    : class_type nullable_type_annotation?
    | 'class' nullable_type_annotation?
    | 'struct'
    | 'notnull'
    | 'unmanaged'
    ;

secondary_constraint
    : interface_type nullable_type_annotation?
    | type_parameter nullable_type_annotation?
    ;

secondary_constraints
    : secondary_constraint (',' secondary_constraint)*
    ;

constructor_constraint
    : 'new' '(' ')'
    ;

Každý type_parameter_constraints_clause se skládá z tokenu wherenásledovaného názvem parametru typu, za kterým následuje dvojtečka a seznam omezení pro tento parametr typu. Pro každý parametr typu může existovat maximálně jedna where klauzule a where klauzule mohou být uvedeny v libovolném pořadí. get Podobně jako tokeny a set tokeny v přístupovém objektu where vlastnosti token není klíčovým slovem.

Seznam omezení uvedených v where klauzuli může obsahovat některou z následujících součástí v tomto pořadí: jedno primární omezení, jedno nebo více sekundárních omezení a omezení konstruktoru, new().

Primárním omezením může být typ třídy, typu odkazu , typu hodnoty ,class nebo nespravované omezenístructtypu . Typ třídy a omezení typu odkazu mohou zahrnovat nullable_type_annotation.

Sekundárním omezením může být interface_type nebo type_parameter, případně za ním následuje nullable_type_annotation. Přítomnost nullable_type_annotatione* značí, že argument typu může být odkazový typ s možnou hodnotou null, který odpovídá nenulovatelnému typu odkazu, který splňuje omezení.

Omezení typu odkazu určuje, že argument typu použitý pro parametr typu musí být odkazovým typem. Toto omezení splňují všechny typy tříd, typy rozhraní, typy delegátů, typy polí a parametry typu, které jsou známé jako odkazový typ (jak je definováno níže).

Typ třídy, omezení typu odkazu a sekundární omezení mohou obsahovat poznámku typu s možnou hodnotou null. Přítomnost nebo absence této poznámky u parametru typu indikuje očekávání nulové hodnoty argumentu typu:

  • Pokud omezení neobsahuje anotaci typu s možnou hodnotou null, očekává se, že argument typu nebude mít hodnotu null. Kompilátor může vydat upozornění, pokud je argument typu odkazem s možnou hodnotou null.
  • Pokud omezení obsahuje anotaci typu s možnou hodnotou null, je omezení splněno jak typem odkazu, který není nullable, tak typem odkazu s možnou hodnotou null.

Hodnota nullability argumentu typu se nemusí shodovat s hodnotou null parametru typu. Kompilátor může vydat upozornění, pokud hodnota null parametru typu neodpovídá hodnotě nullability argumentu typu.

Poznámka: Chcete-li určit, že argument typu je typ odkazu s možnou hodnotou null, nepřidávejte poznámku typu s možnou hodnotou null jako omezení (použít T : class nebo T : BaseClass), ale v T? rámci obecné deklarace uveďte odpovídající typ odkazu s možnou hodnotou null pro argument typu. koncová poznámka

Anotaci ?typu s možnou hodnotou null nelze použít u argumentu typu bez omezení.

Pro parametr T typu, pokud je argument typu nullable typ C?, instance T? jsou interpretovány jako C?, nikoli C??.

Příklad: Následující příklady ukazují, jak nullability argumentu typu ovlivňuje hodnotu nullability deklarace typu parametru:

public class C
{
}

public static class  Extensions
{
    public static void M<T>(this T? arg) where T : notnull
    {

    }
}

public class Test
{
    public void M()
    {
        C? mightBeNull = new C();
        C notNull = new C();

        int number = 5;
        int? missing = null;

        mightBeNull.M(); // arg is C?
        notNull.M(); //  arg is C?
        number.M(); // arg is int?
        missing.M(); // arg is int?
    }
}

Pokud je argument typu nenulový, znamená to, ? že parametr je odpovídajícím typem s možnou hodnotou null. Pokud je argument typu již typu odkazu s možnou hodnotou null, parametr je stejný typ s možnou hodnotou null.

end example

Omezení not null určuje, že argument typu použitý pro parametr typu by měl být nenulový typ hodnoty nebo nenulový odkazový typ. Argument typu, který není typem hodnoty null nebo nenulovým odkazovým typem, je povolen, ale kompilátor může vytvořit diagnostické upozornění.

Omezení typu hodnoty určuje, že argument typu použitý pro parametr typu musí být nenulový typ hodnoty. Všechny typy struktury, typy výčtu a parametry typu, které nemají hodnotu null, splňují toto omezení. Všimněte si, že i když je klasifikovaný jako typ hodnoty, typ hodnoty null (§8.3.12) nevyhovuje omezení typu hodnoty. Parametr typu s omezením typu hodnoty nesmí mít také constructor_constraint, i když jej lze použít jako argument typu pro jiný parametr typu s constructor_constraint.

Poznámka: Typ System.Nullable<T> určuje omezení typu hodnoty bez hodnoty null pro T. Proto rekurzivně konstruované typy formulářů T?? a Nullable<Nullable<T>> jsou zakázány. koncová poznámka

Protože unmanaged není klíčové slovo, v primary_constraint nespravované omezení je vždy syntakticky nejednoznačné s class_type. Z důvodu kompatibility je-li vyhledávání názvu (§12.8.4) názvu unmanaged úspěšné, je považováno class_typeza . Jinak se považuje za nespravované omezení.

Omezení nespravovaného typu určuje, že argument typu použitý pro parametr typu je nespravovatelný nespravovaný typ (§8.8).

Typy ukazatelů nikdy nesmí být argumenty typu a nesplňují žádná omezení typu, ani nespravované, i když jsou nespravované typy.

Pokud je omezení typ třídy, typ rozhraní nebo parametr typu, tento typ určuje minimální "základní typ", který musí podporovat každý argument typu použitý pro tento parametr typu. Při každém použití vytvořeného typu nebo obecné metody je argument typu kontrolován proti omezením parametru typu v době kompilace. Zadaný argument typu splňuje podmínky popsané v §8.4.5.

Omezení class_type musí splňovat následující pravidla:

  • Typ musí být typ třídy.
  • Typ nesmí být sealed.
  • Typ nesmí být jedním z následujících typů: System.Array nebo System.ValueType.
  • Typ nesmí být object.
  • Nejméně jedno omezení pro daný parametr typu může být typ třídy.

Typ určený jako omezení interface_type musí splňovat následující pravidla:

  • Typ musí být typ rozhraní.
  • V dané where klauzuli nesmí být typ určen více než jednou.

V obou případech může omezení zahrnovat kterýkoli z parametrů typu přidruženého typu nebo deklarace metody jako součást konstruovaného typu a může zahrnovat deklarovaný typ.

Každá třída nebo typ rozhraní určený jako omezení parametru typu musí být alespoň tak přístupný (§7.5.5) jako obecný typ nebo metoda deklarovaná.

Typ určený jako omezení type_parameter musí splňovat následující pravidla:

  • Typ musí být parametr typu.
  • V dané where klauzuli nesmí být typ určen více než jednou.

Kromě toho nesmí existovat žádné cykly v grafu závislostí parametrů typu, kde závislost představuje tranzitivní vztah definovaný:

  • Pokud se parametr T typu použije jako omezení parametru typuS, Szávisí naT tom.
  • Pokud parametr S typu závisí na parametru T typu a T závisí na parametru U typu, Szávisí naU tom.

Vzhledem k tomuto vztahu se jedná o chybu v době kompilace, kdy parametr typu závisí na sobě (přímo nebo nepřímo).

Všechna omezení musí být konzistentní mezi závislými parametry typu. Pokud parametr S typu závisí na parametru T typu, pak:

  • T nesmí mít omezení typu hodnoty. V opačném případě je účinně zapečetěno, T takže S by bylo nuceno být stejný typ jako T, eliminuje potřebu dvou parametrů typu.
  • Pokud S má omezení typu hodnoty, T nesmí mít class_type omezení.
  • Pokud S a AT, bude existovat převod identity nebo implicitní převod odkazu z B nebo implicitní převod odkazu z ABna .
  • Pokud S také závisí na parametru U typu a U a AT, musí existovat převod identity nebo implicitní převod odkazu z B nebo implicitní převod odkazu z A na B.

Je platný S , aby měl omezení typu hodnoty a T aby měl omezení typu odkazu. Efektivně se tím omezí T typy System.Object, System.ValueType, System.Enuma všechny typy rozhraní.

where Pokud klauzule parametru typu obsahuje omezení konstruktoru (který má formulářnew()), je možné pomocí new operátoru vytvořit instance typu (§12.8.17.2). Libovolný argument typu použitý pro parametr typu s omezením konstruktoru musí být typ hodnoty, ne abstraktní třída s veřejným konstruktorem bez parametrů nebo parametr typu s omezením typu nebo konstruktorem.

Jedná se o chybu v době kompilace pro type_parameter_constraints s primary_constraintstruct nebo unmanaged také constructor_constraint.

Příklad: Tady jsou příklady omezení:

interface IPrintable
{
    void Print();
}

interface IComparable<T>
{
    int CompareTo(T value);
}

interface IKeyProvider<T>
{
    T GetKey();
}

class Printer<T> where T : IPrintable {...}
class SortedList<T> where T : IComparable<T> {...}

class Dictionary<K,V>
    where K : IComparable<K>
    where V : IPrintable, IKeyProvider<K>, new()
{
    ...
}

Následující příklad je chybný, protože způsobuje cykličnost v grafu závislostí parametrů typu:

class Circular<S,T>
    where S: T
    where T: S // Error, circularity in dependency graph
{
    ...
}

Následující příklady ilustrují další neplatné situace:

class Sealed<S,T>
    where S : T
    where T : struct // Error, `T` is sealed
{
    ...
}

class A {...}
class B {...}

class Incompat<S,T>
    where S : A, T
    where T : B // Error, incompatible class-type constraints
{
    ...
}

class StructWithClass<S,T,U>
    where S : struct, T
    where T : U
    where U : A // Error, A incompatible with struct
{
    ...
}

end example

Dynamické vymazání typu C je Cₓ typu vytvořené takto:

  • Pokud C je vnořený typ Outer.Inner , Cₓ jedná se o vnořený typ Outerₓ.Innerₓ.
  • Pokud CCₓje konstruovaný typ G<A¹, ..., Aⁿ> s argumenty A¹, ..., Aⁿ typu, pak Cₓ je konstruovaný typ G<A¹ₓ, ..., Aⁿₓ>.
  • Pokud C je typ E[] pole, jedná Cₓ se o typ Eₓ[]pole .
  • Pokud C je dynamická, pak Cₓ je object.
  • Cₓ V opačném případě je C.

Efektivní základní třída parametru T typu je definována takto:

Pojďme R být sadou typů, které:

  • Pro každé omezení T , které je parametr typu, R obsahuje jeho efektivní základní třídu.
  • Pro každé omezení T , které je typu struktury, R obsahuje System.ValueType.
  • Pro každé omezení, které je typu výčtu T , R obsahuje System.Enum.
  • Pro každé omezení T , které je typu delegáta, R obsahuje jeho dynamické vymazání.
  • Pro každé omezení T , které je typu pole, R obsahuje System.Array.
  • Pro každé omezení T , které je typem třídy, R obsahuje jeho dynamické vymazání.

Pak...

  • Pokud T má omezení typu hodnoty, jeho efektivní základní třída je System.ValueType.
  • V opačném případě, pokud R je prázdná, efektivní základní třída je object.
  • V opačném případě je účinná základní třída T nejzahrnující typ (§10.5.3) množiny R. Pokud sada neobsahuje žádný typ, je platná základní třída T .object Pravidla konzistence zajišťují, že existuje nejobsáhodnější typ.

Pokud je parametr typu parametrem metody, jehož omezení jsou zděděna ze základní metody, efektivní základní třída se vypočítá po nahrazení typu.

Tato pravidla zajišťují, aby efektivní základní třída byla vždy class_type.

Efektivní sada rozhraní parametru T typu je definována takto:

  • Pokud T nemá žádné secondary_constraints, jeho efektivní sada rozhraní je prázdná.
  • Pokud Tinterface_type omezení, ale žádná omezení type_parameter, je jeho platná sada rozhraní sadou dynamických vymazání interface_type omezení.
  • Pokud T nemá žádná interface_type omezení, ale má omezení type_parameter , je její platná sada rozhraní sjednocením efektivních sad rozhraní jejích type_parameter omezení.
  • Pokud T má omezení interface_type i omezení type_parameter, její účinná sada rozhraní je sjednocení sady dynamických vymazání omezení interface_type a efektivní sady rozhraní omezení type_parameter.

Parametr typu je známý jako referenční typ , pokud má omezení typu odkazu nebo jeho efektivní základní třída není object nebo System.ValueType. Parametr typu je známý jako nenulový odkazový typ , pokud je známo, že jde o typ odkazu a má omezení typu odkaz s nenulovou hodnotou.

Hodnoty typu omezeného typu lze použít pro přístup ke členům instance odvozeným z omezení.

Příklad: V následujícím příkladu:

interface IPrintable
{
    void Print();
}

class Printer<T> where T : IPrintable
{
    void PrintOne(T x) => x.Print();
}

metody IPrintable lze vyvolat přímo, x protože T je omezena na vždy implementovat IPrintable.

end example

Pokud částečná deklarace obecného typu obsahuje omezení, musí omezení souhlasit se všemi ostatními částmi, které obsahují omezení. Konkrétně každá část, která obsahuje omezení, musí mít omezení pro stejnou sadu parametrů typu a pro každý parametr typu musí být sady primárních, sekundárních a konstruktorových omezení ekvivalentní. Dvě sady omezení jsou ekvivalentní, pokud obsahují stejné členy. Pokud žádná část částečného obecného typu specifikuje omezení parametru typu, parametry typu se považují za nekonstruované.

Příklad:

partial class Map<K,V>
    where K : IComparable<K>
    where V : IKeyProvider<K>, new()
{
    ...
}

partial class Map<K,V>
    where V : IKeyProvider<K>, new()
    where K : IComparable<K>
{
    ...
}

partial class Map<K,V>
{
    ...
}

je správná, protože ty části, které obsahují omezení (první dva), efektivně určují stejnou sadu primárních, sekundárních a konstruktorových omezení pro stejnou sadu parametrů typu.

end example

Tělo třídy 15.2.6

Class_body třídy definuje členy této třídy.

class_body
    : '{' class_member_declaration* '}'
    ;

15.2.7 Částečné deklarace

Modifikátor partial se používá při definování třídy, struktury nebo typu rozhraní ve více částech. partial Modifikátor je kontextové klíčové slovo (§6.4.4) a má zvláštní význam pouze bezprostředně před jedním z klíčových slov class, structnebo interface.

Každá část částečné deklarace typu musí obsahovat partial modifikátor a musí být deklarována ve stejném oboru názvů nebo obsahujícím typ jako ostatní části. partial Modifikátor označuje, že další části deklarace typu mohou existovat jinde, ale existence těchto dodatečných částí není požadavkem; je platná pouze pro deklaraci typu, která má obsahovat partial modifikátor. Platí pouze pro jednu deklaraci částečného typu, která zahrnuje základní třídu nebo implementovaná rozhraní. Všechny deklarace základní třídy nebo implementovaných rozhraní se však musí shodovat, včetně nullability všech zadaných argumentů typu.

Všechny části částečného typu se kompilují společně tak, aby se části mohly sloučit v době kompilace. Částečné typy výslovně neumožňují rozšíření již kompilovaných typů.

Vnořené typy lze deklarovat ve více částech pomocí modifikátoru partial . Typ obsahující se obvykle deklaruje také pomocí partial a každá část vnořeného typu je deklarována v jiné části obsahujícího typu.

Příklad: Následující částečná třída je implementována ve dvou částech, které se nacházejí v různých kompilačních jednotkách. První část je stroj generovaný nástrojem pro mapování databáze, zatímco druhá část je ručně vytvořena:

public partial class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }
}

// File: Customer2.cs
public partial class Customer
{
    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

Když jsou obě výše uvedené části zkompilovány dohromady, výsledný kód se chová tak, jako kdyby byla třída napsána jako jedna jednotka, následujícím způsobem:

public class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }

    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

end example

Zpracování atributů určených pro parametry typu nebo typu různých částí částečné deklarace je popsáno v §22.3.

15.3 Členové třídy

15.3.1 Obecné

Členy třídy se skládají z členů zavedených jeho class_member_declarationa členy zděděné z přímé základní třídy.

class_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    ;

Členové třídy jsou rozděleni do následujících kategorií:

  • Konstanty, které představují konstantní hodnoty spojené s třídou (§15.4).
  • Pole, která jsou proměnnými třídy (§15.5).
  • Metody, které implementují výpočty a akce, které mohou být provedeny třídou (§15.6).
  • Vlastnosti, které definují pojmenované charakteristiky a akce spojené se čtením a zápisem těchto charakteristik (§15.7).
  • Události, které definují oznámení, která mohou být generována třídou (§15.8).
  • Indexery, které umožňují indexování instancí třídy stejným způsobem (syntakticky) jako pole (§15.9).
  • Operátory, které definují operátory výrazů, které lze použít u instancí třídy (§15.10).
  • Konstruktory instancí, které implementují akce potřebné k inicializaci instancí třídy (§15.11)
  • Finalizační metody, které implementují akce, které mají být provedeny před instancemi třídy, jsou trvale zahozeny (§15.13).
  • Statické konstruktory, které implementují akce potřebné k inicializaci samotné třídy (§15.12).
  • Typy, které představují typy, které jsou místní pro třídu (§14.7).

Class_declaration vytvoří nový prostor deklarace (§7.3) a type_parametera class_member_declarations okamžitě obsažené class_declaration nové členy do tohoto prostoru prohlášení. Pro class_member_declarations platí následující pravidla:

  • Konstruktory instancí, finalizátory a statické konstruktory mají stejný název jako bezprostředně uzavřená třída. Všichni ostatní členové mají názvy, které se liší od názvu bezprostředně ohraničující třídy.

  • Název parametru typu v type_parameter_list deklarace třídy se liší od názvů všech ostatních parametrů typu ve stejném type_parameter_list a musí se lišit od názvu třídy a názvů všech členů třídy.

  • Název typu se liší od názvů všech netypových členů deklarovaných ve stejné třídě. Pokud dvě nebo více deklarací typu sdílejí stejný plně kvalifikovaný název, musí mít partial deklarace modifikátor (§15.2.7) a tyto deklarace kombinují tak, aby definovaly jeden typ.

Poznámka: Vzhledem k tomu, že plně kvalifikovaný název deklarace typu kóduje počet parametrů typu, mohou dva odlišné typy sdílet stejný název, pokud mají jiný počet parametrů typu. koncová poznámka

  • Název konstanty, pole, vlastnosti nebo události se liší od názvů všech ostatních členů deklarovaných ve stejné třídě.

  • Název metody se liší od názvů všech ostatních metod, které nejsou deklarované ve stejné třídě. Kromě toho se podpis (§7.6) metody liší od podpisů všech ostatních metod deklarovaných ve stejné třídě a dvě metody deklarované ve stejné třídě nesmí obsahovat podpisy, které se liší pouze inpomocí , outa ref.

  • Podpis konstruktoru instance se liší od podpisů všech ostatních konstruktorů instancí deklarovaných ve stejné třídě a dva konstruktory deklarované ve stejné třídě nemají podpisy, které se liší pouze ref podle a out.

  • Podpis indexeru se liší od podpisů všech ostatních indexerů deklarovaných ve stejné třídě.

  • Podpis operátora se liší od podpisů všech ostatních operátorů deklarovaných ve stejné třídě.

Zděděné členy třídy (§15.3.4) nejsou součástí deklarací prostoru třídy.

Poznámka: Odvozená třída proto může deklarovat člen se stejným názvem nebo podpisem jako zděděný člen (který v důsledku skryje zděděný člen). koncová poznámka

Sada členů typu deklarovaných ve více částech (§15.2.7) je sjednocení členů deklarovaných v každé části. Subjekty všech částí prohlášení o typu mají stejný prostor prohlášení (§7.3) a rozsah každého člena (§7.7) se vztahuje na subjekty všech částí. Doména přístupnosti každého člena vždy zahrnuje všechny části ohraničujícího typu; soukromý člen deklarovaný v jedné části je volně přístupný z jiné části. Jedná se o chybu v době kompilace, která deklaruje stejný člen ve více než jedné části typu, pokud tento člen není typem, který má partial modifikátor.

Příklad:

partial class A
{
    int x;                   // Error, cannot declare x more than once

    partial class Inner      // Ok, Inner is a partial type
    {
        int y;
    }
}

partial class A
{
    int x;                   // Error, cannot declare x more than once

    partial class Inner      // Ok, Inner is a partial type
    {
        int z;
    }
}

end example

Pořadí inicializace polí může být v kódu jazyka C# významné a některé záruky jsou poskytovány, jak je definováno v §15.5.6.1. V opačném případě je řazení členů v rámci typu zřídka významné, ale může být významné při vzájemném propojení s jinými jazyky a prostředími. V těchto případech není pořadí členů v rámci typu deklarovaného ve více částech definováno.

15.3.2 Typ instance

Každá deklarace třídy má přidružený typ instance. Pro obecnou deklaraci třídy je typ instance vytvořen vytvořením vytvořeného typu (§8.4) z deklarace typu, přičemž každý zadaný argument typu je odpovídající parametr typu. Vzhledem k tomu, že typ instance používá parametry typu, lze jej použít pouze tam, kde jsou parametry typu v oboru; to znamená, že uvnitř deklarace třídy. Typ instance je typ this kódu napsaného uvnitř deklarace třídy. Pro negenerické třídy je typ instance jednoduše deklarovanou třídou.

Příklad: Následující příklad ukazuje několik deklarací tříd spolu s jejich typy instancí:

class A<T>             // instance type: A<T>
{
    class B {}         // instance type: A<T>.B
    class C<U> {}      // instance type: A<T>.C<U>
}
class D {}             // instance type: D

end example

15.3.3 Členy konstruovaných typů

Nezděděné členy konstruovaného typu jsou získány nahrazením každé type_parameter v deklaraci člena odpovídající type_argument typu konstrukce. Proces nahrazení je založen na sémantickém významu deklarací typů a není to jen textová náhrada.

Příklad: Při deklaraci obecné třídy

class Gen<T,U>
{
    public T[,] a;
    public void G(int i, T t, Gen<U,T> gt) {...}
    public U Prop { get {...} set {...} }
    public int H(double d) {...}
}

konstruovaný typ Gen<int[],IComparable<string>> má následující členy:

public int[,][] a;
public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...}
public IComparable<string> Prop { get {...} set {...} }
public int H(double d) {...}

Typ členu v deklaraci a obecné třídy je "dvojrozměrné poleGen", takže typ členu T v vytvořeném typu výše je "dvojrozměrné pole jednorozměrného pole a" nebo int.int[,][]

end example

V rámci členů funkce instance je typ this instance (§15.3.2) obsahující deklarace.

Všichni členové obecné třídy mohou použít parametry typu z jakékoli uzavřené třídy, a to buď přímo, nebo jako součást vytvořeného typu. Při použití určitého uzavřeného typu konstrukce (§8.4.3) za běhu se každé použití parametru typu nahradí argumentem typu zadaným pro konstruovaný typ.

Příklad:

class C<V>
{
    public V f1;
    public C<V> f2;

    public C(V x)
    {
        this.f1 = x;
        this.f2 = this;
    }
}

class Application
{
    static void Main()
    {
        C<int> x1 = new C<int>(1);
        Console.WriteLine(x1.f1);              // Prints 1

        C<double> x2 = new C<double>(3.1415);
        Console.WriteLine(x2.f1);              // Prints 3.1415
    }
}

end example

15.3.4 Dědičnost

Třída dědí členy své přímé základní třídy. Dědičnost znamená, že třída implicitně obsahuje všechny členy své přímé základní třídy s výjimkou konstruktorů instancí, finalizátorů a statických konstruktorů základní třídy. Mezi důležité aspekty dědičnosti patří:

  • Dědičnost je tranzitivní. Je-li C odvozen z , a B je odvozen z BA, pak C dědí členy deklarované, B stejně jako členy deklarované v A.

  • Odvozená třída rozšiřuje svou přímou základní třídu. Odvozená třída může přidat nové členy do těch, které dědí, ale nemůže odebrat definici zděděného členu.

  • Konstruktory instancí, finalizátory a statické konstruktory nejsou zděděny, ale všechny ostatní členy jsou bez ohledu na jejich deklarovanou přístupnost (§7.5). V závislosti na deklarované přístupnosti však zděděné členy nemusí být přístupné v odvozené třídě.

  • Odvozená třída může skrýt (§7.7.2.3) zděděné členy deklarací nových členů se stejným názvem nebo podpisem. Skrytí zděděného členu však tento člen neodebere – pouze zpřístupňuje tento člen přímo prostřednictvím odvozené třídy.

  • Instance třídy obsahuje sadu všech polí instance deklarovaných ve třídě a jejích základních tříd a implicitní převod (§10.2.8) existuje z odvozeného typu třídy na kterýkoli z jeho základních typů třídy. Proto lze odkaz na instanci některé odvozené třídy považovat za odkaz na instanci jakékoli její základní třídy.

  • Třída může deklarovat virtuální metody, vlastnosti, indexery a události a odvozené třídy mohou přepsat implementaci těchto členů funkce. To umožňuje třídám vykazovat polymorfní chování, kdy se akce prováděné voláním člena funkce liší v závislosti na typu běhu instance, prostřednictvím které je tento člen funkce vyvolán.

Zděděné členy konstruovaného typu třídy jsou členy okamžitého základního typu třídy (§15.2.4.2), který je nalezen nahrazením argumentů typu konstruovaného typu pro každý výskyt odpovídajících parametrů typu v base_class_specification. Tyto členy jsou následně transformovány nahrazením každé type_parameter v deklaraci člena odpovídající type_argumentbase_class_specification.

Příklad:

class B<U>
{
    public U F(long index) {...}
}

class D<T> : B<T[]>
{
    public T G(string s) {...}
}

Ve výše uvedeném kódu má vytvořený typ D<int> veřejně intG(string s) nezděděný člen získaný nahrazením argumentu int typu parametru typu T. D<int> má také zděděný člen z deklarace Btřídy . Tento zděděný člen je určen prvním určením základního typu B<int[]>D<int> třídy nahrazením intT ve specifikaci B<T[]>základní třídy . Potom jako typ argumentu Bint[] , je nahrazen U v public U F(long index), výnos zděděný člen public int[] F(long index).

end example

15.3.5 Nový modifikátor

Class_member_declaration je povoleno deklarovat člena se stejným názvem nebo podpisem jako zděděný člen. Když k tomu dojde, odvozený člen třídy je řečeno skrýt člen základní třídy. Přesné určení, kdy člen skryje zděděný člen, viz §7.7.2.3 .

Zděděný člen M je považován za a neexistuje žádný jiný zděděný přístupný člen N, který již skryje . Implicitně skrytí zděděného členu se nepovažuje za chybu, ale kompilátor vydá upozornění, pokud deklarace odvozeného členu třídy neobsahuje modifikátor new, který explicitně indikuje, že odvozený člen je určen ke skrytí základního členu. Pokud jedna nebo více částí částečné deklarace (§15.2.7) vnořeného typu obsahuje new modifikátor, nevystaví se žádné upozornění, pokud vnořený typ skryje dostupný zděděný člen.

new Pokud je modifikátor součástí deklarace, která neskryje dostupný zděděný člen, zobrazí se upozornění na tento účinek.

15.3.6 Modifikátory accessu

Class_member_declaration může mít některý z povolených druhů deklarované přístupnosti (§7.5.2): public, , protected internal, protected, private protected, , internalnebo private. protected internal S výjimkou kombinace a private protected kombinace se jedná o chybu kompilátoru, která určuje více než jeden modifikátor přístupu. Pokud class_member_declaration neobsahuje žádné modifikátory přístupu, private předpokládá se.

15.3.7 Typy složek

Typy, které se používají v deklaraci členu, se nazývají základní typy tohoto členu. Možné typy složek jsou typ konstanty, pole, vlastnosti, události nebo indexeru, návratového typu metody nebo operátoru a typy parametrů metody, indexeru, operátoru nebo konstruktoru instance. Základní typy členů musí být alespoň tak přístupné jako člen sám (§7.5.5).

15.3.8 Statické členy a členy instance

Členy třídy jsou buď statické členy, nebo členy instance.

Poznámka: Obecně řečeno, je užitečné si představit statické členy jako patří do tříd a členů instance, jako patří k objektům (instance tříd). koncová poznámka

Pokud pole, metoda, vlastnost, událost, operátor nebo deklarace konstruktoru static obsahuje modifikátor, deklaruje statický člen. Deklarace konstanty nebo typu navíc implicitně deklaruje statický člen. Statické členy mají následující charakteristiky:

  • Je-li na statický člen M odkazován v member_access (§12.8.7) formuláře E.M, E označuje typ, který má člena M. Jedná se o chybu v době kompilace, E která označuje instanci.
  • Statické pole v negenerické třídě identifikuje přesně jedno umístění úložiště. Bez ohledu na to, kolik instancí negenerické třídy je vytvořeno, existuje pouze jedna kopie statického pole. Každý samostatný uzavřený konstruovaný typ (§8.4.3) má svou vlastní sadu statických polí bez ohledu na počet instancí uzavřeného typu konstrukce.
  • Člen statické funkce (metoda, vlastnost, událost, operátor nebo konstruktor) nepracuje s konkrétní instancí a jedná se o chybu v době kompilace, která se na toto odkazuje v takovém členu funkce.

Pokud pole, metoda, vlastnost, událost, indexer, konstruktor nebo finalizační deklarace neobsahuje statický modifikátor, deklaruje člen instance. (Člen instance se někdy označuje jako nestatický člen.) Členové instance mají následující charakteristiky:

  • Je-li na člena M instance odkazováno v member_access (§12.8.7) formuláře E.M, E označí se instance typu, který má člena M. Jedná se o chybu v době vazby, která značí typ E.
  • Každá instance třídy obsahuje samostatnou sadu všech polí instance třídy.
  • Člen funkce instance (metoda, vlastnost, indexer, konstruktor instance nebo finalizátor) pracuje s danou instancí třídy a tato instance je přístupná jako this (§12.8.14).

Příklad: Následující příklad znázorňuje pravidla pro přístup ke statickým členům a členům instance:

class Test
{
    int x;
    static int y;
    void F()
    {
        x = 1;               // Ok, same as this.x = 1
        y = 1;               // Ok, same as Test.y = 1
    }

    static void G()
    {
        x = 1;               // Error, cannot access this.x
        y = 1;               // Ok, same as Test.y = 1
    }

    static void Main()
    {
        Test t = new Test();
        t.x = 1;       // Ok
        t.y = 1;       // Error, cannot access static member through instance
        Test.x = 1;    // Error, cannot access instance member through type
        Test.y = 1;    // Ok
    }
}

Metoda F ukazuje, že v členu funkce instance lze použít simple_name (§12.8.4) pro přístup ke členům instance i statickým členům. Metoda G ukazuje, že ve statickém členu funkce se jedná o chybu v době kompilace pro přístup k členu instance prostřednictvím simple_name. Metoda Main ukazuje, že v member_access (§12.8.7) musí být členy instance přístupné prostřednictvím instancí a statické členy jsou přístupné prostřednictvím typů.

end example

15.3.9 Vnořené typy

15.3.9.1 Obecné

Typ deklarovaný v rámci třídy nebo struktury se nazývá vnořený typ. Typ, který je deklarován v rámci kompilační jednotky nebo oboru názvů, se nazývá nenořený typ.

Příklad: V následujícím příkladu:

class A
{
    class B
    {
        static void F()
        {
            Console.WriteLine("A.B.F");
        }
    }
}

třída B je vnořený typ, protože je deklarován v rámci třídy Aa třída A je nenořený typ, protože je deklarován v rámci kompilační jednotky.

end example

15.3.9.2 Plně kvalifikovaný název

Plně kvalifikovaný název (§7.8.3) pro deklaraci vnořeného typu je S.N, kde S je plně kvalifikovaný název deklarace typu, ve které je deklarován typ N, a N je nekvalifikovaný název (§7.8.2) deklarace vnořeného typu (včetně všech generic_dimension_specifier (§12.8.18)).

15.3.9.3 Deklarovaná přístupnost

Nenořené typy můžou mít public nebo internal deklarovat přístupnost a ve výchozím nastavení deklarovaly internal přístupnost. Vnořené typy můžou mít také tyto formy deklarované přístupnosti a jednu nebo více dalších forem deklarované přístupnosti v závislosti na tom, jestli je typ obsahující třídu nebo strukturu:

  • Vnořený typ, který je deklarován ve třídě, může mít některý z povolených typů deklarované přístupnosti a stejně jako ostatní členy třídy má výchozí hodnotu private deklarovanou přístupnost.
  • Vnořený typ, který je deklarován ve struktuře, může mít libovolnou ze tří forem deklarované přístupnosti (public, internalnebo private) a stejně jako ostatní členy struktury se ve výchozím nastavení private deklaruje přístupnost.

Příklad: Příklad

public class List
{
    // Private data structure
    private class Node
    {
        public object Data;
        public Node? Next;

        public Node(object data, Node? next)
        {
            this.Data = data;
            this.Next = next;
        }
    }

    private Node? first = null;
    private Node? last = null;

    // Public interface
    public void AddToFront(object o) {...}
    public void AddToBack(object o) {...}
    public object RemoveFromFront() {...}
    public object RemoveFromBack() {...}
    public int Count { get {...} }
}

deklaruje soukromou vnořenou třídu Node.

end example

15.3.9.4 Skrytí

Vnořený typ může skrýt (§7.7.2.2) základní člen. new Modifikátor (§15.3.5) je povolen u vnořených deklarací typu, aby bylo možné skrýt explicitně.

Příklad: Příklad

class Base
{
    public static void M()
    {
        Console.WriteLine("Base.M");
    }
}

class Derived: Base
{
    public new class M
    {
        public static void F()
        {
            Console.WriteLine("Derived.M.F");
        }
    }
}

class Test
{
    static void Main()
    {
        Derived.M.F();
    }
}

zobrazuje vnořenou třídu M , která skryje metodu M definovanou v Base.

end example

15.3.9.5 tento přístup

Vnořený typ a jeho typ neobsahuje zvláštní vztah s ohledem na this_access (§12.8.14). Konkrétně this v rámci vnořeného typu nelze použít k odkazování na členy instance obsahujícího typu. V případech, kdy vnořený typ potřebuje přístup k členům instance jeho obsahujícího typu, je možné přístup poskytnout poskytnutím this instance obsahujícího typu jako argumentu konstruktoru pro vnořený typ.

Příklad: Následující příklad

class C
{
    int i = 123;
    public void F()
    {
        Nested n = new Nested(this);
        n.G();
    }

    public class Nested
    {
        C this_c;

        public Nested(C c)
        {
            this_c = c;
        }

        public void G()
        {
            Console.WriteLine(this_c.i);
        }
    }
}

class Test
{
    static void Main()
    {
        C c = new C();
        c.F();
    }
}

ukazuje tuto techniku. Instance vytvoří instanci C instance Nesteda předá své vlastní Nesteddo konstruktoru s cílem poskytnout následný přístup ke Cčlenům instance.

end example

15.3.9.6 Přístup k soukromým a chráněným členům obsahujícího typu

Vnořený typ má přístup ke všem členům, které jsou přístupné pro jeho typ obsahující, včetně členů obsahujícího typu, které mají private a protected deklarovaly přístupnost.

Příklad: Příklad

class C
{
    private static void F() => Console.WriteLine("C.F");

    public class Nested
    {
        public static void G() => F();
    }
}

class Test
{
    static void Main() => C.Nested.G();
}

zobrazuje třídu C , která obsahuje vnořenou třídu Nested. V Nestedrámci metody G volá statickou metodu F definovanou v Ca F má privátní deklarovanou přístupnost.

end example

Vnořený typ může také přistupovat k chráněným členům definovaným v základním typu jeho obsahujícího typu.

Příklad: V následujícím kódu

class Base
{
    protected void F() => Console.WriteLine("Base.F");
}

class Derived: Base
{
    public class Nested
    {
        public void G()
        {
            Derived d = new Derived();
            d.F(); // ok
        }
    }
}

class Test
{
    static void Main()
    {
        Derived.Nested n = new Derived.Nested();
        n.G();
    }
}

vnořená třída Derived.Nested přistupuje k chráněné metodě F definované v Derived'základní třídě , Basevoláním prostřednictvím instance Derived.

end example

15.3.9.7 Vnořené typy v obecných třídách

Deklarace obecné třídy může obsahovat vnořené deklarace typu. Parametry typu ohraničující třídy lze použít v rámci vnořených typů. Deklarace vnořeného typu může obsahovat další parametry typu, které se vztahují pouze na vnořený typ.

Každá deklarace typu obsažená v deklaraci obecné třídy je implicitně deklarace obecného typu. Při zápisu odkazu na typ vnořený do obecného typu se pojmenuje obsahující konstruovaný typ, včetně argumentů jeho typu. Z vnější třídy však lze vnořený typ použít bez kvalifikace; Typ instance vnější třídy lze implicitně použít při vytváření vnořeného typu.

Příklad: Následující příklad ukazuje tři různé správné způsoby, jak odkazovat na vytvořený typ vytvořený z Inner; první dva jsou ekvivalentní:

class Outer<T>
{
    class Inner<U>
    {
        public static void F(T t, U u) {...}
    }

    static void F(T t)
    {
        Outer<T>.Inner<string>.F(t, "abc");    // These two statements have
        Inner<string>.F(t, "abc");             // the same effect
        Outer<int>.Inner<string>.F(3, "abc");  // This type is different
        Outer.Inner<string>.F(t, "abc");       // Error, Outer needs type arg
    }
}

end example

I když je to špatný programovací styl, parametr typu v vnořeném typu může skrýt člen nebo typ parametr deklarovaný ve vnějším typu.

Příklad:

class Outer<T>
{
    class Inner<T>                                  // Valid, hides Outer's T
    {
        public T t;                                 // Refers to Inner's T
    }
}

end example

15.3.10 Názvy rezervovaných členů

15.3.10.1 Obecné

Pro usnadnění provádění podkladového spuštění jazyka C# pro každou deklaraci zdrojového členu, která je vlastností, událostí nebo indexerem, si implementace vyhrazuje dva podpisy metody založené na druhu deklarace člena, jeho názvu a jeho typu (§15.3.10.2, §15.3.10.3, §15.3.10.4). Jedná se o chybu v době kompilace pro program, který deklaruje člena, jehož podpis odpovídá podpisu rezervovanému členem deklarovanému ve stejném oboru, i když základní implementace za běhu tyto rezervace nevyužívá.

Rezervované názvy nezavádějí deklarace, proto se nezúčastní vyhledávání členů. Podpisy vyhrazené metody prohlášení se však účastní dědičnosti (§15.3.4) a mohou být skryty modifikátorem (new).

Poznámka: Rezervace těchto názvů slouží třem účelům:

  1. Pokud chcete základní implementaci povolit použití běžného identifikátoru jako názvu metody pro získání nebo nastavení přístupu k funkci jazyka C#.
  2. Chcete-li umožnit spolupráci jiných jazyků pomocí běžného identifikátoru jako názvu metody pro získání nebo nastavení přístupu k funkci jazyka C#.
  3. Chcete-li zajistit, aby zdroj přijatý jedním odpovídajícím kompilátorem byl přijat jiným, tím, že zajistí konzistentně specifika rezervovaných názvů členů v rámci všech implementací jazyka C#.

koncová poznámka

Prohlášení finalizátoru (§15.13) rovněž způsobí vyhrazení podpisu (§15.3.10.5).

Některé názvy jsou vyhrazeny pro použití jako názvy metod operátorů (§15.3.10.6).

15.3.10.2 Názvy členů rezervované pro vlastnosti

Pro vlastnost P (§15.7) typu Tjsou vyhrazeny následující podpisy:

T get_P();
void set_P(T value);

Oba podpisy jsou vyhrazeny, i když je vlastnost jen pro čtení nebo jen pro zápis.

Příklad: V následujícím kódu

class A
{
    public int P
    {
        get => 123;
    }
}

class B : A
{
    public new int get_P() => 456;

    public new void set_P(int value)
    {
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        A a = b;
        Console.WriteLine(a.P);
        Console.WriteLine(b.P);
        Console.WriteLine(b.get_P());
    }
}

Třída A definuje vlastnost Pjen pro čtení , a proto si rezervuje podpisy pro get_P a set_P metody. A třída B je odvozena z A obou těchto rezervovaných podpisů a skryje je. Příklad vytvoří výstup:

123
123
456

end example

15.3.10.3 Názvy členů vyhrazené pro události

Pro akci E (§15.8) typu Tdelegáta jsou vyhrazeny následující podpisy:

void add_E(T handler);
void remove_E(T handler);

15.3.10.4 Názvy členů vyhrazené pro indexery

Pro indexer (§15.9) typu T se seznamem Lparametrů jsou vyhrazeny následující podpisy:

T get_Item(L);
void set_Item(L, T value);

Oba podpisy jsou rezervované, i když je indexer jen pro čtení nebo jen pro zápis.

Kromě toho je název Item člena vyhrazen.

15.3.10.5 Názvy členů vyhrazené pro finalizátory

Pro třídu obsahující finalizátor (§15.13) je vyhrazen následující podpis:

void Finalize();

15.3.10.6 Názvy metod vyhrazených pro operátory

Následující názvy metod jsou vyhrazeny. Zatímco mnoho z nich má v této specifikaci odpovídající operátory, některé jsou vyhrazené pro použití v budoucích verzích, zatímco některé jsou vyhrazeny pro spolupráci s jinými jazyky.

Název metody Operátor jazyka C#
op_Addition + (binární)
op_AdditionAssignment (rezervováno)
op_AddressOf (rezervováno)
op_Assign (rezervováno)
op_BitwiseAnd & (binární)
op_BitwiseAndAssignment (rezervováno)
op_BitwiseOr \|
op_BitwiseOrAssignment (rezervováno)
op_CheckedAddition (vyhrazeno pro budoucí použití)
op_CheckedDecrement (vyhrazeno pro budoucí použití)
op_CheckedDivision (vyhrazeno pro budoucí použití)
op_CheckedExplicit (vyhrazeno pro budoucí použití)
op_CheckedIncrement (vyhrazeno pro budoucí použití)
op_CheckedMultiply (vyhrazeno pro budoucí použití)
op_CheckedSubtraction (vyhrazeno pro budoucí použití)
op_CheckedUnaryNegation (vyhrazeno pro budoucí použití)
op_Comma (rezervováno)
op_Decrement -- (předpona a přípona)
op_Division /
op_DivisionAssignment (rezervováno)
op_Equality ==
op_ExclusiveOr ^
op_ExclusiveOrAssignment (rezervováno)
op_Explicit explicitní (zužující) převod
op_False false
op_GreaterThan >
op_GreaterThanOrEqual >=
op_Implicit implicitní (rozšiřující) převod
op_Increment ++ (předpona a přípona)
op_Inequality !=
op_LeftShift <<
op_LeftShiftAssignment (rezervováno)
op_LessThan <
op_LessThanOrEqual <=
op_LogicalAnd (rezervováno)
op_LogicalNot !
op_LogicalOr (rezervováno)
op_MemberSelection (rezervováno)
op_Modulus %
op_ModulusAssignment (rezervováno)
op_MultiplicationAssignment (rezervováno)
op_Multiply * (binární)
op_OnesComplement ~
op_PointerDereference (rezervováno)
op_PointerToMemberSelection (rezervováno)
op_RightShift >>
op_RightShiftAssignment (rezervováno)
op_SignedRightShift (rezervováno)
op_Subtraction - (binární)
op_SubtractionAssignment (rezervováno)
op_True true
op_UnaryNegation - (unární)
op_UnaryPlus + (unární)
op_UnsignedRightShift (vyhrazeno pro budoucí použití)
op_UnsignedRightShiftAssignment (rezervováno)

15.4 Konstanty

Konstanta je člen třídy, který představuje konstantní hodnotu: hodnotu, kterou lze vypočítat v době kompilace. Constant_declaration zavádí jednu nebo více konstant daného typu.

constant_declaration
    : attributes? constant_modifier* 'const' type constant_declarators ';'
    ;

constant_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    ;

Constant_declaration může obsahovat sadu atributů (§22), new modifikátor (§15.3.5) a kterýkoli z povolených druhů deklarované přístupnosti (§15.3.6). Atributy a modifikátory platí pro všechny členy deklarované constant_declaration. I když jsou konstanty považovány za statické členy, constant_declaration nevyžaduje ani neumožňuje static modifikátor. Jedná se o chybu, která se u stejného modifikátoru zobrazí vícekrát v deklaraci konstanty.

Typ constant_declaration určuje typ členů zavedených deklarací. Za typem následuje seznam constant_declarator s (§13.6.3), z nichž každý zavádínového člena. Constant_declarator se skládá z identifikátoru, který člena pojmenuje a za= ním "" token následovaný constant_expression (§12.23), který dává hodnotu člena.

Typ uvedený v konstantní deklaraci musí být , , sbyte, byteshort, , ushort, int, uintlongulongcharfloat, double, decimal, , enum_type boolstringnebo reference_type. Každý constant_expression poskytne hodnotu cílového typu nebo typu, který lze převést na cílový typ implicitním převodem (§10.2).

Typ konstanty musí být alespoň tak přístupný jako samotná konstanta (§7.5.5).

Hodnota konstanty se získá ve výrazu pomocí simple_name (§12.8.4) nebo member_access (§12.8.7).

Konstanta se může účastnit constant_expression. Konstanta se tedy může použít v libovolném konstruktoru , který vyžaduje constant_expression.

Poznámka: Mezi příklady takových konstruktorů patří case popisky, goto case příkazy, enum deklarace členů, atributy a další deklarace konstant. koncová poznámka

Poznámka: Jak je popsáno v §12.23, constant_expression je výraz, který lze plně vyhodnotit v době kompilace. Vzhledem k tomu, že jediný způsob, jak vytvořit jinou hodnotu než null reference_type , než string je použití new operátoru, a protože new operátor není povolen v constant_expression, jediná možná hodnota pro konstanty reference_typejiné než string je null. koncová poznámka

Je-li žádoucí symbolický název konstantní hodnoty, ale pokud typ této hodnoty není povolen v deklaraci konstanty nebo pokud hodnotu nelze vypočítat v době kompilace constant_expression, lze místo toho použít pole jen pro čtení (§15.5.3).

Poznámka: Sémantika const správy verzí a readonly odlišná (§15.5.3.3). koncová poznámka

Deklarace konstanty, která deklaruje více konstant, je ekvivalentní více deklarací jednoduchých konstant se stejnými atributy, modifikátory a typem.

Příklad:

class A
{
    public const double X = 1.0, Y = 2.0, Z = 3.0;
}

je ekvivalentem

class A
{
    public const double X = 1.0;
    public const double Y = 2.0;
    public const double Z = 3.0;
}

end example

Konstanty mohou záviset na jiných konstantách ve stejném programu, pokud závislosti nejsou cyklický charakter.

Příklad: V následujícím kódu

class A
{
    public const int X = B.Z + 1;
    public const int Y = 10;
}

class B
{
    public const int Z = A.Y + 1;
}

kompilátor musí nejprve vyhodnotit A.Y, pak vyhodnotit B.Za nakonec vyhodnotit A.X, vytvořit hodnoty 10, 11a 12.

end example

Deklarace konstant můžou záviset na konstantách z jiných programů, ale tyto závislosti jsou možné pouze v jednom směru.

Příklad: Odkazování na výše uvedený příklad, pokud A a B byly deklarovány v samostatných programech, by bylo možné A.X záviset na B.Z, ale B.Z nemohl by pak současně záviset na A.Y. end example

15.5 Pole

15.5.1 Obecné

Pole je člen, který představuje proměnnou přidruženou k objektu nebo třídě. Field_declaration zavádí jedno nebo více polí daného typu.

field_declaration
    : attributes? field_modifier* type variable_declarators ';'
    ;

field_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'readonly'
    | 'volatile'
    | unsafe_modifier   // unsafe code support
    ;

variable_declarators
    : variable_declarator (',' variable_declarator)*
    ;

variable_declarator
    : identifier ('=' variable_initializer)?
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Field_declaration může obsahovat sadu atributů (§22), new modifikátor (§15.3.5), platnou kombinaci čtyř modifikátorů přístupu (§15.3.6) a static modifikátoru (§15.5.2). Kromě toho může field_declaration obsahovat readonly modifikátor (§15.5.3) nebo volatile modifikátor (§15.5.4), ale ne obojí. Atributy a modifikátory se vztahují na všechny členy deklarované field_declaration. Jedná se o chybu, která se u stejného modifikátoru zobrazí vícekrát v field_declaration.

Typ field_declaration určuje typ členů zavedených deklarací. Za typem následuje seznam variable_declarators, z nichž každý představuje nového člena. Variable_declarator se skládá z identifikátoru, který označuje tento člen, volitelně následovaný tokenem "=" a variable_initializer (§15.5.6), který dává počáteční hodnotu tohoto člena.

Typ pole musí být alespoň tak přístupný jako pole samotné (§7.5.5).

Hodnota pole je získána ve výrazu pomocí simple_name (§12.8.4), member_access (§12.8.7) nebo base_access (§12.8.15). Hodnota pole bez čtení je změněna pomocí přiřazení (§12.21). Hodnotu nečteného pole lze získat i upravit pomocí operátorů přírůstku přípony a dekrementace (§12.8.16) a operátory inkrementace a dekrementace předpony (§12.9.6).

Deklarace pole, která deklaruje více polí, je ekvivalentní více deklarací jednoho pole se stejnými atributy, modifikátory a typem.

Příklad:

class A
{
    public static int X = 1, Y, Z = 100;
}

je ekvivalentem

class A
{
    public static int X = 1;
    public static int Y;
    public static int Z = 100;
}

end example

15.5.2 Statická pole a pole instance

Pokud deklarace pole obsahuje static modifikátor, pole zavedená deklarací jsou statická pole. Pokud není k dispozici žádný static modifikátor, pole zavedená deklarací jsou pole instance. Statická pole a pole instancí jsou dvěma druhy proměnných (§9), které jazyk C# podporuje, a v některých případech se označují jako statické proměnné a proměnné instancí.

Jak je vysvětleno v §15.3.8, každá instance třídy obsahuje úplnou sadu polí instance třídy, zatímco existuje pouze jedna sada statických polí pro každou ne generickou třídu nebo uzavřený konstruovaný typ bez ohledu na počet instancí třídy nebo uzavřeného typu konstrukce.

15.5.3 Pole jen pro čtení

15.5.3.1 Obecné

Pokud field_declaration obsahuje readonly modifikátor, pole zavedená deklarací jsou pole jen pro čtení. Přímá přiřazení pro pole jen pro čtení mohou nastat pouze v rámci této deklarace nebo v konstruktoru instance nebo statickém konstruktoru ve stejné třídě. (Pole jen pro čtení lze v těchto kontextech přiřadit vícekrát.) Konkrétně přímé přiřazení k poli jen pro čtení jsou povolená pouze v následujících kontextech:

  • V variable_declarator, která zavádí pole (zahrnutím variable_initializer do deklarace).
  • V případě pole instance v konstruktorech instance třídy, která obsahuje deklaraci pole; pro statické pole ve statickém konstruktoru třídy, která obsahuje deklaraci pole. Jedná se také o jediné kontexty, ve kterých je platné předat pole jen pro čtení jako výstupní nebo referenční parametr.

Pokus o přiřazení k poli jen pro čtení nebo jeho předání jako výstupní nebo referenční parametr v jakémkoli jiném kontextu je chyba v době kompilace.

15.5.3.2 Použití statických polí jen pro čtení pro konstanty

Statické pole jen pro čtení je užitečné, pokud je žádoucí symbolický název konstantní hodnoty, ale pokud typ hodnoty není povolen v deklaraci const nebo pokud hodnotu nelze vypočítat v době kompilace.

Příklad: V následujícím kódu

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);

    private byte red, green, blue;

    public Color(byte r, byte g, byte b)
    {
        red = r;
        green = g;
        blue = b;
    }
}

Black, White, RedGreena Blue členy nelze deklarovat jako členy const, protože jejich hodnoty nelze vypočítat v době kompilace. Deklarování je static readonly ale má mnohem stejný účinek.

end example

15.5.3.3 Správa verzí konstant a statických polí pro čtení

Konstanty a pole jen pro čtení mají různé sémantiky binární správy verzí. Když výraz odkazuje na konstantu, hodnota konstanty se získá v době kompilace, ale když výraz odkazuje na pole jen pro čtení, hodnota pole se nezískne, dokud neběží za běhu.

Příklad: Představte si aplikaci, která se skládá ze dvou samostatných programů:

namespace Program1
{
    public class Utils
    {
        public static readonly int x = 1;
    }
}

a

namespace Program2
{
    class Test
    {
        static void Main()
        {
            Console.WriteLine(Program1.Utils.X);
        }
    }
}

Program1 Obory Program2 názvů označují dva programy, které jsou kompilovány samostatně. Protože Program1.Utils.X je deklarován jako static readonly pole, výstup hodnoty příkazem Console.WriteLine není znám v době kompilace, ale spíše je získán za běhu. Proto pokud je hodnota X změněna a Program1 je rekompilována, Console.WriteLine příkaz vypíše novou hodnotu i v případě Program2 , že není rekompilován. Byla X by však konstanta, hodnota X by byla získána v době Program2 kompilace a zůstala by nedotčena změnami, Program1 dokud Program2 nebude znovu zkompilována.

end example

15.5.4 Nestálá pole

Pokud field_declaration obsahuje volatile modifikátor, pole zavedená danou deklarací jsou nestálá pole. V případě nestálých polí můžou techniky optimalizace, které změní pořadí instrukcí, vést k neočekávaným a nepředvídatelným výsledkům ve vícevláknových programech, které přistupují k polím bez synchronizace, jako je například ta poskytovaná lock_statement (§13.13). Tyto optimalizace může provádět kompilátor, systém za běhu nebo hardware. U nestálých polí jsou taková optimalizace změna pořadí omezena:

  • Čtení nestálého pole se nazývá nestálé čtení. Volatilní čtení má "získání sémantiky"; to znamená, že je zaručeno, že dojde před všemi odkazy na paměť, ke kterým dojde v pořadí instrukcí.
  • Zápis nestálého pole se nazývá těkavý zápis. Těkavý zápis má "sémantiku uvolnění"; to znamená, že je zaručeno, že dojde po jakékoli odkazy na paměť před instrukcí zápisu v pořadí instrukcí.

Tato omezení zajišťují, že všechna vlákna budou sledovat nestálé zápisy prováděné jakýmkoli jiným vláknem v pořadí, v jakém byly provedeny. Odpovídající implementace není nutná k poskytnutí jediného celkového pořadí nestálých zápisů, jak je vidět ze všech vláken provádění. Typ nestálého pole musí být jedním z následujících:

  • Reference_type.
  • Type_parameter, o které je známo, že jde o referenční typ (§15.2.5).
  • Typ byte, , , sbyte, shortushortintuintcharfloat, bool, nebo .System.IntPtrSystem.UIntPtr
  • Enum_type typu , byte, sbyteshort, , ushortnebo int.

Příklad: Příklad

class Test
{
    public static int result;
    public static volatile bool finished;

    static void Thread2()
    {
        result = 143;
        finished = true;
    }

    static void Main()
    {
        finished = false;

        // Run Thread2() in a new thread
        new Thread(new ThreadStart(Thread2)).Start();    

        // Wait for Thread2() to signal that it has a result
        // by setting finished to true.
        for (;;)
        {
            if (finished)
            {
                Console.WriteLine($"result = {result}");
                return;
            }
        }
    }
}

vytvoří výstup:

result = 143

V tomto příkladu metoda Main spustí nové vlákno, které spustí metodu Thread2. Tato metoda ukládá hodnotu do nevolatelní pole volané result, pak ukládá true do nestálého pole finished. Hlavní vlákno čeká na nastavení finishedpole true a pak přečte pole result. Vzhledem k tomu finished , že bylo deklarováno volatile, musí hlavní vlákno číst hodnotu 143 z pole result. finished Pokud pole nebylo deklarováno volatile, pak by bylo možné, aby result úložiště bylo viditelné pro hlavní vlákno po uložení finisheddo , a proto hlavní vlákno číst hodnotu 0 z pole result. Deklarování finished jako volatile pole brání jakékoli takové nekonzistence.

end example

Inicializace pole 15.5.5

Počáteční hodnota pole, ať už jde o statické pole nebo pole instance, je výchozí hodnota (§9.3) typu pole. Před provedením této výchozí inicializace není možné sledovat hodnotu pole a pole proto nikdy není inicializováno.

Příklad: Příklad

class Test
{
    static bool b;
    int i;

    static void Main()
    {
        Test t = new Test();
        Console.WriteLine($"b = {b}, i = {t.i}");
    }
}

vytvoří výstup.

b = False, i = 0

a bi oba jsou automaticky inicializovány na výchozí hodnoty.

end example

15.5.6 Inicializátory proměnných

15.5.6.1 Obecné

Deklarace polí mohou zahrnovat variable_initializers. Pro statická pole inicializátory proměnných odpovídají příkazům přiřazení, které se provádějí během inicializace třídy. Například pole proměnných inicializátory odpovídají příkazům přiřazení, které se spustí při vytvoření instance třídy.

Příklad: Příklad

class Test
{
    static double x = Math.Sqrt(2.0);
    int i = 100;
    string s = "Hello";

    static void Main()
    {
        Test a = new Test();
        Console.WriteLine($"x = {x}, i = {a.i}, s = {a.s}");
    }
}

vytvoří výstup.

x = 1.4142135623730951, i = 100, s = Hello

vzhledem k tomu, že přiřazení, ke kterému dojde x , když inicializátory statických polí provádějí a is při provádění inicializátorů polí instance.

end example

Výchozí inicializace hodnoty popsaná v §15.5.5 se vyskytuje pro všechna pole včetně polí s inicializátory proměnných. Proto při inicializaci třídy jsou všechna statická pole v této třídě nejprve inicializována na jejich výchozí hodnoty a pak jsou inicializátory statických polí prováděny v textovém pořadí. Podobně při vytvoření instance třídy jsou všechna pole instance v této instanci nejprve inicializována na jejich výchozí hodnoty a potom inicializátory pole instance se spustí v textovém pořadí. Pokud existují deklarace polí ve více deklarací částečného typu pro stejný typ, pořadí částí není zadáno. V každé části se však inicializátory polí provádějí v pořadí.

Statická pole s inicializátory proměnných je možné pozorovat ve výchozím stavu hodnoty.

Příklad: Toto se však důrazně nedoporučuje v rámci stylu. Příklad

class Test
{
    static int a = b + 1;
    static int b = a + 1;

    static void Main()
    {
        Console.WriteLine($"a = {a}, b = {b}");
    }
}

vykazuje toto chování. Navzdory cyklických definicích a a b, program je platný. Výsledkem je výstup.

a = 1, b = 2

vzhledem k tomu, že statická pole a a b inicializují se do 0 (výchozí hodnota pro int) před spuštěním jejich inicializátorů. Při inicializátoru spuštění a je hodnota b nula, a proto a je inicializována na 1. Při inicializátoru spuštění b je hodnota již 1a tak b je inicializována na 2.

end example

15.5.6.2 Inicializace statického pole

Inicializátory proměnných statického pole třídy odpovídají sekvenci přiřazení, která jsou provedena v textovém pořadí, ve kterém jsou uvedeny v deklaraci třídy (§15.5.6.1). V rámci částečné třídy je význam "textového pořadí" určen §15.5.6.1. Pokud ve třídě existuje statický konstruktor (§15.12), dojde k provedení inicializátorů statických polí bezprostředně před provedením tohoto statického konstruktoru. Jinak se inicializátory statických polí provádějí v době závislé na implementaci před prvním použitím statického pole této třídy.

Příklad: Příklad

class Test
{
    static void Main()
    {
        Console.WriteLine($"{B.Y} {A.X}");
    }

    public static int F(string s)
    {
        Console.WriteLine(s);
        return 1;
    }
}

class A
{
    public static int X = Test.F("Init A");
}

class B
{
    public static int Y = Test.F("Init B");
}

může vytvořit výstup:

Init A
Init B
1 1

nebo výstup:

Init B
Init A
1 1

vzhledem k tomu, že spuštění inicializátoru Xa Yinicializátoru může dojít v jiném pořadí; jsou omezeny pouze před odkazy na tato pole. V tomto příkladu však:

class Test
{
    static void Main()
    {
        Console.WriteLine($"{B.Y} {A.X}");
    }

    public static int F(string s)
    {
        Console.WriteLine(s);
        return 1;
    }
}

class A
{
    static A() {}
    public static int X = Test.F("Init A");
}

class B
{
    static B() {}
    public static int Y = Test.F("Init B");
}

výstupem musí být:

Init B
Init A
1 1

vzhledem k tomu, že pravidla pro provádění statických konstruktorů (jak je definováno v §15.12) stanoví, že Bstatický konstruktor (a proto Binicializátory statických polí) se spustí před Astatickým konstruktorem a inicializátory polí.

end example

Inicializace pole instance 15.5.6.3

Inicializátory proměnných pole instance odpovídají sekvenci přiřazení, která jsou provedena okamžitě po vstupu do některé z konstruktorů instance (§15.11.3) této třídy. V rámci částečné třídy je význam "textového pořadí" určen §15.5.6.1. Inicializátory proměnných se provádějí v textovém pořadí, ve kterém jsou uvedeny v deklaraci třídy (§15.5.6.1). Proces vytvoření a inicializace instance třídy je dále popsán v §15.11.

Inicializátor proměnné pro pole instance nemůže odkazovat na instanci, která se vytváří. Jedná se tedy o chybu v době kompilace odkazování this v inicializátoru proměnné, protože se jedná o chybu v době kompilace pro inicializátor proměnné odkazovat na libovolný člen instance prostřednictvím simple_name.

Příklad: V následujícím kódu

class A
{
    int x = 1;
    int y = x + 1;     // Error, reference to instance member of this
}

Inicializátor proměnné má y za následek chybu v době kompilace, protože odkazuje na člena vytvářené instance.

end example

15.6 Metody

15.6.1 Obecné

Metoda je člen, který implementuje výpočet nebo akci, která může být provedena objektem nebo třídou. Metody jsou deklarovány pomocí method_declarations:

method_declaration
    : attributes? method_modifiers return_type method_header method_body
    | attributes? ref_method_modifiers ref_kind ref_return_type method_header
      ref_method_body
    ;

method_modifiers
    : method_modifier* 'partial'?
    ;

ref_kind
    : 'ref'
    | 'ref' 'readonly'
    ;

ref_method_modifiers
    : ref_method_modifier*
    ;

method_header
    : member_name '(' parameter_list? ')'
    | member_name type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause*
    ;

method_modifier
    : ref_method_modifier
    | 'async'
    ;

ref_method_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

return_type
    : ref_return_type
    | 'void'
    ;

ref_return_type
    : type
    ;

member_name
    : identifier
    | interface_type '.' identifier
    ;

method_body
    : block
    | '=>' null_conditional_invocation_expression ';'
    | '=>' expression ';'
    | ';'
    ;

ref_method_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

Gramatické poznámky:

  • unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
  • při uznání method_body , pokud se použijí alternativy null_conditional_invocation_expression i výrazu , zvolí se první možnost.

Poznámka: Překrývající se alternativy a priority mezi těmito alternativami jsou určeny výhradně pro popisné pohodlí; gramatická pravidla by mohla být propracovaná tak, aby se překrývala. ANTLR a další gramatické systémy přijímají stejné pohodlí, takže method_body má zadanou sémantiku automaticky. koncová poznámka

Method_declaration může obsahovat sadu atributů (§22) a jeden z povolených druhů deklarované přístupnosti (§15.3.6), new (§15.3.5), (§15.6.3), (static), (virtual). 6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7), extern (§15.6.8) a async (§15.15) modifikátory.

Deklarace má platnou kombinaci modifikátorů, pokud jsou splněny všechny následující podmínky:

  • Prohlášení zahrnuje platnou kombinaci modifikátorů přístupu (§15.3.6).
  • Deklarace neobsahuje vícekrát stejný modifikátor.
  • Deklarace obsahuje nejvýše jeden z následujících modifikátorů: static, virtuala override.
  • Deklarace obsahuje nejvýše jeden z následujících modifikátorů: new a override.
  • Pokud deklarace obsahuje abstract modifikátor, deklarace neobsahuje žádný z následujících modifikátorů: static, virtual, sealednebo extern.
  • Pokud deklarace obsahuje private modifikátor, deklarace neobsahuje žádný z následujících modifikátorů: virtual, overridenebo abstract.
  • Pokud deklarace obsahuje sealed modifikátor, pak deklarace obsahuje override také modifikátor.
  • Pokud deklarace obsahuje partial modifikátor, neobsahuje žádný z následujících modifikátorů: new, public, , protectedinternal, private, virtualsealed, override, , nebo abstractextern.

Metody jsou klasifikovány podle toho, co, pokud něco, vrátí:

  • Pokud ref je k dispozici, metoda je vrátí-by-ref a vrátí proměnnou odkaz, který je volitelně jen pro čtení;
  • V opačném případě, pokud , metoda je void a nevrací hodnotu;
  • V opačném případě metoda vrátí hodnotu po hodnotě a vrátí hodnotu.

Return_type deklarace metody return-by-value nebo return-no-value určuje typ výsledku, pokud existuje, vrácená metodou. Modifikátorpartial) může obsahovat pouze metodu vrácení bez hodnoty. Pokud deklarace obsahuje async modifikátor,return_type musí být void nebo metoda vrátí po hodnotě a návratový typ je typ úkolu (§15.15.1).

Ref_return_type deklarace metody return-by-ref určuje typ proměnné odkazované variable_reference vrácenou metodou.

Obecná metoda je metoda, jejíž deklarace obsahuje type_parameter_list. Určuje parametry typu pro metodu. Volitelné type_parameter_constraints_clauseurčují omezení parametrů typu.

Obecná method_declaration pro explicitní implementaci člena rozhraní nesmí mít žádné type_parameter_constraints_clause; deklarace dědí všechna omezení z omezení metody rozhraní.

Podobně deklarace metody s modifikátorem override nesmí mít žádné type_parameter_constraints_clausea omezení parametrů typu metody se dědí z virtuální metody, která se přepisuje.

Member_name určuje název metody. Není-li metoda explicitní implementací člena rozhraní (§18.6.2), member_name je jednoduše identifikátor.

Pro explicitní implementaci člena rozhraní se member_name skládá z interface_type následované "." a identifikátorem. V tomto případě prohlášení neobsahuje žádné jiné modifikátory než (případně) extern nebo async.

Nepovinný parameter_list určuje parametry metody (§15.6.2).

Return_type nebo ref_return_type a každý typ uvedený v parameter_list metody musí být alespoň tak přístupný jako samotná metoda (§7.5.5).

Method_body metody returns-by-value nebo returns-no-value je středník, tělo bloku nebo tělo výrazu. Tělo bloku se skládá z bloku, který určuje příkazy, které se mají provést při vyvolání metody. Tělo výrazu se skládá z výrazu =>, následovaného null_conditional_invocation_expression nebo výrazem a středníkem, a označuje jeden výraz, který se má provést při vyvolání metody.

Pro abstraktní a extern metody se method_body skládá jednoduše ze středníku. U částečných metod může method_body obsahovat buď středník, blokový text nebo tělo výrazu. U všech ostatních metod je method_body buď blokovým tělem, nebo tělem výrazu.

Pokud se method_body skládá z středníku, nesmí prohlášení obsahovat async modifikátor.

Ref_method_body metody returns-by-ref je středník, blokové tělo nebo tělo výrazu. Tělo bloku se skládá z bloku, který určuje příkazy, které se mají provést při vyvolání metody. Tělo výrazu se skládá z výrazu =>, následovaného ref, variable_reference a středníku, a označuje jeden variable_reference vyhodnotit při vyvolání metody.

U abstraktních a externových metod se ref_method_body skládá jednoduše ze středníku; pro všechny ostatní metody je ref_method_body buď blokovým tělem, nebo tělem výrazu.

Název, počet parametrů typu a seznam parametrů metody definují podpis (§7.6) metody. Podpis metody se konkrétně skládá z jeho názvu, počtu parametrů jeho typu a čísla, parameter_mode_modifier s (§15.6.2.1) atypů jeho parametrů. Návratový typ není součástí podpisu metody ani nejsou názvy parametrů, názvy parametrů typu nebo omezení. Pokud typ parametru odkazuje na parametr typu metody, pro ekvivalenci typů se použije pořadová pozice parametru typu (nikoli název parametru typu).

Název metody se liší od názvů všech ostatních metod, které nejsou deklarované ve stejné třídě. Kromě toho se podpis metody liší od podpisů všech ostatních metod deklarovaných ve stejné třídě a dvě metody deklarované ve stejné třídě nemají podpisy, které se liší pouze inpomocí , outa ref.

Type_parameter metody jsou v oboru v celém method_declaration a lze je použít k formování typů v celém oboru v return_type nebo ref_return_type, method_body nebo ref_method_body a type_parameter_constraints_clause, ale ne v atributech.

Všechny parametry a parametry typu musí mít různé názvy.

Parametry metody 15.6.2

15.6.2.1 Obecné

Parametry metody, pokud existují, jsou deklarovány parameter_list metody.

parameter_list
    : fixed_parameters
    | fixed_parameters ',' parameter_array
    | parameter_array
    ;

fixed_parameters
    : fixed_parameter (',' fixed_parameter)*
    ;

fixed_parameter
    : attributes? parameter_modifier? type identifier default_argument?
    ;

default_argument
    : '=' expression
    ;

parameter_modifier
    : parameter_mode_modifier
    | 'this'
    ;

parameter_mode_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

parameter_array
    : attributes? 'params' array_type identifier
    ;

Seznam parametrů se skládá z jednoho nebo více parametrů oddělených čárkami, z nichž pouze poslední může být parameter_array.

Fixed_parameter se skládá z volitelné sady atributů (§22), volitelného in, out, , refnebo this modifikátoru; typu; identifikátoru; a volitelného default_argument. Každý fixed_parameter deklaruje parametr daného typu s daným názvem. this Modifikátor určuje metodu jako rozšiřující metodu a je povolena pouze u prvního parametru statické metody v ne generické statické třídě, která není vnořená. Pokud je struct parametr typem nebo parametrem structtypu omezeným na , this modifikátor může být kombinován s modifikátorem ref nebo in modifikátorem, ale ne s modifikátorem out . Rozšiřující metody jsou dále popsány v §15.6.10. Fixed_parameter s default_argument se označuje jako volitelný parametr, zatímco fixed_parameter bez default_argument je povinný parametr. Požadovaný parametr se v parameter_list nezobrazí za volitelným parametrem.

Parametr s modifikátorem refout nebo this parametr nemůže mít default_argument. Vstupní parametr může mít default_argument. Výraz v default_argument musí být jeden z následujících:

  • constant_expression
  • výraz formuláře new S() , kde S je typ hodnoty
  • výraz formuláře default(S) , kde S je typ hodnoty

Výraz se implicitně konvertibilní identitou nebo převodem s možnou hodnotou null na typ parametru.

Pokud v prováděcí deklaraci částečné metody dojde k nepovinným parametrům (§15.6.9), explicitní implementace člena rozhraní (§18.6.2), deklarace indexeru s jedním parametrem (§ 15.9) nebo v deklaraci operátoru (§15.10.1) by kompilátor měl dát upozornění, protože tyto členy nelze vyvolat způsobem, který umožňuje vynechat argumenty.

Parameter_array se skládá z volitelné sady atributů (§22), params modifikátoru, array_type a identifikátoru. Pole parametrů deklaruje jeden parametr daného typu pole s daným názvem. Array_type pole parametru musí být jednorozměrný typ matice (§17.2). Při vyvolání metody pole parametrů umožňuje zadat jeden argument daného typu pole, nebo umožňuje zadat nula nebo více argumentů typu prvku pole. Pole parametrů jsou podrobněji popsána v §15.6.2.4.

K parameter_array může dojít po volitelném parametru, ale nemůže mít výchozí hodnotu – vynechání argumentů pro parameter_array by místo toho vedlo k vytvoření prázdného pole.

Příklad: Následující příklad znázorňuje různé druhy parametrů:

void M<T>(
    ref int i,
    decimal d,
    bool b = false,
    bool? n = false,
    string s = "Hello",
    object o = null,
    T t = default(T),
    params int[] a
) { }

V parameter_list pro Mije povinný ref parametrd, je povinný parametr bhodnoty , , so a t jsou volitelné parametry hodnoty a a je pole parametrů.

end example

Deklarace metody vytvoří samostatný prostor deklarace (§7.3) pro parametry a parametry typu. Názvy se do tohoto prostoru deklarací zavádějí seznamem parametrů typu a seznamem parametrů metody. Tělo metody, pokud existuje, je považováno za vnořené v rámci tohoto prostoru deklarace. Jedná se o chybu, že dva členové prostoru deklarace metody mají stejný název.

Vyvolání metody (§12.8.10.2) vytvoří kopii specifickou pro toto vyvolání, parametrů a místních proměnných metody a seznam argumentů vyvolání přiřadí hodnoty nebo proměnné odkazy na nově vytvořené parametry. V rámci bloku metody mohou být parametry odkazovány jejich identifikátory ve výrazech simple_name (§12.8.4).

Existují následující typy parametrů:

Poznámka: Jak je popsáno v §7.6, inmodifikátory , outa ref modifikátory jsou součástí podpisu metody, ale params modifikátor není. koncová poznámka

15.6.2.2 Parametry hodnoty

Parametr deklarovaný bez modifikátorů je parametr hodnoty. Parametr hodnoty je místní proměnná, která získá počáteční hodnotu z odpovídajícího argumentu zadaného v vyvolání metody.

Určitá pravidla přiřazení naleznete v §9.2.5.

Odpovídající argument v vyvolání metody je výraz, který je implicitně konvertibilní (§10.2) na typ parametru.

Metoda má povoleno přiřazovat nové hodnoty k parametru hodnoty. Taková přiřazení mají vliv pouze na umístění místního úložiště reprezentované parametrem hodnoty – nemají žádný vliv na skutečný argument zadaný při vyvolání metody.

15.6.2.3 Parametry podle odkazu

15.6.2.3.1 Obecné

Vstupní, výstupní a referenční parametry jsou parametrypodle odkazu. Parametr by-reference je místní referenční proměnná (§9.7); počáteční odkaz je získán z odpovídajícího argumentu zadaného při vyvolání metody.

Poznámka: Odkaz na parametr by-reference lze změnit pomocí operátoru přiřazení odkazu (= ref).

Pokud je parametr parametrem podle odkazu, odpovídající argument vyvolání metody se skládá z odpovídajícího klíčového slova , innebo refout, následovaného variable_reference (§9.5) stejného typu jako parametr. Pokud je parametr parametrem in , může být argument výrazem , pro který existuje implicitní převod (§10.2) z tohoto výrazu argumentu na typ odpovídajícího parametru.

Referenční parametry nejsou povoleny pro funkce deklarované jako iterátor (§15.14) nebo asynchronní funkce (§15.15).

V metodě, která přebírá více parametrů podle odkazu, je možné, aby více názvů představovalo stejné umístění úložiště.

15.6.2.3.2 Vstupní parametry

Parametr deklarovaný pomocí modifikátoru je vstupní parametr.in Argument odpovídající vstupnímu parametru je buď proměnná existující v okamžiku vyvolání metody, nebo proměnná vytvořená implementací (§12.6.2.3) při vyvolání metody. Určitá pravidla přiřazení naleznete v §9.2.8.

Jedná se o chybu v době kompilace pro úpravu hodnoty vstupního parametru.

Poznámka: Primárním účelem vstupních parametrů je efektivita. Pokud je typ parametru metody velkou strukturou (z hlediska požadavků na paměť), je užitečné se vyhnout kopírování celé hodnoty argumentu při volání metody. Vstupní parametry umožňují metodám odkazovat na existující hodnoty v paměti a zároveň poskytovat ochranu proti nežádoucím změnám těchto hodnot. koncová poznámka

15.6.2.3.3 Referenční parametry

Parametr deklarovaný pomocí modifikátoru je referenční parametr.ref Pro pravidla určitého přiřazení viz §9.2.6.

Příklad: Příklad

class Test
{
    static void Swap(ref int x, ref int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }

    static void Main()
    {
        int i = 1, j = 2;
        Swap(ref i, ref j);
        Console.WriteLine($"i = {i}, j = {j}");
    }
}

vytvoří výstup.

i = 2, j = 1

Pro vyvolání Swap in Main, x představuje i a y představuje j. Vyvolání tedy má vliv na prohození hodnot a ij.

end example

Příklad: V následujícím kódu

class A
{
    string s;
    void F(ref string a, ref string b)
    {
        s = "One";
        a = "Two";
        b = "Three";
    }

    void G()
    {
        F(ref s, ref s);
    }
}

vyvolání F in G předá odkaz na s obojí a i b. Proto pro toto vyvolání, názvy s, aa b všechny odkazují na stejné umístění úložiště a tři přiřazení všechny upravují pole sinstance .

end example

struct Pro typ v rámci metody instance se objekt instance (§12.2.1) nebo konstruktor instance s inicializátorem this konstruktoru chová klíčové slovo přesně jako referenční parametr typu struktury (§12.8.14).

15.6.2.3.4 Výstupní parametry

Parametr deklarovaný s modifikátorem out je výstupní parametr. Určitá pravidla přiřazení naleznete v §9.2.7.

Metoda deklarovaná jako částečná metoda (§15.6.9) nesmí mít výstupní parametry.

Poznámka: Výstupní parametry se obvykle používají v metodách, které vytvářejí více návratových hodnot. koncová poznámka

Příklad:

class Test
{
    static void SplitPath(string path, out string dir, out string name)
    {
        int i = path.Length;
        while (i > 0)
        {
            char ch = path[i - 1];
            if (ch == '\\' || ch == '/' || ch == ':')
            {
                break;
            }
            i--;
        }
        dir = path.Substring(0, i);
        name = path.Substring(i);
    }

    static void Main()
    {
        string dir, name;
        SplitPath(@"c:\Windows\System\hello.txt", out dir, out name);
        Console.WriteLine(dir);
        Console.WriteLine(name);
    }
}

Příklad vytvoří výstup:

c:\Windows\System\
hello.txt

Všimněte si, že dir před předáním nameproměnné a SplitPath proměnné mohou být nepřiřazeny a že jsou považovány za rozhodně přiřazené po volání.

end example

15.6.2.4 Pole parametrů

Parametr deklarovaný pomocí modifikátoru params je pole parametrů. Pokud seznam parametrů obsahuje pole parametrů, musí být posledním parametrem v seznamu a musí se jednat o jednorozměrný typ pole.

Příklad: Typy string[] a string[][] lze je použít jako typ pole parametrů, ale typ string[,] nemůže. end example

Poznámka: Modifikátor není možné kombinovat params s modifikátory in, outnebo ref. koncová poznámka

Pole parametrů umožňuje zadat argumenty jedním ze dvou způsobů volání metody:

  • Argumentem zadaným pro pole parametrů může být jediný výraz, který je implicitně konvertibilní (§10.2) na typ pole parametru. V tomto případě pole parametrů funguje přesně jako parametr hodnoty.
  • Volání může také zadat nula nebo více argumentů pro pole parametrů, kde každý argument je výraz, který je implicitně konvertibilní (§10.2) na typ prvku pole parametru. V tomto případě vyvolání vytvoří instanci typu pole parametru s délkou odpovídající počtu argumentů, inicializuje prvky instance pole s danými hodnotami argumentu a použije nově vytvořenou instanci matice jako skutečný argument.

S výjimkou povolení proměnného počtu argumentů ve vyvolání je pole parametrů přesně ekvivalentní parametru parametru (§15.6.2.2) stejného typu.

Příklad: Příklad

class Test
{
    static void F(params int[] args)
    {
        Console.Write($"Array contains {args.Length} elements:");
        foreach (int i in args)
        {
            Console.Write($" {i}");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        int[] arr = {1, 2, 3};
        F(arr);
        F(10, 20, 30, 40);
        F();
    }
}

vytvoří výstup.

Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:

První vyvolání F jednoduše předá pole arr jako parametr hodnoty. Druhé vyvolání jazyka F automaticky vytvoří čtyři prvky int[] s danými hodnotami elementu a předá instanci pole jako parametr hodnoty. Stejně tak třetí vyvolání F vytvoří prvek int[] nula a předá instanci jako parametr hodnoty. Druhá a třetí vyvolání jsou přesně ekvivalentní psaní:

F(new int[] {10, 20, 30, 40});
F(new int[] {});

end example

Při řešení přetížení lze použít metodu s polem parametrů, a to buď v normální podobě, nebo v rozšířené podobě (§12.6.4.2). Rozšířená forma metody je k dispozici pouze v případě, že normální forma metody není použitelná a pouze v případě, že příslušná metoda se stejným podpisem jako rozbalený formulář není již deklarována ve stejném typu.

Příklad: Příklad

class Test
{
    static void F(params object[] a) =>
        Console.WriteLine("F(object[])");

    static void F() =>
        Console.WriteLine("F()");

    static void F(object a0, object a1) =>
        Console.WriteLine("F(object,object)");

    static void Main()
    {
        F();
        F(1);
        F(1, 2);
        F(1, 2, 3);
        F(1, 2, 3, 4);
    }
}

vytvoří výstup.

F()
F(object[])
F(object,object)
F(object[])
F(object[])

V příkladu jsou dvě z možných rozšířených forem metody s polem parametrů již zahrnuty do třídy jako běžné metody. Tyto rozšířené formuláře se proto při provádění řešení přetížení nepovažují a první a třetí vyvolání metody tak vyberte běžné metody. Když třída deklaruje metodu s polem parametrů, není neobvyklé zahrnout také některé rozšířené formuláře jako běžné metody. Tímto způsobem je možné zabránit přidělení instance pole, ke kterému dochází při vyvolání rozšířené formy metody s polem parametrů.

end example

Matice je referenční typ, takže hodnota předaná pro pole parametrů může být null.

Příklad: Příklad:

class Test
{
    static void F(params string[] array) =>
        Console.WriteLine(array == null);

    static void Main()
    {
        F(null);
        F((string) null);
    }
}

vytvoří výstup:

True
False

Druhé vyvolání vznikne False tak, jak je ekvivalentní F(new string[] { null }) a předává pole obsahující jediný odkaz s hodnotou null.

end example

Pokud je object[]typ pole parametrů , může dojít k nejednoznačnosti mezi normální formou metody a rozbaleným formulářem pro jeden object parametr. Důvodem nejednoznačnosti je, že object[] se sám implicitně konvertibilní na typ object. Nejednoznačnost ale nemá žádný problém, protože ji lze v případě potřeby vyřešit vložením přetypování.

Příklad: Příklad

class Test
{
    static void F(params object[] args)
    {
        foreach (object o in args)
        {
            Console.Write(o.GetType().FullName);
            Console.Write(" ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        object[] a = {1, "Hello", 123.456};
        object o = a;
        F(a);
        F((object)a);
        F(o);
        F((object[])o);
    }
}

vytvoří výstup.

System.Int32 System.String System.Double
System.Object[]
System.Object[]
System.Int32 System.String System.Double

V prvním a posledním vyvolání F, normální forma F je použitelná, protože implicitní převod existuje z typu argumentu na typ parametru (oba jsou typu object[]). Rozlišení přetížení tedy vybere normální formu Fa argument se předá jako normální parametr hodnoty. Ve druhém a třetím vyvolání není normální forma F použitelná, protože neexistuje žádný implicitní převod z typu argumentu na typ parametru (typ object nelze implicitně převést na typ object[]). Rozšířená forma F je však použitelná, takže je vybrána překladem přetížení. Výsledkem je vytvoření jednoho prvku object[] vyvoláním a jeden prvek pole je inicializován s danou hodnotou argumentu (což je samotný odkaz na object[]).

end example

15.6.3 Statické metody a metody instancí

Pokud deklarace metody obsahuje static modifikátor, říká se, že tato metoda je statickou metodou. Pokud není k dispozici žádný static modifikátor, říká se, že metoda instance.

Statická metoda nepracuje s konkrétní instancí a jedná se o chybu v době kompilace odkazující na this statickou metodu.

Metoda instance pracuje s danou instancí třídy a k této instanci lze přistupovat jako this (§12.8.14).

Rozdíly mezi statickými členy a členy instance jsou popsány dále v §15.3.8.

15.6.4 Virtuální metody

Když deklarace metody instance obsahuje virtuální modifikátor, říká se, že tato metoda je virtuální metoda. Pokud není k dispozici žádný virtuální modifikátor, říká se, že metoda není virtuální metoda.

Implementace jiné než virtuální metody je invariantní: Implementace je stejná, zda metoda je vyvolána na instanci třídy, ve které je deklarována, nebo instance odvozené třídy. Naproti tomu implementace virtuální metody může být nahrazena odvozenými třídami. Proces převedení implementace zděděné virtuální metody se označuje jako přepsání této metody (§15.6.5).

Při vyvolání virtuální metody určuje typ běhu instance, pro kterou probíhá vyvolání, skutečnou implementaci metody, která se má vyvolat. Při vyvolání jiné než virtuální metody je určujícím faktorem typ kompilace instance. Pokud je metoda pojmenovaná N volána se seznamem A argumentů v instanci s typem C kompilace a typem R za běhu (kde R je buď C nebo třída odvozena), Cvyvolání se zpracuje takto:

  • V době vazby se rozlišení přetížení použije na , a , vybrat konkrétní metodu C ze sady metod deklarovaných a zděděných N.AMC Toto je popsáno v §12.8.10.2.
  • Pak za běhu:
    • Pokud M je jiná než virtuální metoda, M je vyvolána.
    • M V opačném případě je virtuální metoda a je vyvolána nejvíce odvozená implementace M s ohledem naR.

Pro každou virtuální metodu deklarovanou ve třídě nebo zděděnou třídou existuje nejvýraznější implementace metody s ohledem na danou třídu. Nejvýraznější implementace virtuální metody M s ohledem na třídu R je určena takto:

  • Pokud R obsahuje zavedení virtuální deklarace M, pak je to nejvýraznější implementace M s ohledem na R.
  • Jinak, pokud R obsahuje přepsání M, pak je to nejvýraznější implementace M s ohledem na R.
  • V opačném případě je nejvýraznější implementace M s ohledem na R nejvíce odvozenou implementaci M s ohledem na přímou základní třídu R.

Příklad: Následující příklad znázorňuje rozdíly mezi virtuálními a ne virtuálními metodami:

class A
{
    public void F() => Console.WriteLine("A.F");
    public virtual void G() => Console.WriteLine("A.G");
}

class B : A
{
    public new void F() => Console.WriteLine("B.F");
    public override void G() => Console.WriteLine("B.G");
}

class Test
{
    static void Main()
    {
        B b = new B();
        A a = b;
        a.F();
        b.F();
        a.G();
        b.G();
    }
}

V příkladu A představuje ne-virtuální metodu F a virtuální metodu G. Třída B zavádí novou ne-virtuální metodu F, čímž skryje zděděný F, a také přepíše zděděnou metodu G. Příklad vytvoří výstup:

A.F
B.F
B.G
B.G

Všimněte si, že příkaz a.G() vyvolá B.G, ne A.G. Důvodem je to, že typ spuštění instance (což je B), nikoli typ kompilace instance (což je A), určuje skutečnou implementaci metody, která se má vyvolat.

end example

Protože metody mohou skrýt zděděné metody, je možné, aby třída obsahovala několik virtuálních metod se stejným podpisem. To nepředstavuje nejednoznačnost problém, protože všechny, ale nejvíce odvozené metody jsou skryté.

Příklad: V následujícím kódu

class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

class B : A
{
    public override void F() => Console.WriteLine("B.F");
}

class C : B
{
    public new virtual void F() => Console.WriteLine("C.F");
}

class D : C
{
    public override void F() => Console.WriteLine("D.F");
}

class Test
{
    static void Main()
    {
        D d = new D();
        A a = d;
        B b = d;
        C c = d;
        a.F();
        b.F();
        c.F();
        d.F();
    }
}

třídy CD obsahují dvě virtuální metody se stejným podpisem: jeden zavedený A a druhý zaveden .C Metoda zavedená skrytím C metody zděděné z A. Proto přepsání deklarace v D přepsání metody zavedené C, a není možné D přepsat metodu zavedenou metodou A. Příklad vytvoří výstup:

B.F
B.F
D.F
D.F

Všimněte si, že je možné vyvolat skrytou virtuální metodu přístupem k instanci D prostřednictvím méně odvozeného typu, ve kterém metoda není skrytá.

end example

15.6.5 Přepsání metod

Když deklarace metody instance obsahuje override modifikátor, metoda je řečeno, že je přepsána metoda. Metoda přepsání přepíše zděděnou virtuální metodu se stejným podpisem. Zatímco deklarace virtuální metody zavádí novou metodu, deklarace metody přepsání se specializuje na existující zděděnou virtuální metodu tím, že poskytuje novou implementaci této metody.

Metoda přepsáná deklarací přepsání je známá jako přepsaná základní metoda Pro přepsání metody M deklarované ve třídě C, přepsáná základní metoda je určena prozkoumáním každé základní třídy C, počínaje přímou základní třídou C a pokračováním s každou následnou přímou základní třídou, dokud v daném typu základní třídy není umístěna alespoň jedna přístupná metoda, která má stejný podpis jako M po nahrazení argumentů typu. Pro účely vyhledání přepsané základní metody je metoda považována za přístupnou, pokud je public, pokud je , pokud je protected, pokud protected internalje , nebo je , nebo pokud je buď internal nebo private protected a deklarována ve stejném programu jako C.

K chybě v době kompilace dochází, pokud nejsou splněny všechny následující podmínky pro deklaraci přepsání:

  • Přepsanou základní metodu lze najít, jak je popsáno výše.
  • Existuje přesně jedna taková přepsaná základní metoda. Toto omezení platí pouze v případě, že typ základní třídy je konstruovaný typ, kde nahrazení argumentů typu vytváří podpis dvou metod stejně.
  • Přepsaná základní metoda je virtuální, abstraktní nebo přepsáná metoda. Jinými slovy, přepsaná základní metoda nemůže být statická nebo ne virtuální.
  • Přepsaná základní metoda není zapečetěná metoda.
  • Mezi návratový typ přepsané základní metody a metodou přepsání existuje převod identity.
  • Deklarace přepsání a přepsaná základní metoda mají stejnou deklarovanou přístupnost. Jinými slovy, deklarace přepsání nemůže změnit přístupnost virtuální metody. Je-li však přepsaná základní metoda chráněna vnitřní a je deklarována v jiném sestavení než sestavení obsahující deklaraci přepsání, bude deklarována deklarace přepsání přístupnosti chráněna.
  • Deklarace přepsání nezadá žádné type_parameter_constraints_clauses. Místo toho jsou omezení zděděna z přepsáné základní metody. Omezení, která jsou parametry typu v přepsáné metodě mohou být nahrazeny argumenty typu v zděděné omezení. To může vést k omezením, která nejsou platná při explicitně zadaném typu, jako jsou typy hodnot nebo zapečetěné typy.

Příklad: Následující příklad ukazuje, jak přepsání pravidel funguje pro obecné třídy:

abstract class C<T>
{
    public virtual T F() {...}
    public virtual C<T> G() {...}
    public virtual void H(C<T> x) {...}
}

class D : C<string>
{
    public override string F() {...}            // Ok
    public override C<string> G() {...}         // Ok
    public override void H(C<T> x) {...}        // Error, should be C<string>
}

class E<T,U> : C<U>
{
    public override U F() {...}                 // Ok
    public override C<U> G() {...}              // Ok
    public override void H(C<T> x) {...}        // Error, should be C<U>
}

end example

Přepsání deklarace může přistupovat k přepisované základní metodě pomocí base_access (§12.8.15).

Příklad: V následujícím kódu

class A
{
    int x;

    public virtual void PrintFields() => Console.WriteLine($"x = {x}");
}

class B : A
{
    int y;

    public override void PrintFields()
    {
        base.PrintFields();
        Console.WriteLine($"y = {y}");
    }
}

base.PrintFields() vyvolání vyvolá B metodu PrintFields deklarovanou v A. Base_access zakáže virtuální mechanismus vyvolání a jednoduše považuje základní metodu za metodu bezvirtual metody. Bylo-li vyvolání zapsáno B, by rekurzivně vyvolá metodu ((A)this).PrintFields() deklarovanou v PrintFields, nikoli v B, protože A je virtuální a typ PrintFields běhu je ((A)this).B

end example

Pouze zahrnutím modifikátoru override může metoda přepsat jinou metodu. Ve všech ostatních případech metoda se stejným podpisem jako zděděná metoda jednoduše skryje zděděnou metodu.

Příklad: V následujícím kódu

class A
{
    public virtual void F() {}
}

class B : A
{
    public virtual void F() {} // Warning, hiding inherited F()
}

metoda FB neobsahuje override modifikátor, a proto nepřepíše metodu F v A. Metoda v F skrytí metody v Ba upozornění je hlášena, A protože deklarace neobsahuje nový modifikátor.

end example

Příklad: V následujícím kódu

class A
{
    public virtual void F() {}
}

class B : A
{
    private new void F() {} // Hides A.F within body of B
}

class C : B
{
    public override void F() {} // Ok, overrides A.F
}

metoda F v B skrytí virtuální F metody zděděné z A. Vzhledem k tomu, že nový F v B má soukromý přístup, jeho rozsah zahrnuje pouze tělo B třídy a neprovádí se na C. F Deklarace in C je proto povolena k přepsání F zděděného zděděného z A.

end example

15.6.6 Zapečetěné metody

Když deklarace metody instance obsahuje sealed modifikátor, tato metoda se říká, že je zapečetěná metoda. Zapečetěná metoda přepíše zděděnou virtuální metodu se stejným podpisem. Zapečetěná metoda musí být také označena modifikátorem override . Použití modifikátoru sealed zabraňuje odvozené třídě v dalším přepsání metody.

Příklad: Příklad

class A
{
    public virtual void F() => Console.WriteLine("A.F");
    public virtual void G() => Console.WriteLine("A.G");
}

class B : A
{
    public sealed override void F() => Console.WriteLine("B.F");
    public override void G()        => Console.WriteLine("B.G");
}

class C : B
{
    public override void G() => Console.WriteLine("C.G");
}

třída B poskytuje dvě metody přepsání: F metoda, která má sealed modifikátor a metodu G , která není. Bpoužití modifikátoru sealed zabraňuje C dalšímu přepsání F.

end example

15.6.7 Abstraktní metody

Když deklarace metody instance obsahuje abstract modifikátor, tato metoda je řečeno, že je abstraktní metoda. I když abstraktní metoda je implicitně také virtuální metoda, nemůže mít modifikátor virtual.

Deklarace abstraktní metody zavádí novou virtuální metodu, ale neposkytuje implementaci této metody. Místo toho jsou k poskytnutí vlastní implementace vyžadovány jiné než abstraktní odvozené třídy přepsáním této metody. Vzhledem k tomu, že abstraktní metoda neposkytuje žádnou skutečnou implementaci, tělo metody abstraktní metody se jednoduše skládá z středníku.

Deklarace abstraktní metody jsou povoleny pouze v abstraktních třídách (§15.2.2.2).

Příklad: V následujícím kódu

public abstract class Shape
{
    public abstract void Paint(Graphics g, Rectangle r);
}

public class Ellipse : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawEllipse(r);
}

public class Box : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawRect(r);
}

třída Shape definuje abstraktní pojem geometrického objektu obrazce, který může malovat sám. Metoda Paint je abstraktní, protože neexistuje smysluplná výchozí implementace. Ellipse A Box třídy jsou konkrétní Shape implementace. Vzhledem k tomu, že tyto třídy nejsou abstraktní, jsou vyžadovány k přepsání Paint metody a poskytnutí skutečné implementace.

end example

Jedná se o chybu v době kompilace pro base_access (§12.8.15) odkazující na abstraktní metodu.

Příklad: V následujícím kódu

abstract class A
{
    public abstract void F();
}

class B : A
{
    // Error, base.F is abstract
    public override void F() => base.F();
}

Pro vyvolání se zobrazí base.F() chyba v době kompilace, protože odkazuje na abstraktní metodu.

end example

Deklarace abstraktní metody je povolena k přepsání virtuální metody. To umožňuje abstraktní třídě vynutit opětovné implementaci metody v odvozených třídách a znepřístupňuje původní implementaci metody.

Příklad: V následujícím kódu

class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

abstract class B: A
{
    public abstract override void F();
}

class C : B
{
    public override void F() => Console.WriteLine("C.F");
}

Třída A deklaruje virtuální metodu, třída B přepíše tuto metodu abstraktní metodou a třída C přepíše abstraktní metodu tak, aby poskytovala vlastní implementaci.

end example

15.6.8 Externí metody

Pokud deklarace metody obsahuje extern modifikátor, metoda se říká, že se jedná o externí metodu. Externí metody se implementují externě, obvykle používají jiný jazyk než C#. Vzhledem k tomu, že deklarace externí metody neposkytuje žádnou skutečnou implementaci, tělo metody externí metody se jednoduše skládá ze středníku. Externí metoda nesmí být obecná.

Mechanismus, kterým je dosaženo propojení s externí metodou, závisí na implementaci.

Příklad: Následující příklad ukazuje použití extern modifikátoru a atributu DllImport :

class Path
{
    [DllImport("kernel32", SetLastError=true)]
    static extern bool CreateDirectory(string name, SecurityAttribute sa);

    [DllImport("kernel32", SetLastError=true)]
    static extern bool RemoveDirectory(string name);

    [DllImport("kernel32", SetLastError=true)]
    static extern int GetCurrentDirectory(int bufSize, StringBuilder buf);

    [DllImport("kernel32", SetLastError=true)]
    static extern bool SetCurrentDirectory(string name);
}

end example

15.6.9 Částečné metody

Pokud deklarace metody obsahuje partial modifikátor, říká se, že tato metoda je částečnou metodou. Částečné metody lze deklarovat pouze jako členy částečných typů (§15.2.7) a podléhají řadě omezení.

Částečné metody mohou být definovány v jedné části deklarace typu a implementovány v jiné. Implementace je nepovinná; Pokud žádná část neimplementuje částečnou metodu, deklarace částečné metody a všechna volání jsou odebrána z deklarace typu, která je výsledkem kombinace částí.

Částečné metody nedefinují modifikátory přístupu; jsou implicitně soukromé. Jejich návratový typ musí být voida jejich parametry nesmí být výstupními parametry. Částečný identifikátor se rozpozná jako kontextové klíčové slovo (§6.4.4) v deklaraci metody pouze v případě, že se zobrazí bezprostředně před klíčovým slovem void . Částečná metoda nemůže explicitně implementovat metody rozhraní.

Existují dva druhy deklarací částečné metody: Pokud tělo deklarace metody je středník, deklarace se říká, že se jedná o definici částečné deklarace metody. Pokud je tělo jiné než středník, deklarace se říká, že se jedná o implementaci částečné deklarace metody. V částech deklarace typu může existovat pouze jedna definující částečná deklarace metody s daným podpisem a může existovat pouze jedna implementace částečné deklarace metody s daným podpisem. Je-li zadána prováděcí deklarace částečné metody, musí existovat odpovídající definice částečné deklarace metody a deklarace se shodují, jak je uvedeno v následujících:

  • Deklarace mají stejné modifikátory (i když nemusí nutně ve stejném pořadí), název metody, počet parametrů typu a počet parametrů.
  • Odpovídající parametry v deklaracích mají stejné modifikátory (i když nemusí nutně ve stejném pořadí) a stejné typy nebo typy konvertibilních identit (rozdíly moduluo v názvech parametrů typů).
  • Odpovídající parametry typu v deklarací mají stejná omezení (rozdíly moduluo v názvech parametrů typu).

Implementace částečné deklarace metody se může objevit ve stejné části jako odpovídající definice částečné deklarace metody.

Pouze definice částečné metody se účastní řešení přetížení. Proto zda je uvedena implementační deklarace, vyvolání výrazy mohou přeložit na vyvolání částečné metody. Vzhledem k tomu, že částečná metoda vždy vrátí void, takové vyvolání výrazy budou vždy výrazy příkazy. Vzhledem k tomu, že částečná metoda je implicitně private, takové příkazy budou vždy probíhat v jedné části deklarace typu, ve které je částečná metoda deklarována.

Poznámka: Definice odpovídající definice definování a implementace deklarací částečné metody nevyžaduje, aby se názvy parametrů shodovaly. To může vést k překvapivým, i když dobře definovaným chováním při použití pojmenovaných argumentů (§12.6.2.1). Například při definování částečné deklarace metody pro M v jednom souboru a implementaci částečné deklarace metody v jiném souboru:

// File P1.cs:
partial class P
{
    static partial void M(int x);
}

// File P2.cs:
partial class P
{
    static void Caller() => M(y: 0);
    static partial void M(int y) {}
}

je neplatný , protože vyvolání používá název argumentu z implementace a nikoli definici částečné deklarace metody.

koncová poznámka

Pokud žádná část deklarace částečného typu neobsahuje implementovanou deklaraci pro danou částečnou metodu, jakýkoli příkaz výrazu, který jej vyvolá, je jednoduše odebrán z kombinované deklarace typu. Výraz vyvolání, včetně jakýchkoli dílčích výrazů, tedy nemá žádný účinek za běhu. Samotná částečná metoda je také odstraněna a nebude členem kombinované deklarace typu.

Pokud pro danou částečnou metodu existuje implementační deklarace, zachovají se vyvolání částečných metod. Částečná metoda vede k deklaraci metody podobné implementaci částečné deklarace metody s výjimkou následujících:

  • Modifikátor partial není zahrnut.

  • Atributy ve výsledné deklaraci metody jsou kombinované atributy definice a implementace částečné deklarace metody v nezadaném pořadí. Duplicitní položky se neodeberou.

  • Atributy parametrů výsledné deklarace metody jsou kombinované atributy odpovídajících parametrů definice a implementace částečné deklarace metody v nezadaném pořadí. Duplicitní položky se neodeberou.

Pokud je pro částečnou metodu Muvedena definice deklarace, ale nikoli prováděcí deklarace, platí následující omezení:

  • Jedná se o chybu v době kompilace pro vytvoření delegáta (M§12.8.17.6).

  • Jedná se o chybu v době kompilace odkazující na M uvnitř anonymní funkce, která je převedena na typ stromu výrazu (§8.6).

  • Výrazy, ke kterým dochází jako součást vyvolání M , nemají vliv na určitý stav přiřazení (§9.4), což může potenciálně vést k chybám v době kompilace.

  • M nemůže být vstupním bodem žádosti (§7.1).

Částečné metody jsou užitečné pro povolení jedné části deklarace typu přizpůsobit chování jiné části, například jednu, která je generována nástrojem. Zvažte následující částečnou deklaraci třídy:

partial class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    partial void OnNameChanging(string newName);
    partial void OnNameChanged();
}

Pokud je tato třída zkompilována bez jakýchkoli jiných částí, definice částečné deklarace metody a jejich vyvolání budou odebrány a výsledná kombinovaná deklarace třídy bude ekvivalentní následujícímu:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set => name = value;
    }
}

Předpokládejme však, že je uvedena další část, která poskytuje prováděcí deklarace částečných metod:

partial class Customer
{
    partial void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    partial void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

Výsledná kombinovaná deklarace třídy pak bude ekvivalentní následujícímu:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

15.6.10 Rozšiřující metody

Když první parametr metody obsahuje this modifikátor, tato metoda se říká, že se jedná o rozšiřující metodu. Rozšiřující metody se deklarují pouze v ne generických statických třídách, které nejsou vnořené. První parametr metody rozšíření je omezen následujícím způsobem:

  • Může se jednat pouze o vstupní parametr, pokud má typ hodnoty.
  • Parametr odkazu může být pouze v případě, že má typ hodnoty nebo má obecný typ omezený na strukturu.
  • Nesmí se jednat o typ ukazatele.

Příklad: Následuje příklad statické třídy, která deklaruje dvě rozšiřující metody:

public static class Extensions
{
    public static int ToInt32(this string s) => Int32.Parse(s);

    public static T[] Slice<T>(this T[] source, int index, int count)
    {
        if (index < 0 || count < 0 || source.Length - index < count)
        {
            throw new ArgumentException();
        }
        T[] result = new T[count];
        Array.Copy(source, index, result, 0, count);
        return result;
    }
}

end example

Rozšiřující metoda je běžná statická metoda. Kromě toho, pokud je její uzavření statické třídy v oboru, lze metodu rozšíření vyvolat pomocí syntaxe vyvolání metody instance (§12.8.10.3) pomocí výrazu příjemce jako prvního argumentu.

Příklad: Následující program používá metody rozšíření deklarované výše:

static class Program
{
    static void Main()
    {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in strings.Slice(1, 2))
        {
            Console.WriteLine(s.ToInt32());
        }
    }
}

Metoda Slice je k dispozici na string[]a ToInt32 metoda je k dispozici na string, protože byly deklarovány jako rozšiřující metody. Význam programu je stejný jako následující, pomocí běžných volání statické metody:

static class Program
{
    static void Main()
    {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in Extensions.Slice(strings, 1, 2))
        {
            Console.WriteLine(Extensions.ToInt32(s));
        }
    }
}

end example

Tělo metody 15.6.11

Tělo metody deklarace metody se skládá z těla bloku, těla výrazu nebo středníku.

Abstraktní a externí deklarace metody neposkytují implementaci metody, takže jejich těla metody se jednoduše skládají z středníku. Pro jakoukoli jinou metodu je tělo metody blok (§13.3), který obsahuje příkazy, které se mají provést při vyvolání této metody.

Účinný návratový typ metody jevoid, pokud je voidnávratový typ , nebo je-li metoda asynchronní a návratový typ je «TaskType» (§15.15.1). V opačném případě je účinný návratový typ nesynchronní metody jeho návratovým typem a účinný návratový typ asynchronní metody s návratovým typem «TaskType»<T>(§15.15.1) je T.

Je-li účinný návratový typ metody void a metoda má blokový orgán, return prohlášení (§13.10.5) v bloku nezadá výraz. Pokud se provádění bloku metody void dokončí normálně (tj. řízení toků mimo konec těla metody), tato metoda se jednoduše vrátí do volajícího.

Je-li účinný návratový typ metody void a metoda má tělo výrazu, výraz E musí být statement_expression a tělo je přesně ekvivalentní blokové tělu formuláře { E; }.

Pro metodu return-by-value (§15.6.1) musí každý návratový příkaz v těle této metody zadat výraz, který je implicitně konvertibilní na efektivní návratový typ.

Pro metodu return-by-ref (§15.6.1) musí každý návratový příkaz v těle této metody určit výraz, jehož typem je efektivní návratový typ, a má odkazově bezpečný kontext kontextu volajícího (§9.7.2).

V případě metod vrácení po hodnotě a návratových metod není koncový bod těla metody dosažitelný. Jinými slovy, ovládací prvek není povolen, aby tok z konce těla metody.

Příklad: V následujícím kódu

class A
{
    public int F() {} // Error, return value required

    public int G()
    {
        return 1;
    }

    public int H(bool b)
    {
        if (b)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    public int I(bool b) => b ? 1 : 0;
}

Výsledkem metody vracející F hodnotu je chyba v době kompilace, protože řízení může dojít mimo konec těla metody. G A H metody jsou správné, protože všechny možné cesty provádění končí v návratovém příkazu, který určuje návratovou hodnotu. Metoda I je správná, protože její tělo je ekvivalentní bloku pouze s jedním návratovým příkazem v něm.

end example

15.7 Možností ubytování

15.7.1 Obecné

Vlastnost je člen, který poskytuje přístup k vlastnosti objektu nebo třídy. Mezi příklady vlastností patří délka řetězce, velikost písma, titulek okna a jméno zákazníka. Vlastnosti jsou přirozené rozšíření polí – oba jsou pojmenované členy s přidruženými typy a syntaxe pro přístup k polím a vlastnostem je stejná. Na rozdíl od polí však vlastnosti neoznačí umístění úložiště. Místo toho mají vlastnosti přístupové objekty , které určují příkazy, které mají být provedeny při čtení nebo zápisu jejich hodnot. Vlastnosti tak poskytují mechanismus pro přidružení akcí ke čtení a zápisu vlastností objektu nebo třídy; navíc umožňují, aby se tyto charakteristiky počítaly.

Vlastnosti jsou deklarovány pomocí property_declarations:

property_declaration
    : attributes? property_modifier* type member_name property_body
    | attributes? property_modifier* ref_kind type member_name ref_property_body
    ;    

property_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;
    
property_body
    : '{' accessor_declarations '}' property_initializer?
    | '=>' expression ';'
    ;

property_initializer
    : '=' variable_initializer ';'
    ;

ref_property_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Existují dva druhy property_declaration:

  • První deklaruje nehodnocenou vlastnost. Jeho hodnota má typ typu. Tento druh vlastnosti může být čitelný nebo zapisovatelný.
  • Druhá deklaruje vlastnost ref-valued. Jeho hodnota je variable_reference (§9,5 Tento druh vlastnosti je jen čitelný.

Property_declaration může obsahovat sadu atributů (§22) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new (§15.3.5), static (§15.7.2virtual, §15.7.6), (override, §15.7.6), (sealed), (abstract, §15.7.6) a (extern) modifikátory.

Deklarace vlastností podléhají stejným pravidlům jako deklarace metody (§15.6) s ohledem na platné kombinace modifikátorů.

Member_name (§15.6.1) určuje název nemovitosti. Pokud není vlastnost explicitní implementací člena rozhraní, member_name je jednoduše identifikátor. Pro explicitní implementaci člena rozhraní (§18.6.2) se member_name skládá z interface_type následovaného "." a identifikátorem.

Typ nemovitosti musí být alespoň tak přístupný jako vlastní majetek (§7.5.5).

Property_body se může skládat z textu příkazu nebo textu výrazu. V prohlášení , accessor_declarations, který musí být uzavřen v "{" a "}" tokeny, deklarujte příslušenství (§15.7.3) nemovitosti. Přístupové objekty určují spustitelné příkazy přidružené ke čtení a zápisu vlastnosti.

V property_body tělo výrazu sestávající z následovaného => výrazem E a středník je přesně ekvivalentní textu příkazu { get { return E; } }, a proto lze použít pouze k určení vlastností jen pro čtení, kde výsledek přístupového objektu get je dán jediným výrazem.

Property_initializer lze poskytnout pouze pro automaticky implementovanou vlastnost (§15.7.4) a způsobí inicializaci podkladového pole těchto vlastností s hodnotou danou výrazem.

Ref_property_body se může skládat z těla příkazu nebo textu výrazu. V prohlášení orgán get_accessor_declaration deklaruje get příslušenství (§15.7.3) nemovitosti. Přístupové objekty určují spustitelné příkazy přidružené ke čtení vlastnosti.

V ref_property_body

Poznámka: I když syntaxe pro přístup k vlastnosti je stejná jako u pole, vlastnost není klasifikována jako proměnná. Nelze tedy předat vlastnost jako in, outnebo ref argument, pokud je vlastnost ref-valued, a proto vrátí proměnnou odkaz (§9.7). koncová poznámka

Pokud deklarace vlastnosti obsahuje extern modifikátor, vlastnost je řečeno, že je externí vlastností. Vzhledem k tomu, že prohlášení o vnější vlastnosti neposkytuje žádnou skutečnou implementaci, musí být každý z accessor_bodyv jeho accessor_declarations středníkem.

15.7.2 Statické vlastnosti a vlastnosti instance

Pokud deklarace vlastnosti obsahuje static modifikátor, vlastnost je řečeno, že je statická vlastnost. Pokud není k dispozici žádný static modifikátor, vlastnost se říká, že je to vlastnost instance.

Statická vlastnost není přidružená ke konkrétní instanci a jedná se o chybu v době kompilace odkazující na this přístupové objekty statické vlastnosti.

Vlastnost instance je přidružena k dané instanci třídy a k této instanci lze přistupovat jako this (§12.8.14) v přistupujícími objekty této vlastnosti.

Rozdíly mezi statickými členy a členy instance jsou popsány dále v §15.3.8.

15.7.3 Příslušenství

Poznámka: Tato klauzule se vztahuje na obě vlastnosti (§15.7) a indexery (§15.9). Klauzule se zapisuje z hlediska vlastností, při čtení indexerů nahradí indexer/indexery vlastnost/vlastnosti a prohlédnou seznam rozdílů mezi vlastnostmi a indexery zadanými v §15.9.2. koncová poznámka

Accessor_declarations vlastnosti určují spustitelné příkazy přidružené k zápisu nebo čtení této vlastnosti.

accessor_declarations
    : get_accessor_declaration set_accessor_declaration?
    | set_accessor_declaration get_accessor_declaration?
    ;

get_accessor_declaration
    : attributes? accessor_modifier? 'get' accessor_body
    ;

set_accessor_declaration
    : attributes? accessor_modifier? 'set' accessor_body
    ;

accessor_modifier
    : 'protected'
    | 'internal'
    | 'private'
    | 'protected' 'internal'
    | 'internal' 'protected'
    | 'protected' 'private'
    | 'private' 'protected'
    ;

accessor_body
    : block
    | '=>' expression ';'
    | ';' 
    ;

ref_get_accessor_declaration
    : attributes? accessor_modifier? 'get' ref_accessor_body
    ;
    
ref_accessor_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

Accessor_declarations se skládají z get_accessor_declaration, set_accessor_declaration nebo obojího. Každá deklarace přístupového objektu se skládá z volitelných atributů, volitelného accessor_modifier, tokenu nebo get, set následovaného accessor_body.

Pro ref-valued vlastnost ref_get_accessor_declaration skládá volitelné atributy, volitelné accessor_modifier, token getnásledovaný ref_accessor_body.

Použití accessor_modifierse řídí následujícími omezeními:

  • Accessor_modifier se nepoužije v rozhraní ani v explicitní implementaci člena rozhraní.
  • U vlastnosti nebo indexeru, který nemá žádný override modifikátor, je accessor_modifier povolen pouze v případě, že vlastnost nebo indexer má přístupové objekty get a set, a pak je povolen pouze na jednom z těchto přístupových objektů.
  • U vlastnosti nebo indexeru override , který obsahuje modifikátor, musí přistupovací objekt odpovídat accessor_modifier, pokud existuje, přepsaného přístupového objektu.
  • Accessor_modifier deklaruje přístupnost, která je přísně omezenější než deklarovaná přístupnost vlastnosti nebo samotného indexeru. Přesné:
    • Pokud má vlastnost nebo indexer deklarovanou přístupnost public, může být přístupnost deklarovaná private protected buď , , protected internal, internal, protectednebo private.
    • Pokud má vlastnost nebo indexer deklarovanou přístupnost protected internal, může být přístupnost deklarovaná private protected buď , , protected private, internal, protectednebo private.
    • Pokud má vlastnost nebo indexer deklarovanou přístupnost internalprotectednebo , musí být přístupnost deklarovaná accessor_modifier buď private protected nebo private.
    • Pokud má vlastnost nebo indexer deklarovanou přístupnost private protected, musí být přístupnost deklarovaná private .
    • Pokud má vlastnost nebo indexer deklarovanou přístupnost private, nelze použít žádné accessor_modifier .

Pro abstract vlastnosti bez extern ref-value, všechny accessor_body pro každé zadané přístupové objekty je jednoduše středník. Ne abstraktní, ne externí vlastnost, ale ne indexer, může mít také accessor_body pro všechny přístupové objekty určené středníkem, v takovém případě se jedná o automaticky implementovanou vlastnost (§15.7.4). Automaticky implementovaná vlastnost musí mít alespoň přístupový objekt get. Pro přístupové objekty jakékoli jiné ne abstraktní, non-extern vlastnost, accessor_body je buď:

  • blok, který určuje příkazy, které se mají spustit při vyvolání odpovídajícího přístupového objektu; nebo
  • tělo výrazu, které se skládá z => výrazu a středníku, a označuje jeden výraz, který se má provést při vyvolání odpovídajícího přístupového objektu.

Pro abstract vlastnosti ref-valued externref_accessor_body je jednoduše středník. Pro přístup k jakékoli jiné non-abstraktní, non-extern vlastnost, ref_accessor_body je buď:

  • blok, který určuje příkazy, které se mají spustit při vyvolání přístupového objektu get; nebo
  • tělo výrazu, které se skládá z => následovaného ref, variable_reference a středníku. Odkaz na proměnnou se vyhodnotí při vyvolání přístupového objektu get.

Get accessor for a non-ref-valued vlastnost odpovídá parametrless metoda s návratovou hodnotou typu vlastnosti. S výjimkou cíle přiřazení, je-li taková vlastnost odkazována ve výrazu, který je jeho get accessor vyvolána k výpočtu hodnoty vlastnosti (§12.2.2).

Subjekt get accessor pro vlastnost, která není ref-valued, odpovídá pravidlům pro metody vrácení hodnoty popsané v §15.6.11. Zejména všechny return příkazy v těle přístupového objektu get musí určovat výraz, který je implicitně konvertibilní na typ vlastnosti. Koncový bod přístupového objektu get navíc není dostupný.

Get accessor for a ref-valued property odpovídá parametrless method with a return value of a variable_reference to a variable of the property type. Pokud je taková vlastnost odkazována ve výrazu, který jeho get accessor je vyvolána pro výpočet variable_reference hodnotu vlastnosti. Tento odkaz na proměnnou, stejně jako jakýkoli jiný, se pak použije ke čtení nebo pro nečtené variable_references, zapište odkazovanou proměnnou podle potřeby kontextu.

Příklad: Následující příklad znázorňuje vlastnost ref-valued jako cíl přiřazení:

class Program
{
    static int field;
    static ref int Property => ref field;

    static void Main()
    {
        field = 10;
        Console.WriteLine(Property); // Prints 10
        Property = 20;               // This invokes the get accessor, then assigns
                                     // via the resulting variable reference
        Console.WriteLine(field);    // Prints 20
    }
}

end example

Subjekt get accessor pro vlastnost s ref-hodnotou musí odpovídat pravidlům pro metody ref-valued popsané v §15.6.11.

Objekt set odpovídá metodě s jedním parametrem hodnoty typu vlastnosti a návratovým typem void . Implicitní parametr přístupového objektu sady je vždy pojmenován value. Pokud je vlastnost odkazována jako cíl přiřazení (§12.21), nebo jako operand nebo ++–- (§12.8.16, §12.9.6), je přistupující sada vyvolána argumentem, který poskytuje novou hodnotu (§12.21.2). Subjekt množiny musí odpovídat pravidlům pro void metody popsané v §15.6.11. Zejména návratové příkazy v těle objektu set nejsou povoleny k určení výrazu. Vzhledem k tomu, že objekt set accessor má implicitně pojmenovaný valueparametr , jedná se o chybu v době kompilace pro místní proměnnou nebo deklaraci konstanty v přístupovém objektu sady, aby měl tento název.

Na základě přítomnosti nebo nepřítomnosti přístupových objektů get a set se vlastnost klasifikuje takto:

  • Vlastnost, která zahrnuje přístupové objekty get i objekt set, se říká, že se jedná o vlastnost pro čtení i zápis.
  • Vlastnost, která má pouze přístupové objekty get, se říká, že se jedná o vlastnost jen pro čtení. Jedná se o chybu v době kompilace, kdy vlastnost jen pro čtení představuje cíl přiřazení.
  • Vlastnost, která má pouze objekt set, je řečeno, že je jen pro zápis vlastnost. S výjimkou cíle přiřazení se jedná o chybu v době kompilace odkazování na vlastnost jen pro zápis ve výrazu.

Poznámka: U vlastností jen pro zápis nelze použít operátory pre- a postfix ++ a operátory a -- složené operátory přiřazení, protože tyto operátory před zápisem nového operandu čtou starou hodnotu svého operandu. koncová poznámka

Příklad: V následujícím kódu

public class Button : Control
{
    private string caption;

    public string Caption
    {
        get => caption;
        set
        {
            if (caption != value)
            {
                caption = value;
                Repaint();
            }
        }
    }

    public override void Paint(Graphics g, Rectangle r)
    {
        // Painting code goes here
    }
}

ovládací Button prvek deklaruje veřejnou Caption vlastnost. Get accessor of the Caption vlastnost vrátí string uložené v privátním caption poli. Objekt set zkontroluje, jestli se nová hodnota liší od aktuální hodnoty, a pokud ano, uloží novou hodnotu a přeformátuje ovládací prvek. Vlastnosti často následují podle výše uvedeného vzoru: Get Accessor jednoduše vrátí hodnotu uloženou private v poli a objekt set změní toto private pole a pak provede všechny další akce potřebné k aktualizaci stavu objektu. Výše uvedená Button třída představuje příklad použití Caption vlastnosti:

Button okButton = new Button();
okButton.Caption = "OK"; // Invokes set accessor
string s = okButton.Caption; // Invokes get accessor

V této části je objekt set vyvolán přiřazením hodnoty vlastnosti a get accessor je vyvolán odkazem na vlastnost ve výrazu.

end example

Přístupové objekty get a set vlastnosti nejsou jedinečné členy a není možné deklarovat přístupové objekty vlastnosti samostatně.

Příklad: Příklad

class A
{
    private string name;

    // Error, duplicate member name
    public string Name
    { 
        get => name;
    }

    // Error, duplicate member name
    public string Name
    { 
        set => name = value;
    }
}

deklaruje jednu vlastnost pro čtení i zápis. Místo toho deklaruje dvě vlastnosti se stejným názvem, jednou jen pro čtení a jen pro zápis. Vzhledem k tomu, že dva členy deklarované ve stejné třídě nemohou mít stejný název, způsobí to, že v příkladu dojde k chybě kompilace.

end example

Když odvozená třída deklaruje vlastnost se stejným názvem jako zděděná vlastnost, odvozená vlastnost skryje zděděnou vlastnost s ohledem na čtení i zápis.

Příklad: V následujícím kódu

class A
{
    public int P
    {
        set {...}
    }
}

class B : A
{
    public new int P
    {
        get {...}
    }
}

vlastnost PB skryje P vlastnost A v souvislosti s čtením i zápisem. Proto v příkazech

B b = new B();
b.P = 1;       // Error, B.P is read-only
((A)b).P = 1;  // Ok, reference to A.P

přiřazení, které b.P způsobí nahlášenou chybu v době kompilace, protože vlastnost P jen pro B čtení skryje vlastnost jen P pro zápis v A. Všimněte si však, že přetypování lze použít pro přístup ke skryté P vlastnosti.

end example

Na rozdíl od veřejných polí poskytují vlastnosti oddělení mezi interním stavem objektu a jeho veřejným rozhraním.

Příklad: Představte si následující kód, který používá Point strukturu k reprezentaci umístění:

class Label
{
    private int x, y;
    private string caption;

    public Label(int x, int y, string caption)
    {
        this.x = x;
        this.y = y;
        this.caption = caption;
    }

    public int X => x;
    public int Y => y;
    public Point Location => new Point(x, y);
    public string Caption => caption;
}

Label V této třídě se používají dvě int pole x a yk uložení jeho umístění. Umístění je veřejně vystaveno jak jako vlastnost XY , tak jako Location vlastnost typu Point. Pokud se v budoucí verzi Label, stává se pohodlnější uložit umístění jako Point interně, může být změna provedena, aniž by to mělo vliv na veřejné rozhraní třídy:

class Label
{
    private Point location;
    private string caption;

    public Label(int x, int y, string caption)
    {
        this.location = new Point(x, y);
        this.caption = caption;
    }

    public int X => location.X;
    public int Y => location.Y;
    public Point Location => location;
    public string Caption => caption;
}

Kdyby x a y místo toho byla public readonly pole, bylo by nemožné provést takovou změnu Label třídy.

end example

Poznámka: Zveřejnění stavu prostřednictvím vlastností nemusí nutně být méně efektivní než přímé vystavení polí. Konkrétně platí, že pokud vlastnost není virtuální a obsahuje pouze malé množství kódu, může spouštěcí prostředí nahradit volání přístupových objektů skutečným kódem přístupových objektů. Tento proces se označuje jako vkládání a umožňuje přístup k vlastnostem jako efektivní jako přístup k polím, ale zachovává větší flexibilitu vlastností. koncová poznámka

Příklad: Vzhledem k tomu, že vyvolání přístupového objektu get je koncepčně ekvivalentní čtení hodnoty pole, považuje se za špatný programovací styl pro získání přístupových objektů, které mají pozorovatelné vedlejší účinky. V příkladu

class Counter
{
    private int next;

    public int Next => next++;
}

hodnota Next vlastnosti závisí na tom, kolikrát byla vlastnost dříve přístupná. Proto přístup k vlastnosti vytváří pozorovatelný vedlejší účinek a vlastnost by měla být implementována jako metoda.

Konvence "žádné vedlejší účinky" pro přístupové objekty get neznamená, že přístupové objekty get by měly být vždy zapsány jednoduše za účelem vrácení hodnot uložených v polích. Získání přístupových objektů často vypočítá hodnotu vlastnosti přístupem k více polím nebo vyvoláním metod. Správně navržený přístupový objekt get však neprovádí žádné akce, které způsobují pozorovatelné změny ve stavu objektu.

end example

Vlastnosti lze použít ke zpoždění inicializace prostředku do okamžiku, kdy se na něj poprvé odkazuje.

Příklad:

public class Console
{
    private static TextReader reader;
    private static TextWriter writer;
    private static TextWriter error;

    public static TextReader In
    {
        get
        {
            if (reader == null)
            {
                reader = new StreamReader(Console.OpenStandardInput());
            }
            return reader;
        }
    }

    public static TextWriter Out
    {
        get
        {
            if (writer == null)
            {
                writer = new StreamWriter(Console.OpenStandardOutput());
            }
            return writer;
        }
    }

    public static TextWriter Error
    {
        get
        {
            if (error == null)
            {
                error = new StreamWriter(Console.OpenStandardError());
            }
            return error;
        }
    }
...
}

Třída Console obsahuje tři vlastnosti, , InOuta Error, které představují standardní vstup, výstup a chybové zařízení, v uvedeném pořadí. Zveřejněním těchto členů jako vlastností může třída zpozdit jejich inicializaci, Console dokud se skutečně nepoužívají. Například při prvním odkazování na Out vlastnost, jako v

Console.Out.WriteLine("hello, world");

vytvoří se podklad TextWriter pro výstupní zařízení. Pokud však aplikace nebude odkazovat na vlastnosti In a Error vlastnosti, nebudou se pro tato zařízení vytvářet žádné objekty.

end example

15.7.4 Automaticky implementované vlastnosti

Automaticky implementovaná vlastnost (nebo automatická vlastnost pro short) je ne abstraktní, non-extern, non-ref-valued vlastnost s středníkem pouze accessor_bodys. Automatické vlastnosti musí mít přístupové objekty get a mohou mít volitelně nastavené příslušenství.

Pokud je vlastnost zadána jako automaticky implementovaná vlastnost, skryté backing pole je automaticky k dispozici pro vlastnost a přistupující objekty jsou implementovány pro čtení a zápis do daného záložního pole. Skryté záložní pole je nepřístupné, může být přečteno a zapsáno pouze prostřednictvím automaticky implementovaných přístupových objektů vlastností, a to i v rámci obsahujícího typu. Pokud vlastnost auto-property nemá žádné nastavení příslušenství, je považováno za záložní pole readonly (§15.5.3). Stejně jako readonly pole může být v těle konstruktoru nadřazené třídy přiřazena také automatická vlastnost jen pro čtení. Takové přiřazení přiřadí přímo backingové pole vlastnosti jen pro čtení.

Automatická vlastnost může mít volitelně property_initializer, která se použije přímo na zadní pole jako variable_initializer (§17.7).

Příklad:

public class Point
{
    public int X { get; set; } // Automatically implemented
    public int Y { get; set; } // Automatically implemented
}

je ekvivalentní následující deklaraci:

public class Point
{
    private int x;
    private int y;

    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}

end example

Příklad: V následujícím příkladu

public class ReadOnlyPoint
{
    public int X { get; }
    public int Y { get; }

    public ReadOnlyPoint(int x, int y)
    {
        X = x;
        Y = y;
    }
}

je ekvivalentní následující deklaraci:

public class ReadOnlyPoint
{
    private readonly int __x;
    private readonly int __y;
    public int X { get { return __x; } }
    public int Y { get { return __y; } }

    public ReadOnlyPoint(int x, int y)
    {
        __x = x;
        __y = y;
    }
}

Přiřazení k poli jen pro čtení jsou platná, protože se vyskytují v rámci konstruktoru.

end example

I když je backingové pole skryté, toto pole může obsahovat atributy cílené na pole přímo na něj prostřednictvím automaticky implementovaného majetku property_declaration (§15.7.1).

Příklad: Následující kód

[Serializable]
public class Foo
{
    [field: NonSerialized]
    public string MySecret { get; set; }
}

výsledkem je použití atributu NonSerialized cíleného na pole kompilátoru generovaného backingem, jako by kód byl napsán takto:

[Serializable]
public class Foo
{
    [NonSerialized]
    private string _mySecretBackingField;
    public string MySecret
    {
        get { return _mySecretBackingField; }
        set { _mySecretBackingField = value; }
    }
}

end example

15.7.5 Přístupnost

Pokud má příslušenství accessor_modifier, určí se doména přístupnosti (§7.5.3) přístupového objektu pomocí deklarované přístupnosti accessor_modifier. Pokud příslušenství nemá accessor_modifier, je doména přístupnosti přístupového objektu určena z deklarované přístupnosti vlastnosti nebo indexeru.

Přítomnost accessor_modifier nikdy neovlivní vyhledávání členů (§12.5) nebo řešení přetížení (§12.6.4). Modifikátory vlastnosti nebo indexeru vždy určují, ke které vlastnosti nebo indexeru je vázána bez ohledu na kontext přístupu.

Jakmile je vybrána konkrétní nehodnocená vlastnost nebo indexer bez hodnoty ref, použijí se domény přístupnosti příslušných přístupových objektů k určení, jestli je toto použití platné:

  • Je-li použití jako hodnota (§12.2.2), musí být přístupový objekt get k dispozici a přístupný.
  • Pokud je použití jako cíl jednoduchého přiřazení (§12.21.2), musí existovat a být přístupný.
  • Je-li použití jako cíl složeného přiřazení (§12.21.4) nebo jako cíl ++ operátorů (--§12.8.16, §12.9.6), musí existovat a být přístupný.

Příklad: V následujícím příkladu je vlastnost skryta vlastností A.TextB.Text, a to i v kontextech, kde je volána pouze sada přístup. Naproti tomu vlastnost B.Count není přístupná pro třídu M, takže je použita přístupná vlastnost A.Count .

class A
{
    public string Text
    {
        get => "hello";
        set { }
    }

    public int Count
    {
        get => 5;
        set { }
    }
}

class B : A
{
    private string text = "goodbye";
    private int count = 0;

    public new string Text
    {
        get => text;
        protected set => text = value;
    }

    protected new int Count
    {
        get => count;
        set => count = value;
    }
}

class M
{
    static void Main()
    {
        B b = new B();
        b.Count = 12;       // Calls A.Count set accessor
        int i = b.Count;    // Calls A.Count get accessor
        b.Text = "howdy";   // Error, B.Text set accessor not accessible
        string s = b.Text;  // Calls B.Text get accessor
    }
}

end example

Jakmile je vybrána konkrétní vlastnost ref-valued nebo indexer ref-valued – ať už je použití jako hodnota, cíl jednoduchého přiřazení nebo cíle složeného přiřazení – doména přístupnosti příslušného přístupového objektu get slouží k určení, jestli je toto použití platné.

Přístup, který se používá k implementaci rozhraní, nesmí mít accessor_modifier. Pokud se k implementaci rozhraní používá pouze jeden přístupový objekt, může být druhý přístup deklarován pomocí accessor_modifier:

Příklad:

public interface I
{
    string Prop { get; }
}

public class C : I
{
    public string Prop
    {
        get => "April";     // Must not have a modifier here
        internal set {...}  // Ok, because I.Prop has no set accessor
    }
}

end example

15.7.6 Virtuální, zapečetěné, přepsání a abstraktní přístupové objekty

Poznámka: Tato klauzule se vztahuje na obě vlastnosti (§15.7) a indexery (§15.9). Klauzule se zapisuje z hlediska vlastností, při čtení indexerů nahradí indexer/indexery vlastnost/vlastnosti a prohlédnou seznam rozdílů mezi vlastnostmi a indexery zadanými v §15.9.2. koncová poznámka

Deklarace virtuální vlastnosti určuje, že přístupové objekty vlastnosti jsou virtuální. Modifikátor virtual se vztahuje na všechny nesoukromého přístupového objektu vlastnosti. Pokud má přístup k virtuální vlastnosti privateaccessor_modifier, privátní přístup implicitně není virtuální.

Abstraktní deklarace vlastnosti určuje, že přístupové objekty vlastnosti jsou virtuální, ale neposkytuje skutečnou implementaci přístupových objektů. Místo toho jsou k poskytnutí vlastní implementace přístupových objektů vyžadovány jiné než abstraktní odvozené třídy přepsáním vlastnosti. Vzhledem k tomu, že přístup k deklaraci abstraktní vlastnosti neposkytuje žádnou skutečnou implementaci, jeho accessor_body se jednoduše skládá ze středníku. Abstraktní vlastnost nesmí mít private příslušenství.

Deklarace vlastnosti, která zahrnuje jak modifikátory abstractoverride , určuje, že vlastnost je abstraktní a přepisuje základní vlastnost. Přístupové objekty takové vlastnosti jsou také abstraktní.

Abstraktní deklarace vlastností jsou povoleny pouze v abstraktních třídách (§15.2.2.2).2). Přístupové objekty zděděné virtuální vlastnosti lze přepsat v odvozené třídě zahrnutím deklarace vlastnosti, která určuje direktivu override . Označuje se jako deklarace vlastnosti přepsání. Deklarace přepsání vlastnosti deklaruje novou vlastnost. Místo toho se jednoduše specializuje na implementace přístupových objektů existující virtuální vlastnosti.

Deklarace přepsání a přepsaná základní vlastnost musí mít stejnou deklarovanou přístupnost. Jinými slovy, deklarace přepsání nezmění přístupnost základní vlastnosti. Pokud je však přepsaná základní vlastnost chráněna interně a je deklarována v jiném sestavení než sestavení obsahující deklaraci přepsání, bude deklarována deklarace přepsání přístupnosti chráněna. Pokud zděděná vlastnost má pouze jeden přístupový objekt (tj. pokud je zděděná vlastnost jen pro čtení nebo jen pro zápis), přepisovaná vlastnost musí obsahovat pouze tento přístupový objekt. Pokud zděděná vlastnost zahrnuje oba přístupové objekty (tj. pokud je zděděná vlastnost jen pro čtení i zápis), může přepsání zahrnovat buď jeden přístupový objekt, nebo oba přístupové objekty. Mezi typem přepsání a zděděnou vlastností musí existovat převod identity.

Přepsání deklarace vlastnosti může zahrnovat sealed modifikátor. Použití tohoto modifikátoru zabraňuje odvozené třídě v dalším přepsání vlastnosti. Příslušenství zapečetěné vlastnosti jsou také zapečetěné.

S výjimkou rozdílů v deklaraci a vyvolání syntaxe, virtuální, zapečetěné, přepsání a abstraktní přístupové objekty se chovají přesně jako virtuální, zapečetěné, přepsání a abstraktní metody. Konkrétně platí, že pravidla popsaná v §15.6.4, §15.6.5, §15.6.6 a §15.6.7 se vztahují, jako by byly metody příslušenství odpovídajícího formuláře:

  • Get Accessor odpovídá metodě bez parametrů s návratovou hodnotou typu vlastnosti a stejnými modifikátory jako obsahující vlastnost.
  • Objekt set odpovídá metodě s jedním parametrem hodnoty typu vlastnosti, návratovým typem void a stejnými modifikátory jako obsahující vlastnost.

Příklad: V následujícím kódu

abstract class A
{
    int y;

    public virtual int X
    {
        get => 0;
    }

    public virtual int Y
    {
        get => y;
        set => y = value;
    }

    public abstract int Z { get; set; }
}

X je virtuální vlastnost jen pro čtení, Y je virtuální vlastností pro čtení i zápis a Z je abstraktní vlastností pro čtení i zápis. Vzhledem k tomu Z , že je abstraktní, musí být i třída A deklarována abstraktně.

Níže je uvedena třída odvozená od A :

class B : A
{
    int z;

    public override int X
    {
        get => base.X + 1;
    }

    public override int Y
    {
        set => base.Y = value < 0 ? 0: value;
    }

    public override int Z
    {
        get => z;
        set => z = value;
    }
}

Zde jsou deklarace X, Ya Z jsou přepsány deklarace vlastností. Každá deklarace vlastnosti přesně odpovídá modifikátorům přístupnosti, typu a názvu odpovídající zděděné vlastnosti. Get accessor of X a set accessor of use the Y base keyword to access the zděděné přístupové objekty. Deklarace přepsání jak abstraktních přístupových Z objektů, tak neexistují žádné nevyřízené abstract členy Bfunkce a B je povoleno být ne-abstraktní třídou.

end example

Pokud je vlastnost deklarována jako přepsání, musí být všechny přepsané přístupové objekty přístupné přepisovacímu kódu. Kromě toho deklarovaná přístupnost samotné vlastnosti nebo indexeru a přístupových objektů musí odpovídat vlastnosti a příslušenství přepsaného člena a přístupových objektů.

Příklad:

public class B
{
    public virtual int P
    {
        get {...}
        protected set {...}
    }
}

public class D: B
{
    public override int P
    {
        get {...}            // Must not have a modifier here
        protected set {...}  // Must specify protected here
    }
}

end example

15.8 Události

15.8.1 Obecné

Událost je člen, který umožňuje objektu nebo třídě poskytovat oznámení. Klienti mohou připojit spustitelný kód pro události zadáním obslužných rutin událostí.

Události se deklarují pomocí event_declarations:

event_declaration
    : attributes? event_modifier* 'event' type variable_declarators ';'
    | attributes? event_modifier* 'event' type member_name
        '{' event_accessor_declarations '}'
    ;

event_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

event_accessor_declarations
    : add_accessor_declaration remove_accessor_declaration
    | remove_accessor_declaration add_accessor_declaration
    ;

add_accessor_declaration
    : attributes? 'add' block
    ;

remove_accessor_declaration
    : attributes? 'remove' block
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Event_declaration může obsahovat sadu atributů (§22) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new (§15.3.5), static (§15.6.3, §15.8.4), virtual (§15.6.4, §15.8.5), override (§15.6.5, §15.8.5), sealed (§15.6.6), abstract (§15.6.7, §15.8.5) a extern (§15.6.8) modifikátory.

Deklarace událostí podléhají stejným pravidlům jako deklarace metody (§15.6) s ohledem na platné kombinace modifikátorů.

Typ prohlášení o události je delegate_type (§8.2.8) a že delegate_type musí být alespoň tak přístupné jako samotná událost (§7.5.5).

Deklarace události může obsahovat event_accessor_declarations. Pokud tomu tak není, pro neexterní a neabstraktní události je kompilátor poskytne automaticky (§15.8.2); pro události extern jsou přístupové metody poskytovány externě.

Deklarace události, která vynechá event_accessor_declarations definuje jednu nebo více událostí – jednu pro každou z variable_declarators. Atributy a modifikátory se vztahují na všechny členy deklarované takovým event_declaration.

Jedná se o chybu v době kompilace, kdy event_declaration zahrnout abstract modifikátor i event_accessor_declarations.

Pokud deklarace události obsahuje extern modifikátor, říká se, že událost je externí událost. Vzhledem k tomu, že deklarace externí události neposkytuje žádnou skutečnou implementaci, jedná se o chybu, která zahrnuje extern modifikátor i event_accessor_declarations.

Jedná se o chybu v době kompilace pro variable_declarator deklarace události s nebo abstract modifikátorem external.

Událost lze použít jako levý operand += operátorů a -= operátory. Tyto operátory se používají k připojení obslužných rutin událostí nebo k odebrání obslužných rutin událostí z události a modifikátory přístupu ovládacího prvku události kontexty, ve kterých jsou tyto operace povoleny.

Jediné operace, které jsou povoleny u události kódem, který je mimo typ, ve kterém je tato událost deklarována, jsou += a -=. I když takový kód může přidávat a odebírat obslužné rutiny pro událost, nemůže přímo získat ani upravit podkladový seznam obslužných rutin událostí.

Při operaci formuláře x += y nebo , pokud x –= y je výsledkem operace typ x (void) (na rozdíl od typu za přiřazením s hodnotou x za přiřazením, stejně jako u jiných x+= operátorů definovaných v jiných typech -=než událostí). To brání externímu kódu v nepřímém zkoumání podkladového delegáta události.

Příklad: Následující příklad ukazuje, jak jsou obslužné rutiny událostí připojeny k instancím Button třídy:

public delegate void EventHandler(object sender, EventArgs e);

public class Button : Control
{
    public event EventHandler Click;
}

public class LoginDialog : Form
{
    Button okButton;
    Button cancelButton;

    public LoginDialog()
    {
        okButton = new Button(...);
        okButton.Click += new EventHandler(OkButtonClick);
        cancelButton = new Button(...);
        cancelButton.Click += new EventHandler(CancelButtonClick);
    }

    void OkButtonClick(object sender, EventArgs e)
    {
        // Handle okButton.Click event
    }

    void CancelButtonClick(object sender, EventArgs e)
    {
        // Handle cancelButton.Click event
    }
}

LoginDialog Zde konstruktor instance vytvoří dvě Button instance a připojí obslužné rutiny událostí k událostemClick.

end example

15.8.2 Události podobné polím

V rámci programu textu třídy nebo struktury, která obsahuje deklaraci události, lze určité události použít jako pole. Aby byla použita tímto způsobem, nesmí být událost abstraktní ani externá a nesmí explicitně obsahovat event_accessor_declarations. Takovou událost lze použít v libovolném kontextu, který povoluje pole. Pole obsahuje delegáta (§20), který odkazuje na seznam obslužných rutin událostí, které byly do události přidány. Pokud nebyly přidány žádné obslužné rutiny událostí, pole obsahuje null.

Příklad: V následujícím kódu

public delegate void EventHandler(object sender, EventArgs e);

public class Button : Control
{
    public event EventHandler Click;

    protected void OnClick(EventArgs e)
    {
        EventHandler handler = Click;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    public void Reset() => Click = null;
}

Click slouží jako pole v rámci Button třídy. Jak ukazuje příklad, pole lze prozkoumat, upravit a použít ve výrazech vyvolání delegáta. Metoda OnClick ve Button třídě vyvolá Click událost. Pojem vyvolání události je přesně ekvivalentem vyvolání delegáta reprezentované událostí , a proto neexistují žádné speciální jazykové konstrukce pro vyvolání událostí. Všimněte si, že vyvolání delegáta předchází kontrola, která zajišťuje, že delegát nemá hodnotu null a že kontrola je provedena v místní kopii, aby se zajistilo zabezpečení vlákna.

Mimo deklaraci Button třídy Click lze člen použít pouze na levé straně += operátorů a –= operátorů, jak je uvedeno v části

b.Click += new EventHandler(...);

který připojí delegáta k seznamu Click vyvolání události a

Click –= new EventHandler(...);

která odebere delegáta ze seznamu Click vyvolání události.

end example

Při kompilaci události podobné poli kompilátor automaticky vytvoří úložiště pro uložení delegáta a vytvoří přístupové metody k události, které přidávají nebo odebírají obslužné rutiny událostí do pole delegáta. Operace přidávání a odebírání jsou bezpečné pro nit a mohou být provedeny (ale nejsou nutné) při držení zámku (§13.13) na objektu obsahujícím instanci nebo System.Type objektu (§12.8.18) pro statickou událost.

Poznámka: Deklarace události instance formuláře:

class X
{
    public event D Ev;
}

se zkompilují na něco, co odpovídá:

class X
{
    private D __Ev; // field to hold the delegate

    public event D Ev
    {
        add
        {
            /* Add the delegate in a thread safe way */
        }
        remove
        {
            /* Remove the delegate in a thread safe way */
        }
    }
}

V rámci třídy Xse odkazy na Ev levou stranu += a –= operátory způsobí vyvolání doplňků pro přidání a odebrání. Všechny ostatní odkazy jsou Ev kompilovány tak, aby odkazovaly na skryté pole __Ev (§12.8.7). Název "__Ev" je libovolný. Skryté pole může mít jakýkoli název nebo vůbec žádný název.

koncová poznámka

15.8.3 Příslušenství událostí

Poznámka: Deklarace událostí obvykle vynechávají event_accessor_declarations, jak je uvedeno v příkladu Button výše. Mohou být například zahrnuté, pokud náklady na úložiště jednoho pole na událost nejsou přijatelné. V takových případech může třída zahrnovat event_accessor_declarations a použít privátní mechanismus pro ukládání seznamu obslužných rutin událostí. koncová poznámka

Event_accessor_declarations události určují spustitelné příkazy přidružené k přidávání a odebírání obslužných rutin událostí.

Deklarace příslušenství se skládají z add_accessor_declaration a remove_accessor_declaration. Každá deklarace přístupového objektu se skládá z přidání nebo odebrání tokenu následovaného blokem. Blok přidružený k add_accessor_declaration určuje příkazy, které se mají provést při přidání obslužné rutiny události, a blok přidružený k remove_accessor_declaration určuje příkazy, které se mají provést při odebrání obslužné rutiny události.

Každý add_accessor_declaration a remove_accessor_declaration odpovídá metodě s jedním parametrem hodnoty typu události a návratovým typem void . Implicitní parametr přístupového objektu událostí má název value. Pokud se událost použije v přiřazení události, použije se odpovídající přístup k události. Konkrétně platí, že pokud je += operátor přiřazení, použije se přístupný objekt pro přidání a pokud je –= operátor přiřazení, použije se odebraný přístup. V obou případech se pravý operand operátoru přiřazení použije jako argument pro přístup události. Blok add_accessor_declaration nebo remove_accessor_declaration odpovídá pravidlům pro metody popsané v §15.6.9.void Zejména return příkazy v tomto bloku nejsou povoleny k určení výrazu.

Vzhledem k tomu, že příslušenství událostí implicitně má pojmenovaný valueparametr , jedná se o chybu v době kompilace pro místní proměnnou nebo konstantu deklarovanou v přístupovém objektu události, aby měl tento název.

Příklad: V následujícím kódu


class Control : Component
{
    // Unique keys for events
    static readonly object mouseDownEventKey = new object();
    static readonly object mouseUpEventKey = new object();

    // Return event handler associated with key
    protected Delegate GetEventHandler(object key) {...}

    // Add event handler associated with key
    protected void AddEventHandler(object key, Delegate handler) {...}

    // Remove event handler associated with key
    protected void RemoveEventHandler(object key, Delegate handler) {...}

    // MouseDown event
    public event MouseEventHandler MouseDown
    {
        add { AddEventHandler(mouseDownEventKey, value); }
        remove { RemoveEventHandler(mouseDownEventKey, value); }
    }

    // MouseUp event
    public event MouseEventHandler MouseUp
    {
        add { AddEventHandler(mouseUpEventKey, value); }
        remove { RemoveEventHandler(mouseUpEventKey, value); }
    }

    // Invoke the MouseUp event
    protected void OnMouseUp(MouseEventArgs args)
    {
        MouseEventHandler handler;
        handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
        if (handler != null)
        {
            handler(this, args);
        }
    }
}

třída Control implementuje interní mechanismus úložiště pro události. Metoda AddEventHandler přidruží hodnotu delegáta ke klíči, GetEventHandler metoda vrátí delegáta, který je aktuálně přidružený ke klíči, a RemoveEventHandler metoda odebere delegáta jako obslužnou rutinu události pro zadanou událost. Předpokládá se, že základní mechanismus úložiště je navržen tak, že neexistuje žádné náklady na přidružení hodnoty delegáta s hodnotou null ke klíči, a proto neošetřené události spotřebovávají žádné úložiště.

end example

15.8.4 Statické události a události instance

Když deklarace události obsahuje static modifikátor, říká se, že událost je statickou událostí. Pokud není k dispozici žádný static modifikátor, říká se, že událost je událost instance.

Statická událost není přidružená ke konkrétní instanci a jedná se o chybu v době kompilace odkazující na this přístupové objekty statické události.

Událost instance je přidružena k dané instanci třídy a tato instance je přístupná jako this (§12.8.14) v přístupových objektech této události.

Rozdíly mezi statickými členy a členy instance jsou popsány dále v §15.3.8.

15.8.5 Virtuální, zapečetěné, přepsání a abstraktní přístupové objekty

Deklarace virtuální události určuje, že přístupové objekty této události jsou virtuální. virtual Modifikátor se vztahuje na oba přístupové objekty události.

Abstraktní deklarace události určuje, že přístupové objekty události jsou virtuální, ale neposkytuje skutečnou implementaci přístupových objektů. Místo toho jsou pro přístupové objekty vyžadovány jiné než abstraktní odvozené třídy, aby poskytovaly vlastní implementaci přepsáním události. Vzhledem k tomu, že příslušenství pro abstraktní deklaraci události neposkytuje žádnou skutečnou implementaci, neposkytuje event_accessor_declarations.

Deklarace události, která obsahuje jak modifikátory abstractoverride , určuje, že událost je abstraktní a přepíše základní událost. Přístupové objekty takové události jsou také abstraktní.

Abstraktní deklarace událostí jsou povoleny pouze v abstraktních třídách (§15.2.2.2.2).

Přístupové objekty zděděné virtuální události lze přepsat v odvozené třídě zahrnutím deklarace události, která určuje override modifikátor. To se označuje jako deklarace události přepsání. Deklarace události přepsání nehlásí novou událost. Místo toho se jednoduše specializuje na implementace přístupových objektů existující virtuální události.

Deklarace události přepsání určuje přesně stejné modifikátory přístupnosti a název jako událost přepsání, musí existovat převod identity mezi typem přepsání a přepsánou událostí, a v rámci deklarace musí být zadány doplňky a doplňky pro odebrání.

Deklarace události přepsání sealed může obsahovat modifikátor. Použití modifikátoru this brání odvozené třídě v dalším přepsání události. Příslušenství zapečetěné události jsou také zapečetěné.

Jedná se o chybu v době kompilace pro přepsání deklarace události, která zahrnuje new modifikátor.

S výjimkou rozdílů v deklaraci a vyvolání syntaxe, virtuální, zapečetěné, přepsání a abstraktní přístupové objekty se chovají přesně jako virtuální, zapečetěné, přepsání a abstraktní metody. Konkrétně platí, že pravidla popsaná v §15.6.4, §15.6.5, §15.6.6 a §15.6.7 platí, jako by byly metody příslušenství odpovídajícího formuláře. Každý přístup odpovídá metodě s jedním parametrem hodnoty typu události, návratovým void typem a stejnými modifikátory jako obsahující událost.

15.9 Indexery

15.9.1 Obecné

Indexer je člen, který umožňuje indexování objektu stejným způsobem jako pole. Indexery se deklarují pomocí indexer_declarations:

indexer_declaration
    : attributes? indexer_modifier* indexer_declarator indexer_body
    | attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
    ;

indexer_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

indexer_declarator
    : type 'this' '[' parameter_list ']'
    | type interface_type '.' 'this' '[' parameter_list ']'
    ;

indexer_body
    : '{' accessor_declarations '}' 
    | '=>' expression ';'
    ;  

ref_indexer_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Existují dva druhy indexer_declaration:

  • První deklaruje indexer bez ref-value. Jeho hodnota má typ typu. Tento druh indexeru může být čitelný nebo zapisovatelný.
  • Druhá deklaruje indexer ref-valued. Jeho hodnota je variable_reference (§9,5 Tento druh indexeru je jen čitelný.

Indexer_declaration může obsahovat sadu atributů (§22) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new (§15.3.5), (§15.3.5), (virtual).) 6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7) a extern (§15.6.8) modifikátory.

Deklarace indexeru podléhají stejným pravidlům jako deklarace metody (§15.6) s ohledem na platné kombinace modifikátorů, přičemž jedinou výjimkou je, že static modifikátor není u deklarace indexeru povolen.

Typ deklarace indexeru určuje typ prvku indexeru zavedeného deklarací.

Poznámka: Vzhledem k tomu, že indexery jsou navrženy tak, aby se používaly v kontextech podobných elementům pole, používá se s indexerem také typ prvku termínu definovaný pro pole. koncová poznámka

Pokud indexer není explicitní implementace člena rozhraní, typ je následovaný klíčovým slovem this. Pro explicitní implementaci člena rozhraní je typ následovaný interface_type, "." a klíčové slovo this. Na rozdíl od ostatních členů nemají indexery uživatelsky definované názvy.

Parameter_list určuje parametry indexeru. Seznam parametrů indexeru odpovídá metodě (§15.6.2), s tím rozdílem, že musí být zadán alespoň jeden parametr a že thismodifikátory parametrů ref, a out parametru nejsou povoleny.

Typ indexeru a každý z typů odkazovaných v parameter_list musí být alespoň tak přístupný jako samotný indexer (§7.5.5).

Indexer_body se může skládat z textu prohlášení (§15.7.1) nebo výrazu (§15.6.1). V prohlášení , accessor_declarations, který musí být uzavřen v "{" a "}" tokeny, deklarujte příslušenství (§15.7.3) indexeru. Přístupové objekty určují spustitelné příkazy přidružené k prvkům indexeru pro čtení a zápis.

V indexer_body tělo výrazu skládající se z výrazu "=>" následovaného výrazem E a středník je přesně ekvivalentní textu příkazu { get { return E; } }, a proto lze použít pouze k určení indexerů jen pro čtení, kde výsledek get accessoru je dán jediným výrazem.

Ref_indexer_body se může skládat z textu příkazu nebo textu výrazu. V těle prohlášení get_accessor_declaration deklaruje přístupový objekt get (§15.7.3) indexeru. Přistupovací objekt určuje spustitelné příkazy přidružené ke čtení indexeru.

V ref_indexer_body

Poznámka: I když syntaxe pro přístup k elementu indexeru je stejná jako pro prvek pole, indexer element není klasifikován jako proměnná. Proto není možné předat prvek indexeru injako , outnebo ref argument, pokud indexer není ref-valued, a proto vrátí odkaz (§9.7). koncová poznámka

Parameter_list indexeru definuje podpis (§7.6) indexeru. Podpis indexeru se konkrétně skládá z počtu a typů jeho parametrů. Typ prvku a názvy parametrů nejsou součástí podpisu indexeru.

Podpis indexeru se liší od podpisů všech ostatních indexerů deklarovaných ve stejné třídě.

Když deklarace indexeru extern obsahuje modifikátor, indexer se označuje jako externí indexer. Vzhledem k tomu, že deklarace externího indexeru neposkytuje žádnou skutečnou implementaci, musí být každý z accessor_bodyv jeho accessor_declarations středníkem.

Příklad: Následující příklad deklaruje BitArray třídu, která implementuje indexer pro přístup k jednotlivým bitům v bitovém poli.

class BitArray
{
    int[] bits;
    int length;

    public BitArray(int length)
    {
        if (length < 0)
        {
            throw new ArgumentException();
        }
        bits = new int[((length - 1) >> 5) + 1];
        this.length = length;
    }

    public int Length => length;

    public bool this[int index]
    {
        get
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            return (bits[index >> 5] & 1 << index) != 0;
        }
        set
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            if (value)
            {
                bits[index >> 5] |= 1 << index;
            }
            else
            {
                bits[index >> 5] &= ~(1 << index);
            }
        }
    }
}

Instance BitArray třídy spotřebovává podstatně méně paměti než odpovídající bool[] (protože každá hodnota bývalého objektu zabírá pouze jeden bit místo toho bytedruhého), ale umožňuje stejné operace jako bool[].

Následující CountPrimes třída používá BitArray a klasický algoritmus "síta" k výpočtu počtu primes mezi 2 a daným maximem:

class CountPrimes
{
    static int Count(int max)
    {
        BitArray flags = new BitArray(max + 1);
        int count = 0;
        for (int i = 2; i <= max; i++)
        {
            if (!flags[i])
            {
                for (int j = i * 2; j <= max; j += i)
                {
                    flags[j] = true;
                }
                count++;
            }
        }
        return count;
    }

    static void Main(string[] args)
    {
        int max = int.Parse(args[0]);
        int count = Count(max);
        Console.WriteLine($"Found {count} primes between 2 and {max}");
    }
}

Všimněte si, že syntaxe pro přístup k prvkům objektu BitArray je přesně stejná jako u bool[].

Následující příklad ukazuje třídu mřížky 26×10, která má indexer se dvěma parametry. První parametr musí být velké nebo malé písmeno v oblasti A–Z a druhá musí být celé číslo v rozsahu 0–9.

class Grid
{
    const int NumRows = 26;
    const int NumCols = 10;
    int[,] cells = new int[NumRows, NumCols];

    public int this[char row, int col]
    {
        get
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            return cells[row - 'A', col];
        }
        set
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException ("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            cells[row - 'A', col] = value;
        }
    }
}

end example

15.9.2 Indexer a rozdíly vlastností

Indexery a vlastnosti jsou v konceptu velmi podobné, ale liší se následujícími způsoby:

  • Vlastnost je identifikována svým názvem, zatímco indexer je identifikován svým podpisem.
  • Nemovitost je přístupná prostřednictvím simple_name (§12.8.4) nebo member_access (§12.8.7), zatímco k prvku indexeru se přistupuje prostřednictvím element_access (§12.8.12.3).
  • Vlastnost může být statickým členem, zatímco indexer je vždy členem instance.
  • Přístupové objekty get vlastnosti odpovídají metodě bez parametrů, zatímco přístupové objekty get indexeru odpovídají metodě se stejným seznamem parametrů jako indexer.
  • Objekt set vlastnosti odpovídá metodě s jedním parametrem s názvem value, zatímco objekt set přístupového objektu indexeru odpovídá metodě se stejným seznamem parametrů jako indexer a další parametr s názvem value.
  • Jedná se o chybu v době kompilace pro přístup k indexeru, která deklaruje místní proměnnou nebo místní konstantu se stejným názvem jako parametr indexeru.
  • V deklaraci přepsání vlastnosti je zděděná vlastnost přístup pomocí syntaxe base.P, kde P je název vlastnosti. V deklaraci přepsání indexeru je zděděný indexer přístupný pomocí syntaxe base[E], kde E je čárkami oddělený seznam výrazů.
  • Neexistuje žádný koncept "automaticky implementovaného indexeru". Jedná se o chybu, která nemá abstraktní externí indexer s středníkem accessor_bodys.

Kromě těchto rozdílů se všechna pravidla definovaná v §15.7.3, §15.7.5 a §15.7.6 vztahují i na přístupové objekty indexeru.

Tato výměna vlastností/vlastností indexerem/indexery při čtení §15.7.3, §15.7.5 a §15.7.6 platí i pro definované termíny. Konkrétně se vlastnost pro čtení a zápis stává indexerem pro čtení, jen pro čtení se stane indexerem jen pro čtení a vlastnost jen pro zápis se stane indexerem jen pro zápis.

15.10 – operátory

15.10.1 Obecné

Operátor je člen, který definuje význam operátoru výrazu, který lze použít na instance třídy. Operátory jsou deklarovány pomocí operator_declarations:

operator_declaration
    : attributes? operator_modifier+ operator_declarator operator_body
    ;

operator_modifier
    : 'public'
    | 'static'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

operator_declarator
    : unary_operator_declarator
    | binary_operator_declarator
    | conversion_operator_declarator
    ;

unary_operator_declarator
    : type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
    ;

logical_negation_operator
    : '!'
    ;

overloadable_unary_operator
    : '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
    ;

binary_operator_declarator
    : type 'operator' overloadable_binary_operator
        '(' fixed_parameter ',' fixed_parameter ')'
    ;

overloadable_binary_operator
    : '+'  | '-'  | '*'  | '/'  | '%'  | '&' | '|' | '^'  | '<<' 
    | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
    ;

conversion_operator_declarator
    : 'implicit' 'operator' type '(' fixed_parameter ')'
    | 'explicit' 'operator' type '(' fixed_parameter ')'
    ;

operator_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Poznámka: Operátory logické předpony (§12.9.4) a přípony null-odpouštějící operátory (§12.8.9), které jsou reprezentovány stejným lexikálním tokenem (!), jsou odlišné. Druhý operátor není přetížitelným operátorem. koncová poznámka

Existují tři kategorie přetížitelných operátorů: unární operátory (§15.10.2), binární operátory (§15.10.3) a konverzní operátory (§15.10.4).

Operator_body je buď středníkem, blokovým tělem (§15.6.1), nebo výrazem (§15.6.1). Tělo bloku se skládá z bloku, který určuje příkazy, které se mají provést při vyvolání operátoru. Blok odpovídá pravidlům pro metody vrácení hodnoty popsané v §15.6.11. Tělo výrazu se skládá z výrazu následovaného => výrazem a středníkem a označuje jeden výraz, který se má provést při vyvolání operátoru.

Pro extern operátory se operator_body skládá jednoduše ze středníku. U všech ostatních operátorů je operator_body buď blokovým tělem, nebo tělem výrazu.

Následující pravidla platí pro všechny deklarace operátorů:

  • Prohlášení operátoru publicstatic zahrnuje jak modifikátor, tak i modifikátor.
  • Parametry operátoru nesmí mít jiné modifikátory než in.
  • Podpis operátora (§15.10.2, §15.10.3, §15.10.4) se liší od podpisů všech ostatních operátorů deklarovaných ve stejné třídě.
  • Všechny typy uvedené v prohlášení operátora musí být alespoň tak přístupné jako samotný provozovatel (§7.5.5).
  • Jedná se o chybu, která se u stejného modifikátoru zobrazí vícekrát v deklaraci operátoru.

Každá kategorie operátoru ukládá další omezení, jak je popsáno v následujících dílčích náklážích.

Stejně jako ostatní členy jsou operátory deklarované v základní třídě zděděné odvozenými třídami. Vzhledem k tomu, že deklarace operátorů vždy vyžadují třídu nebo strukturu, ve které je operátor deklarován pro účast v podpisu operátoru, není možné, aby operátor deklarovaný v odvozené třídě skryl operátor deklarovaný v základní třídě. new Modifikátor se tedy nikdy nevyžaduje, a proto není nikdy povolen v deklaraci operátoru.

Další informace o unárních a binárních operátorech naleznete v §12.4.

Další informace o převodních operátorech naleznete v §10.5.

15.10.2 Unární operátory

Následující pravidla se vztahují na deklarace unárního operátoru, kde T označuje typ instance třídy nebo struktury, která obsahuje deklaraci operátoru:

  • Unární +, ( -! pouze logická negace) nebo ~ operátor použije jeden parametr typu T nebo T? může vrátit libovolný typ.
  • Unární ++ nebo -- operátor přijme jeden parametr typu T nebo T? vrátí stejný typ nebo typ odvozený z něj.
  • Unární true nebo false operátor použije jeden parametr typu T nebo T? vrátí typ bool.

Podpis unárního operátoru se skládá z tokenu operátoru (+, -!, ~, ++, --, , true, nebo false) a typu jednoho parametru. Návratový typ není součástí podpisu unárního operátoru ani není název parametru.

Operátory true a false unární operátory vyžadují deklaraci typu pár. K chybě v době kompilace dochází, pokud třída deklaruje jeden z těchto operátorů, aniž by deklaroval druhý. Operátory true a false operátory jsou dále popsány v §12.24.

Příklad: Následující příklad ukazuje implementaci a následné použití operátoru++ pro celočíselnou třídu vektoru:

public class IntVector
{
    public IntVector(int length) {...}
    public int Length { get { ... } }                      // Read-only property
    public int this[int index] { get { ... } set { ... } } // Read-write indexer

    public static IntVector operator++(IntVector iv)
    {
        IntVector temp = new IntVector(iv.Length);
        for (int i = 0; i < iv.Length; i++)
        {
            temp[i] = iv[i] + 1;
        }
        return temp;
    }
}

class Test
{
    static void Main()
    {
        IntVector iv1 = new IntVector(4); // Vector of 4 x 0
        IntVector iv2;
        iv2 = iv1++;              // iv2 contains 4 x 0, iv1 contains 4 x 1
        iv2 = ++iv1;              // iv2 contains 4 x 2, iv1 contains 4 x 2
    }
}

Všimněte si, jak metoda operátoru vrátí hodnotu vytvořenou přidáním 1 do operandu, stejně jako operátory přírůstku a dekrementace přípony (§12.8.16) a operátory inkrementace a dekrementace předpony (§12.9.6). Na rozdíl od jazyka C++ by tato metoda neměla upravovat hodnotu svého operandu přímo, protože by porušila standardní sémantiku operátoru přírůstku přípony (§12.8.16).

end example

15.10.3 Binární operátory

Následující pravidla platí pro deklarace binárního operátoru, kde T označuje typ instance třídy nebo struktury, která obsahuje deklaraci operátoru:

  • Binární operátor bez posunu musí mít dva parametry, alespoň jeden z nich musí mít typ T nebo T?, a může vrátit jakýkoli typ.
  • Binární << nebo >> operátor (§12.11) má dva parametry, z nichž první musí mít typ T nebo T? a druhý z nichž má typ int nebo int?a může vrátit libovolný typ.

Podpis binárního operátoru se skládá z tokenu operátoru (+, -, *, /%&|^<<>>==!=, >, , <nebo >=<=) a typů dvou parametrů. Návratový typ a názvy parametrů nejsou součástí podpisu binárního operátoru.

Některé binární operátory vyžadují deklaraci typu pár. Pro každou deklaraci některého operátoru páru musí existovat odpovídající deklarace druhého operátoru dvojice. Dvě deklarace operátorů se shodují, pokud mezi jejich návratovými typy a odpovídajícími typy parametrů existují převody identit. Následující operátory vyžadují deklaraci podle páru:

  • operátor == a operátor !=
  • operátor > a operátor <
  • operátor >= a operátor <=

15.10.4 Převodní operátory

Deklarace operátoru převodu zavádí převod definovaný uživatelem (§10.5), který rozšiřuje předem definované implicitní a explicitní převody.

Deklarace operátoru převodu implicit , která obsahuje klíčové slovo, zavádí uživatelem definovaný implicitní převod. Implicitní převody můžou nastat v různých situacích, včetně volání členů funkce, výrazů přetypování a přiřazení. Toto je popsáno dále v §10.2.

Deklarace operátoru převodu explicit , která obsahuje klíčové slovo, zavádí uživatelem definovaný explicitní převod. Explicitní převody mohou nastat ve výrazech přetypování a jsou popsány dále v §10.3.

Operátor převodu se převede ze zdrojového typu označeného typem parametru operátoru převodu na cílový typ označený návratovým typem operátoru převodu.

Pro daný typ zdroje a cílový typ ST, pokud S nebo T jsou typy hodnot null, let S₀ a T₀ odkazovat na jejich základní typy; jinak a S₀T₀ jsou rovny S a T v uvedeném pořadí. Třída nebo struktura je povolena deklarovat převod ze zdrojového typu na cílový typ ST pouze v případě, že jsou splněny všechny následující podmínky:

  • S₀ a T₀ jsou různé typy.

  • Buď S₀ nebo T₀ je typ instance třídy nebo struktury, která obsahuje deklaraci operátoru.

  • Ani S₀ interface_typeT₀.

  • S výjimkou uživatelem definovaných převodů neexistuje převod z S do T nebo z T do S.

Pro účely těchto pravidel jsou všechny parametry typu přidružené S nebo T považovány za jedinečné typy, které nemají žádný vztah dědičnosti s jinými typy, a všechna omezení těchto parametrů typu jsou ignorována.

Příklad: V následujícím příkladu:

class C<T> {...}

class D<T> : C<T>
{
    public static implicit operator C<int>(D<T> value) {...}     // Ok
    public static implicit operator C<string>(D<T> value) {...}  // Ok
    public static implicit operator C<T>(D<T> value) {...}       // Error
}

první dvě deklarace operátoru jsou povoleny, protože Tint a string, v uvedeném pořadí jsou považovány za jedinečné typy bez relace. Třetí operátor je však chyba, protože C<T> je základní třídou D<T>.

end example

Z druhého pravidla vyplývá, že operátor převodu převede buď na třídu nebo z typu struktury, ve které je operátor deklarován.

Příklad: Typ třídy nebo struktury C je možné definovat převod z Cint do a z int do C, ale ne z int do bool. end example

Není možné přímo předdefinovat předdefinovaný převod. Operátory převodu tedy nemohou převést z nebo na object , protože implicitní a explicitní převody již existují mezi object a všemi ostatními typy. Stejně tak zdroj ani cílové typy převodu nemohou být základním typem druhého, protože převod by již existoval. Je však možné deklarovat operátory u obecných typů, které pro konkrétní argumenty typu určují převody, které již existují jako předdefinované převody.

Příklad:

struct Convertible<T>
{
    public static implicit operator Convertible<T>(T value) {...}
    public static explicit operator T(Convertible<T> value) {...}
}

pokud je typ object zadán jako typ argument pro T, druhý operátor deklaruje převod, který již existuje (implicitní, a proto také explicitní převod existuje z libovolného typu na objekt typu).

end example

V případech, kdy existuje předdefinovaný převod mezi dvěma typy, budou všechny uživatelem definované převody mezi těmito typy ignorovány. Konkrétně:

  • Pokud předdefinovaný implicitní převod (§10.2) existuje z typu S na typ T, budou ignorovány všechny uživatelem definované převody (implicitní nebo explicitní).ST
  • Pokud předdefinovaný explicitní převod (§10.3) existuje z typu S na typ T, budou ignorovány všechny explicitní převody ST definované uživatelem. Mimoto:
    • Pokud se jedná S o T typ rozhraní, budou se ignorovat implicitní převody ST definované uživatelem.
    • V opačném případě se budou i nadále zvažovat S implicitní převody T definované uživatelem.

Pro všechny typy, ale objectoperátory deklarované typem Convertible<T> výše nejsou v konfliktu s předdefinovanými převody.

Příklad:

void F(int i, Convertible<int> n)
{
    i = n;                    // Error
    i = (int)n;               // User-defined explicit conversion
    n = i;                    // User-defined implicit conversion
    n = (Convertible<int>)i;  // User-defined implicit conversion
}

U typu objectvšak předdefinované převody skryjí uživatelem definované převody ve všech případech, ale jednu:

void F(object o, Convertible<object> n)
{
    o = n;                       // Pre-defined boxing conversion
    o = (object)n;               // Pre-defined boxing conversion
    n = o;                       // User-defined implicit conversion
    n = (Convertible<object>)o;  // Pre-defined unboxing conversion
}

end example

Uživatelem definované převody nelze převést z nebo na interface_types. Konkrétně toto omezení zajišťuje, že při převodu na interface_type nedojde k žádným uživatelem definovaným transformacím a že převod na interface_type bude úspěšný pouze v případě object , že převáděná transformace skutečně implementuje zadanou interface_type.

Podpis operátoru převodu se skládá ze zdrojového typu a cílového typu. (Toto je jediná forma člena, pro který se návratový typ účastní podpisu.) Implicitní nebo explicitní klasifikace operátoru převodu není součástí podpisu operátora. Třída nebo struktura proto nemůže deklarovat implicitní i explicitní převodní operátor se stejnými zdrojovými a cílovými typy.

Poznámka: Obecně platí, že implicitní převody definované uživatelem by měly být navrženy tak, aby nikdy nevyvolávat výjimky a nikdy neztratily informace. Pokud převod definovaný uživatelem může vést k výjimkám (například kvůli tomu, že je zdrojový argument mimo rozsah) nebo ztrátu informací (například zahození bitů s vysokým pořadím), měl by být tento převod definován jako explicitní převod. koncová poznámka

Příklad: V následujícím kódu

public struct Digit
{
    byte value;

    public Digit(byte value)
    {
        if (value < 0 || value > 9)
        {
            throw new ArgumentException();
        }
        this.value = value;
    }

    public static implicit operator byte(Digit d) => d.value;
    public static explicit operator Digit(byte b) => new Digit(b);
}

převod z Digit na byte je implicitní, protože nikdy nevyvolá výjimky nebo ztratí informace, ale převod z byte na Digit je explicitní, protože Digit může představovat pouze podmnožinu možných hodnot byte.

end example

15.11 Konstruktory instancí

15.11.1 Obecné

Konstruktor instance je člen, který implementuje akce potřebné k inicializaci instance třídy. Konstruktory instancí jsou deklarovány pomocí constructor_declarations:

constructor_declaration
    : attributes? constructor_modifier* constructor_declarator constructor_body
    ;

constructor_modifier
    : 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

constructor_declarator
    : identifier '(' parameter_list? ')' constructor_initializer?
    ;

constructor_initializer
    : ':' 'base' '(' argument_list? ')'
    | ':' 'this' '(' argument_list? ')'
    ;

constructor_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Constructor_declaration může obsahovat sadu atributů (§22), kterýkoli z povolených druhů deklarované přístupnosti (§15.3.6) a extern modifikátor (§15.6.8). Deklarace konstruktoru není povolena k zahrnutí stejného modifikátoru vícekrát.

Identifikátor constructor_declarator pojmenuje třídu, ve které je deklarován konstruktor instance. Pokud je zadán jakýkoli jiný název, dojde k chybě v době kompilace.

Nepovinný parameter_list konstruktoru instance podléhá stejným pravidlům jako parameter_list metody (§15.6). this Modifikátor parametrů se vztahuje pouze na rozšiřující metody (§15.6.10), ani parametr v parameter_list konstruktoru this nesmí obsahovat modifikátor. Seznam parametrů definuje podpis (§7.6) konstruktoru instance a řídí proces, při kterém řešení přetížení (§12.6.4) při vyvolání vybere konkrétní konstruktor instance.

Každý z typů odkazovaných v parameter_list konstruktoru instance musí být alespoň tak přístupný jako samotný konstruktor (§7.5.5).

Nepovinný constructor_initializer určuje jiný konstruktor instance, který se má vyvolat před spuštěním příkazů uvedených v constructor_body tohoto konstruktoru instance. Toto je popsáno dále v §15.11.2.

Pokud deklarace konstruktoru extern obsahuje modifikátor, konstruktor se říká, že je externí konstruktor. Vzhledem k tomu, že deklarace externího konstruktoru neposkytuje žádnou skutečnou implementaci, jeho constructor_body se skládá z středníku. U všech ostatních konstruktorů se constructor_body skládá z obou

  • blok, který určuje příkazy pro inicializaci nové instance třídy; nebo
  • tělo výrazu, které se skládá z =>výrazu a středníku, a označuje jeden výraz pro inicializaci nové instance třídy.

Constructor_body, která je blokem nebo tělem výrazu, odpovídá přesně bloku metody instance návratovým typem void (§15.6.11).

Konstruktory instancí nejsou zděděné. Třída tedy nemá žádné konstruktory instance jiné než ty, které jsou ve skutečnosti deklarovány ve třídě, s výjimkou, že pokud třída neobsahuje žádné deklarace konstruktoru instance, je automaticky zadán výchozí konstruktor instance (§15.11.5).

Konstruktory instancí jsou vyvolány object_creation_expressions (§12.8.17.2) a constructor_initializer s.

15.11.2 Inicializátory konstruktoru

Všechny konstruktory instance (s výjimkou těch pro třídu object) implicitně zahrnují vyvolání jiného konstruktoru instance bezprostředně před constructor_body. Konstruktor k implicitnímu vyvolání je určen constructor_initializer:

  • Inicializátor konstruktoru instance formuláře base(argument_list) (kde argument_list je volitelný) způsobí vyvolání konstruktoru instance z přímé základní třídy. Tento konstruktor je vybrán pomocí argument_list a pravidel rozlišení přetížení §12.6.4. Sada konstruktorů instance kandidáta se skládá ze všech přístupných konstruktorů instance přímé základní třídy. Pokud je tato sada prázdná nebo pokud nelze identifikovat jeden nejlepší konstruktor instance, dojde k chybě v době kompilace.
  • Inicializátor konstruktoru instance formuláře this(argument_list) (kde argument_list je nepovinný) vyvolá jiný konstruktor instance ze stejné třídy. Konstruktor je vybrán pomocí argument_list a pravidla rozlišení přetížení §12.6.4. Sada konstruktorů instance kandidáta se skládá ze všech konstruktorů instancí deklarovaných v samotné třídě. Pokud je výsledná sada použitelných konstruktorů instancí prázdná nebo pokud nelze identifikovat jeden nejlepší konstruktor instance, dojde k chybě v době kompilace. Pokud se deklarace konstruktoru instance vyvolá sama prostřednictvím řetězu jednoho nebo více inicializátorů konstruktoru, dojde k chybě v době kompilace.

Pokud konstruktor instance nemá žádný konstruktor inicializátor, konstruktor inicializátor formuláře base() je implicitně poskytován.

Poznámka: Deklarace konstruktoru instance formuláře

C(...) {...}

je přesně ekvivalentní

C(...) : base() {...}

koncová poznámka

Rozsah parametrů zadaných parameter_list deklarace konstruktoru instance zahrnuje konstruktor inicializátoru této deklarace. Inicializátor konstruktoru je tedy povolen přístup k parametrům konstruktoru.

Příklad:

class A
{
    public A(int x, int y) {}
}

class B: A
{
    public B(int x, int y) : base(x + y, x - y) {}
}

end example

Inicializátor konstruktoru instance nemá přístup k vytvářené instanci. Proto se jedná o chybu v době kompilace, která se na tuto chybu odkazuje ve výrazu argumentu inicializátoru konstruktoru, protože se jedná o chybu v době kompilace výrazu argumentu odkazovat na libovolný člen instance prostřednictvím simple_name.

15.11.3 Inicializátory proměnných instancí

Pokud konstruktor instance nemá žádný konstruktor inicializátor nebo má konstruktor inicializátor formuláře base(...), že konstruktor implicitně provádí inicializace určené variable_initializers polí instance deklarovaných ve své třídě. To odpovídá posloupnosti přiřazení, která jsou provedena okamžitě po vstupu do konstruktoru a před implicitním vyvoláním přímého konstruktoru základní třídy. Inicializátory proměnných se provádějí v textovém pořadí, ve kterém jsou uvedeny v deklaraci třídy (§15.5.6).

15.11.4 Provádění konstruktoru

Inicializátory proměnných se transformují na příkazy přiřazení a tyto příkazy přiřazení se provádějí před vyvoláním konstruktoru instance základní třídy. Toto řazení zajišťuje, že se všechna pole instance inicializují pomocí inicializátorů proměnných před spuštěním příkazů , které mají přístup k dané instanci.

Příklad: S ohledem na následující:

class A
{
    public A()
    {
        PrintFields();
    }

    public virtual void PrintFields() {}
}
class B: A
{
    int x = 1;
    int y;

    public B()
    {
        y = -1;
    }

    public override void PrintFields() =>
        Console.WriteLine($"x = {x}, y = {y}");
}

pokud se k vytvoření instance použije nový B() , Bvytvoří se následující výstup:

x = 1, y = 0

Hodnota x je 1, protože inicializátor proměnné se spustí před vyvolání konstruktoru instance základní třídy. Hodnota y je však 0 (výchozí hodnota int) protože přiřazení y není provedeno, dokud se konstruktor základní třídy nevrátí. Je užitečné si představit inicializátory proměnných instancí a inicializátory konstruktoru jako příkazy, které jsou automaticky vloženy před constructor_body. Příklad

class A
{
    int x = 1, y = -1, count;

    public A()
    {
        count = 0;
    }

    public A(int n)
    {
        count = n;
    }
}

class B : A
{
    double sqrt2 = Math.Sqrt(2.0);
    ArrayList items = new ArrayList(100);
    int max;

    public B(): this(100)
    {
        items.Add("default");
    }

    public B(int n) : base(n - 1)
    {
        max = n;
    }
}

obsahuje několik inicializátorů proměnných; obsahuje také inicializátory konstruktoru obou formulářů (base a this). Příklad odpovídá níže uvedenému kódu, kde každý komentář označuje automaticky vložený příkaz (syntaxe použitá pro automaticky vložené vyvolání konstruktoru není platná, ale slouží pouze k ilustraci mechanismu).

class A
{
    int x, y, count;
    public A()
    {
        x = 1;      // Variable initializer
        y = -1;     // Variable initializer
        object();   // Invoke object() constructor
        count = 0;
    }

    public A(int n)
    {
        x = 1;      // Variable initializer
        y = -1;     // Variable initializer
        object();   // Invoke object() constructor
        count = n;
    }
}

class B : A
{
    double sqrt2;
    ArrayList items;
    int max;
    public B() : this(100)
    {
        B(100);                      // Invoke B(int) constructor
        items.Add("default");
    }

    public B(int n) : base(n - 1)
    {
        sqrt2 = Math.Sqrt(2.0);      // Variable initializer
        items = new ArrayList(100);  // Variable initializer
        A(n - 1);                    // Invoke A(int) constructor
        max = n;
    }
}

end example

15.11.5 Výchozí konstruktory

Pokud třída neobsahuje žádné deklarace konstruktoru instance, je automaticky poskytnut výchozí konstruktor instance. Tento výchozí konstruktor jednoduše vyvolá konstruktor přímé základní třídy, jako by měl konstruktor inicializátor formuláře base(). Pokud je třída abstraktní, je deklarovaná přístupnost pro výchozí konstruktor chráněna. V opačném případě je deklarovaná přístupnost výchozího konstruktoru veřejná.

Poznámka: Výchozí konstruktor je tedy vždy ve formuláři.

protected C(): base() {}

nebo

public C(): base() {}

kde C je název třídy.

koncová poznámka

Pokud řešení přetížení nemůže určit jedinečného nejlepšího kandidáta pro inicializátor konstruktoru základní třídy, dojde k chybě kompilace.

Příklad: V následujícím kódu

class Message
{
    object sender;
    string text;
}

Je k dispozici výchozí konstruktor, protože třída neobsahuje žádné deklarace konstruktoru instance. Proto je příklad přesně ekvivalentní

class Message
{
    object sender;
    string text;

    public Message() : base() {}
}

end example

15.12 Statické konstruktory

Statický konstruktor je člen, který implementuje akce potřebné k inicializaci uzavřené třídy. Statické konstruktory jsou deklarovány pomocí static_constructor_declarations:

static_constructor_declaration
    : attributes? static_constructor_modifiers identifier '(' ')'
        static_constructor_body
    ;

static_constructor_modifiers
    : 'static'
    | 'static' 'extern' unsafe_modifier?
    | 'static' unsafe_modifier 'extern'?
    | 'extern' 'static' unsafe_modifier?
    | 'extern' unsafe_modifier 'static'
    | unsafe_modifier 'static' 'extern'?
    | unsafe_modifier 'extern' 'static'
    ;

static_constructor_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Static_constructor_declaration může obsahovat sadu atributů (§22) a extern modifikátor (§15.6.8).

Identifikátor static_constructor_declaration pojmenuje třídu, ve které je deklarován statický konstruktor. Pokud je zadán jakýkoli jiný název, dojde k chybě v době kompilace.

Pokud deklarace statického konstruktoru extern obsahuje modifikátor, statický konstruktor se říká, že se jedná o externí statický konstruktor. Vzhledem k tomu, že deklarace externího statického konstruktoru neposkytuje žádnou skutečnou implementaci, jeho static_constructor_body se skládá z středníku. Pro všechny ostatní deklarace statického konstruktoru se static_constructor_body skládá z obou

  • blok, který určuje příkazy, které se mají provést za účelem inicializace třídy; nebo
  • tělo výrazu, které se skládá z =>výrazu a středníku, a označuje jeden výraz, který se má provést, aby se inicializovala třída.

Static_constructor_body, která je blokem nebo tělem výrazu, přesně odpovídá method_body statické metody návratovým typem void (§15.6.11).

Statické konstruktory nejsou zděděné a nelze je volat přímo.

Statický konstruktor pro uzavřenou třídu se spustí najednou v dané doméně aplikace. Spuštění statického konstruktoru se aktivuje první z následujících událostí v rámci domény aplikace:

  • Vytvoří se instance třídy.
  • Na kterýkoli ze statických členů třídy se odkazuje.

Pokud třída obsahuje metodu Main (§7.1), ve které spuštění začíná, statický konstruktor pro tuto třídu se provede před voláním Main metody.

Chcete-li inicializovat nový uzavřený typ třídy, vytvoří se nejprve nová sada statických polí (§15.5.2) pro daný uzavřený typ. Každé statické pole je inicializováno na výchozí hodnotu (§15.5.5). Dále se pro tato statická pole provádějí inicializátory statických polí (§15.5.6.2). Nakonec se spustí statický konstruktor.

Příklad: Příklad

class Test
{
    static void Main()
    {
        A.F();
        B.F();
    }
}

class A
{
    static A()
    {
        Console.WriteLine("Init A");
    }

    public static void F()
    {
        Console.WriteLine("A.F");
    }
}

class B
{
    static B()
    {
        Console.WriteLine("Init B");
    }

    public static void F()
    {
        Console.WriteLine("B.F");
    }
}

musí vytvořit výstup:

Init A
A.F
Init B
B.F

vzhledem k tomu, že spuštění statického Akonstruktoru 'je aktivováno voláním A.Fa spuštění Bstatického konstruktoru 'je aktivováno voláním B.F.

end example

Je možné vytvořit cyklické závislosti, které umožňují sledovat statická pole s inicializátory proměnných ve výchozím stavu hodnoty.

Příklad: Příklad

class A
{
    public static int X;

    static A()
    {
        X = B.Y + 1;
    }
}

class B
{
    public static int Y = A.X + 1;

    static B() {}

    static void Main()
    {
        Console.WriteLine($"X = {A.X}, Y = {B.Y}");
    }
}

vytvoří výstup.

X = 1, Y = 2

Chcete-li spustit metodu Main , systém nejprve spustí inicializátor pro B.Y, před statický konstruktor třídy B. YInicializátor způsobí Astatic spuštění konstruktoruA.X, protože na hodnotu se odkazuje. Statický konstruktor A následně vypočítá hodnotu Xa tím načte výchozí hodnotu Y, která je nula. A.X inicializuje se tedy na 1. Proces spuštění Ainicializátorů statických polí a statického konstruktoru se pak dokončí a vrátí se k výpočtu počáteční hodnoty Y, jehož výsledkem je 2.

end example

Vzhledem k tomu, že statický konstruktor se provádí přesně jednou pro každý uzavřený konstruovaný typ třídy, je vhodné vynutit kontroly za běhu u parametru typu, který nelze zkontrolovat v době kompilace prostřednictvím omezení (§15.2.5).

Příklad: Následující typ používá statický konstruktor k vynucení, že argument typu je výčt:

class Gen<T> where T : struct
{
    static Gen()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("T must be an enum");
        }
    }
}

end example

15.13 Finalizační metody

Poznámka: V dřívější verzi této specifikace se nyní označuje jako "finalizátor", který se nazývá "destruktor". Zkušenosti ukázaly, že termín "destruktor" způsobil nejasnost a často způsoboval nesprávné očekávání, zejména programátorům, kteří znají jazyk C++. V jazyce C++ je destruktor volána determinatem, zatímco v jazyce C# není finalizátor. Pokud chcete získat determinantní chování z jazyka C#, měli byste použít Dispose. koncová poznámka

Finalizátor je člen, který implementuje akce potřebné k dokončení instance třídy. Finalizátor je deklarován pomocí finalizer_declaration:

finalizer_declaration
    : attributes? '~' identifier '(' ')' finalizer_body
    | attributes? 'extern' unsafe_modifier? '~' identifier '(' ')'
      finalizer_body
    | attributes? unsafe_modifier 'extern'? '~' identifier '(' ')'
      finalizer_body
    ;

finalizer_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Finalizer_declaration může obsahovat sadu atributů (§22).

Identifikátor finalizer_declarator pojmenuje třídu, ve které je finalizátor deklarován. Pokud je zadán jakýkoli jiný název, dojde k chybě v době kompilace.

Když deklarace finalizátoru extern obsahuje modifikátor, finalizátor se říká, že se jedná o externí finalizátor. Vzhledem k tomu, že deklarace externí finalizátoru neposkytuje žádnou skutečnou implementaci, jeho finalizer_body se skládá z středníku. U všech ostatních finalizátorů se finalizer_body skládá z obou

  • blok, který určuje příkazy, které se mají provést, aby bylo možné dokončit instanci třídy.
  • nebo tělo výrazu, které se skládá z => výrazu a středníku, a označuje jeden výraz, který se má provést, aby bylo možné dokončit instanci třídy.

Finalizer_body, která je blokemnebo tělem výrazu, přesně odpovídá method_body metody instance s návratovým typem void (§15.6.11).

Finalizační metody nejsou zděděny. Třída tedy nemá žádné finalizační metody jiné než ty, které mohou být deklarovány v této třídě.

Poznámka: Vzhledem k tomu, že finalizátor musí mít žádné parametry, nemůže být přetížen, takže třída může mít maximálně jeden finalizátor. koncová poznámka

Finalizační metody jsou vyvolány automaticky a nelze je vyvolat explicitně. Instance se stane oprávněnou k dokončení, pokud už není možné, aby jakýkoli kód tuto instanci používal. K provedení finalizátoru instance může dojít kdykoli poté, co se instance stane způsobilým k dokončení (§7.9). Při finalizaci instance se finalizační metody v řetězu dědičnosti instance volají v pořadí od většiny odvozených po nejméně odvozené. Finalizační metodu lze spustit na libovolném vlákně. Další diskuzi o pravidlech, která řídí, kdy a jak se finalizátor provádí, najdete v §7.9.

Příklad: Výstup příkladu

class A
{
    ~A()
    {
        Console.WriteLine("A's finalizer");
    }
}

class B : A
{
    ~B()
    {
        Console.WriteLine("B's finalizer");
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

is

B's finalizer
A's finalizer

vzhledem k tomu, že finalizační metody v řetězci dědičnosti jsou volány v pořadí, od většiny odvozených po nejméně odvozené.

end example

Finalizační metody jsou implementovány přepsáním virtuální metody Finalize on System.Object. Programy jazyka C# nemají povoleno přepsat tuto metodu nebo ji volat (nebo přepisovat) přímo.

Příklad: Například program

class A
{
    override protected void Finalize() {}  // Error
    public void F()
    {
        this.Finalize();                   // Error
    }
}

obsahuje dvě chyby.

end example

Kompilátor se chová, jako by tato metoda a její přepsání vůbec neexistovaly.

Příklad: Tímto programem:

class A
{
    void Finalize() {}  // Permitted
}

je platná a zobrazená metoda skryje System.Objectmetodu Finalize .

end example

Diskuzi o chování při vyvolání výjimky z finalizátoru naleznete v §21.4.

15.14 Iterátory

15.14.1 Obecné

Člen funkce (§12.6) implementovaný pomocí bloku iterátoru (§13.3) se nazývá iterátor.

Blok iterátoru lze použít jako tělo člena funkce, pokud návratový typ odpovídajícího členu funkce je jedním z rozhraní enumerátoru (§15.14.2) nebo jednoho z výčtových rozhraní (§15.14.3). Může k němu dojít jako method_body, operator_body nebo accessor_body, zatímco události, konstruktory instancí, statické konstruktory a finalizátory se neimplementují jako iterátory.

Pokud je člen funkce implementovaný pomocí bloku iterátoru, jedná se o chybu v době kompilace pro seznam parametrů člena funkce k určení libovolného inparametru , out, nebo ref parametrů nebo parametru typu ref struct .

15.14.2 Rozhraní enumerátoru

Rozhraní enumerátoru jsou ne generické rozhraní System.Collections.IEnumerator a všechny instance obecného rozhraní System.Collections.Generic.IEnumerator<T>. Pro účely stručnosti se v tomto podkclause a jeho na stejné úrovni tyto rozhraní odkazují jako IEnumerator a IEnumerator<T>v uvedeném pořadí.

15.14.3 Výčet rozhraní

Enumerable rozhraní jsou non-generic rozhraní System.Collections.IEnumerable a všechny instance obecného rozhraní System.Collections.Generic.IEnumerable<T>. Pro účely stručnosti se v tomto podkclause a jeho na stejné úrovni tyto rozhraní odkazují jako IEnumerable a IEnumerable<T>v uvedeném pořadí.

15.14.4 Typ výnosu

Iterátor vytvoří sekvenci hodnot, všechny stejného typu. Tento typ se nazývá typ výnosu iterátoru.

  • Typ výnosu iterátoru, který vrací IEnumerator nebo IEnumerable je object.
  • Typ výnosu iterátoru, který vrací IEnumerator<T> nebo IEnumerable<T> je T.

15.14.5 Enumerator – objekty

15.14.5.1 Obecné

Pokud je člen funkce vracející typ rozhraní enumerátoru implementován pomocí bloku iterátoru, vyvolání člena funkce okamžitě nespustí kód v bloku iterátoru. Místo toho se vytvoří a vrátí objekt enumerátoru. Tento objekt zapouzdřuje kód zadaný v bloku iterátoru a spuštění kódu v bloku iterátoru dojde při vyvolání metody objektu MoveNext enumeratoru. Objekt enumerátoru má následující charakteristiky:

  • Implementuje IEnumerator a IEnumerator<T>, kde T je typ výnosu iterátoru.
  • Implementuje System.IDisposable.
  • Inicializuje se pomocí kopie hodnot argumentů (pokud existuje) a hodnoty instance předané členu funkce.
  • Má čtyři potenciální stavy, před, spuštěním, pozastavením a po, a je zpočátku v představu.

Enumerator objekt je obvykle instance kompilátoru generované enumerator třídy, která zapouzdřuje kód v bloku iterátoru a implementuje rozhraní enumerátoru, ale jiné metody implementace jsou možné. Pokud kompilátor vygeneruje třídu enumerátoru, bude tato třída vnořena přímo nebo nepřímo do třídy obsahující člen funkce, bude mít privátní přístupnost a bude mít název vyhrazený pro použití kompilátoru (§6.4.3).

Objekt enumerátoru může implementovat více rozhraní, než je uvedeno výše.

Následující dílčí popisy popisují požadované chování MoveNext, Currenta Dispose členy IEnumerator implementace rozhraní poskytované IEnumerator<T> enumerátor objektu.

Objekty enumerátoru nepodporují metodu IEnumerator.Reset . Vyvolání této metody způsobí System.NotSupportedException vyvolání.

15.14.5.2 Metoda MoveNext

Metoda MoveNext objektu enumerátoru zapouzdřuje kód bloku iterátoru. Vyvolání MoveNext metody provede kód v bloku iterátoru a podle potřeby nastaví Current vlastnost objektu enumerátoru. Přesná akce provedená MoveNext pomocí závisí na stavu objektu enumerátoru při MoveNext vyvolání:

  • Pokud je stav objektu enumerátoru před, vyvolání MoveNext:
    • Změní stav na spuštěný.
    • Inicializuje parametry (včetně this) bloku iterátoru na hodnoty argumentů a hodnotu instance uloženou při inicializaci objektu enumeratoru.
    • Spustí blok iterátoru od začátku, dokud se nepřeruší provádění (jak je popsáno níže).
  • Pokud je spuštěn stav objektu enumerátoru, výsledek vyvolání MoveNext není zadán.
  • Pokud je stav objektu enumerátoru pozastaven, vyvolání MoveNext:
    • Změní stav na spuštěný.
    • Obnoví hodnoty všech místních proměnných a parametrů (včetně this) na hodnoty uložené při posledním pozastavení bloku iterátoru.

      Poznámka: Obsah všech objektů odkazovaných těmito proměnnými se mohl od předchozího volání MoveNextzměnit . koncová poznámka

    • Obnoví provádění bloku iterátoru bezprostředně za příkazem výnosu, který způsobil pozastavení provádění a pokračuje, dokud se provádění nepřeruší (jak je popsáno níže).
  • Pokud je stav objektu enumerátoru za, vyvolání MoveNext vrátí hodnotu false.

Když MoveNext spustíte blok iterátoru, může být provádění přerušeno čtyřmi způsoby: yield return příkazem yield break , příkazem, narazí na konec bloku iterátoru a vyvoláním a rozšířením výjimky z bloku iterátoru.

  • yield return Pokud je zjištěn výrok (§9.4.4.20):
    • Výraz zadaný v příkazu je vyhodnocen, implicitně převeden na typ výnosu a přiřazen k Current vlastnosti enumerator objektu.
    • Provádění těla iterátoru je pozastaveno. Hodnoty všech místních proměnných a parametrů (včetně this) jsou uloženy, stejně jako umístění tohoto yield return příkazu. yield return Pokud je příkaz v jednom nebo více try blocích, přidružené bloky finally se v tuto chvíli nespustí.
    • Stav objektu enumerátoru se změní na pozastavený.
    • Metoda MoveNext se vrátí true do svého volajícího, což znamená, že iterace úspěšně pokročila na další hodnotu.
  • yield break Pokud je zjištěn výrok (§9.4.4.20):
    • yield break Pokud je příkaz v jednom nebo více try blocích, spustí se přidružené finally bloky.
    • Stav objektu enumerátoru se změní na následující.
    • Metoda MoveNext se vrátí false do svého volajícího, což znamená, že iterace je dokončena.
  • Při výskytu konce textu iterátoru:
    • Stav objektu enumerátoru se změní na následující.
    • Metoda MoveNext se vrátí false do svého volajícího, což znamená, že iterace je dokončena.
  • Při vyvolání a rozšíření výjimky z bloku iterátoru:
    • Šíření výjimky provede příslušné finally bloky v těle iterátoru.
    • Stav objektu enumerátoru se změní na následující.
    • Šíření výjimek pokračuje volajícím MoveNext metody.

15.14.5.3 Aktuální vlastnost

Vlastnost objektu Current enumerátoru je ovlivněna yield return příkazy v bloku iterátoru.

Pokud je objekt enumerátoru v pozastaveném stavu, hodnota je hodnota Current nastavena předchozím voláním MoveNext. Pokud je objekt enumerátoru v před, spuštěném nebo po stavu, není výsledek přístupu Current zadán.

Pro iterátor s jiným typem výnosu než object, výsledek přístupu Current prostřednictvím implementace enumerator objektu IEnumerable odpovídá přístupu prostřednictvím Current implementace enumerátoru objektu IEnumerator<T> a přetypování výsledku na object.

15.14.5.4 Metoda Dispose

Metoda Dispose slouží k vyčištění iterace přenesením objektu enumerátoru do po stavu.

  • Pokud je stav objektu enumerátoru před tím, vyvolání Dispose změní stav na za.
  • Pokud je spuštěn stav objektu enumerátoru, výsledek vyvolání Dispose není zadán.
  • Pokud je stav objektu enumerátoru pozastaven, vyvolání Dispose:
    • Změní stav na spuštěný.
    • Provede všechny bloky finally, jako by poslední spouštěný yield return příkaz byl yield break příkaz. Pokud to způsobí vyvolání a šíření výjimky z těla iterátoru, stav objektu enumerátoru je nastaven na za a výjimka se rozšíří do volající metody Dispose .
    • Změní stav na následující.
  • Pokud je stav objektu enumerátoru po, vyvolání Dispose nemá žádný vliv.

15.14.6 Výčtové objekty

15.14.6.1 Obecné

Pokud je člen funkce vracející typ enumerovatelného rozhraní implementován pomocí bloku iterátoru, vyvolání člena funkce okamžitě nespustí kód v bloku iterátoru. Místo toho se vytvoří a vrátí výčtový objekt. Enumerable objekt GetEnumerator metoda vrátí enumerator objekt, který zapouzdřuje kód zadaný v bloku iterátoru a spuštění kódu v bloku iterátoru dojde při vyvolání metody enumerator objektu MoveNext . Výčtový objekt má následující charakteristiky:

  • Implementuje IEnumerable a IEnumerable<T>, kde T je typ výnosu iterátoru.
  • Inicializuje se pomocí kopie hodnot argumentů (pokud existuje) a hodnoty instance předané členu funkce.

Enumerable objekt je obvykle instance kompilátor-generated enumerable třídy, která zapouzdřuje kód v iterátor bloku a implementuje enumerable rozhraní, ale jiné metody implementace jsou možné. Pokud kompilátor vygeneruje výčtovou třídu, bude tato třída přímo nebo nepřímo vnořena do třídy obsahující člen funkce, bude mít privátní přístupnost a bude mít název vyhrazený pro použití kompilátoru (§6.4.3).

Výčtový objekt může implementovat více rozhraní, než je uvedeno výše.

Poznámka: Například enumerable objekt může také implementovat IEnumerator a IEnumerator<T>umožnit, aby sloužil jako výčet i enumerátor. Taková implementace by obvykle vrátila vlastní instanci (pro uložení přidělení) z prvního volání .GetEnumerator Následné vyvolání GetEnumerator, pokud existuje, vrátí novou instanci třídy, obvykle stejné třídy, takže volání různých instancí enumerátoru nebudou mít vliv na sebe navzájem. Nemůže vrátit stejnou instanci, i když předchozí výčet již vyčíslil za konec sekvence, protože všechna budoucí volání výčtu výčtu musí vyvolat výjimky. koncová poznámka

15.14.6.2 Metoda GetEnumerator

Enumerable objekt poskytuje implementaci GetEnumerator metod IEnumerable a IEnumerable<T> rozhraní. GetEnumerator Dvě metody sdílejí společnou implementaci, která získává a vrací dostupný enumerátor objektu. Objekt enumerátoru se inicializuje s hodnotami argumentu a hodnotou instance uloženou při inicializaci výčtového objektu, ale jinak objekt enumerátoru funguje, jak je popsáno v §15.14.5.

15.15 Asynchronní funkce

15.15.1 Obecné

Metoda (§15.6) nebo anonymní funkce (§12.19) s modifikátorem se nazývá async. Obecně platí, že termín async se používá k popisu jakéhokoli druhu funkce, která má async modifikátor.

Jedná se o chybu v době kompilace pro seznam parametrů asynchronní funkce k určení libovolného inparametru , out, nebo ref parametrů nebo jakéhokoli parametru ref struct typu.

Return_type asynchronní metody musí být buď void nebo typ úkolu. Pro asynchronní metodu, která vytváří výslednou hodnotu, musí být typ úkolu obecný. Pro asynchronní metodu, která nevygeneruje výslednou hodnotu, nesmí být typ úkolu obecný. Tyto typy jsou uvedeny v této specifikaci jako «TaskType»<T> a «TaskType»v uvedeném pořadí. Typ System.Threading.Tasks.Task a typy standardní knihovny vytvořené z System.Threading.Tasks.Task<TResult> jsou typy úkolů, stejně jako třída, struktura nebo typ rozhraní, který je přidružen k typu tvůrce úloh prostřednictvím atributu System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. Tyto typy jsou uvedeny v této specifikaci jako «TaskBuilderType»<T> a «TaskBuilderType». Typ úkolu může mít maximálně jeden parametr typu a nelze jej vnořit do obecného typu.

Asynchronní metoda vracející typ úkolu se říká, že vrací úkol.

Typy úkolů se mohou lišit v přesné definici, ale z pohledu jazyka je typ úkolu v jednom ze stavů neúplných, úspěšných nebo chybných. Chybný úkol zaznamenává příslušnou výjimku. A succeeded«TaskType»<T> records a result of type T. Typy úkolů jsou očekávané a úkoly mohou být operandy výrazů await (§12.9.8).

Příklad: Typ MyTask<T> úlohy je přidružen k typu MyTaskMethodBuilder<T> tvůrce úloh a typu Awaiter<T>awaiter:

using System.Runtime.CompilerServices; 
[AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))]
class MyTask<T>
{
    public Awaiter<T> GetAwaiter() { ... }
}

class Awaiter<T> : INotifyCompletion
{
    public void OnCompleted(Action completion) { ... }
    public bool IsCompleted { get; }
    public T GetResult() { ... }
}

end example

Typ tvůrce úkolů je typ třídy nebo struktury, který odpovídá určitému typu úkolu (§15.15.2). Typ tvůrce úkolů musí přesně odpovídat deklarované přístupnosti odpovídajícího typu úkolu.

Poznámka: Pokud je typ úlohy deklarován internal, musí být odpovídající typ tvůrce deklarován internal a definován ve stejném sestavení. Pokud je typ úkolu vnořený do jiného typu, musí být typ buider úkolu také vnořený do stejného typu. koncová poznámka

Asynchronní funkce má schopnost pozastavit vyhodnocování pomocí výrazů await (§12.9.8) v těle. Vyhodnocení může být později obnoveno v okamžiku pozastavení výrazu await prostřednictvím delegáta obnovení. Delegát obnovení je typu System.Actiona při vyvolání se vyhodnocení asynchronní vyvolání funkce obnoví z výrazu await, kde skončil. Aktuální volající vyvolání asynchronní funkce je původní volající, pokud volání funkce nebylo nikdy pozastaveno nebo poslední volající delegáta obnovení jinak.

15.15.2 Model tvůrce typů úloh

Typ tvůrce úloh může mít maximálně jeden parametr typu a nelze jej vnořit do obecného typu. Typ tvůrce úkolů musí mít následující členy (pro typy tvůrce úloh, které nejsou obecné, SetResult nemají žádné parametry) s deklarovanou public přístupností:

class «TaskBuilderType»<T>
{
    public static «TaskBuilderType»<T> Create();
    public void Start<TStateMachine>(ref TStateMachine stateMachine)
                where TStateMachine : IAsyncStateMachine;
    public void SetStateMachine(IAsyncStateMachine stateMachine);
    public void SetException(Exception exception);
    public void SetResult(T result);
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public «TaskType»<T> Task { get; }
}

Kompilátor vygeneruje kód, který používá "TaskBuilderType" k implementaci sémantiky pozastavení a obnovení vyhodnocení asynchronní funkce. Kompilátor použije "TaskBuilderType" takto:

  • «TaskBuilderType».Create() je vyvolána k vytvoření instance «TaskBuilderType», pojmenované builder v tomto seznamu.
  • builder.Start(ref stateMachine)je vyvolána pro přidružení tvůrce k instanci stavového počítače vygenerovaného kompilátorem . stateMachine
    • Tvůrce zavolá stateMachine.MoveNext() buď in Start() , nebo poté Start() , co se vrátí, aby přešel do stavového stroje.
  • Po Start() vrácení async vyvolá metoda builder.Task pro úlohu vrátit z asynchronní metody.
  • Každé volání stateMachine.MoveNext() přejde na stavový počítač.
  • Pokud se stavový počítač úspěšně dokončí, builder.SetResult() je volána, s návratovou hodnotou metody, pokud existuje.
  • V opačném případě je vyvolána výjimka e ve stavovém počítači builder.SetException(e) .
  • Pokud stavový počítač dosáhne výrazu await expr , expr.GetAwaiter() vyvolá se.
  • Pokud awaiter implementuje ICriticalNotifyCompletion a IsCompleted je false, stavový počítač vyvolá builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine).
    • AwaitUnsafeOnCompleted() by měla volat awaiter.UnsafeOnCompleted(action) s tímto voláním ActionstateMachine.MoveNext() , jakmile se operátor awaiter dokončí.
  • Jinak stavový počítač vyvolá builder.AwaitOnCompleted(ref awaiter, ref stateMachine).
    • AwaitOnCompleted() by měla volat awaiter.OnCompleted(action) s tímto voláním ActionstateMachine.MoveNext() , jakmile se operátor awaiter dokončí.
  • SetStateMachine(IAsyncStateMachine) může být volána kompilátorem vygenerovanou IAsyncStateMachine implementací k identifikaci instance tvůrce přidruženého k instanci stavového počítače, zejména v případech, kdy je stavový počítač implementován jako typ hodnoty.
    • Pokud tvůrce volá stateMachine.SetStateMachine(stateMachine), bude volání stateMachinebuilder.SetStateMachine(stateMachine).

Poznámka: Pro oba SetResult(T result) a «TaskType»<T> Task { get; }, parametr a argument v uvedeném pořadí musí být identity konvertibilní na T. To umožňuje tvůrci typů úloh podporovat typy, jako jsou řazené kolekce členů, kde dva typy, které nejsou stejné, jsou konvertibilní identity. koncová poznámka

15.15.3 Vyhodnocení asynchronní funkce vracející úlohu

Vyvolání asynchronní funkce vracející úlohu způsobí vygenerování instance vráceného typu úlohy. Tomu se říká návratová úloha asynchronní funkce. Úkol je zpočátku v neúplném stavu.

Tělo asynchronní funkce se pak vyhodnocuje, dokud nebude pozastaven (dosažením výrazu await) nebo ukončen, v jakém okamžiku se ovládací prvek vrátí volajícímu spolu s vrácenou úlohou.

Když se tělo asynchronní funkce ukončí, návratový úkol se přesune z neúplného stavu:

  • Pokud se tělo funkce ukončí v důsledku dosažení návratového příkazu nebo konce těla, zaznamená se do návratového úkolu jakákoli výsledná hodnota, která se vloží do úspěšného stavu.
  • Pokud tělo funkce skončí z důvodu nezachycené OperationCanceledException, výjimka se zaznamená do návratové úlohy, která je vložena do zrušeného stavu.
  • Pokud se tělo funkce ukončí jako výsledek jakékoli jiné nezachycené výjimky (§13.10.6), zaznamená se výjimka do návratového úkolu, který je vložen do chybného stavu.

15.15.4 Vyhodnocení asynchronní funkce vracející void

Pokud je voidnávratový typ asynchronní funkce , vyhodnocení se liší od výše uvedeného způsobem: Protože není vrácen žádný úkol, funkce místo toho komunikuje dokončení a výjimky s kontextem synchronizace aktuálního vlákna. Přesná definice kontextu synchronizace je závislá na implementaci, ale představuje reprezentaci "where" aktuální vlákno je spuštěno. Kontext synchronizace se oznámí, když se zahájí vyhodnocení voidasynchronní funkce vracející asynchronní funkci, úspěšně se dokončí nebo způsobí vyvolání nezachycené výjimky.

To umožňuje kontextu sledovat, kolik voidasynchronních funkcí pod ním běží, a rozhodnout se, jak z nich vycházet výjimky.