Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
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.
Struktury (§16) a rozhraní (§18) mají členy podobné třídám, ale s určitými omezeními. Tato klauzule definuje deklarace pro třídy a členy třídy. Klauzule pro struktury a rozhraní definují omezení pro tyto typy z hlediska odpovídajících deklarací v typech tříd.
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ů (§23), za kterou 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 volitelném type_parameter_list (§15.2.3) následuje volitelná specifikace class_base (§15.2.4), za kterou následuje volitelná sada type_parameter_constraints_clauses (§15.2.5), za ním následuje class_body (§15.2.6), po kterém 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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
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
newv 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ýtnullnebo 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
Azavádí abstraktní metoduF. TřídaBzavádí další metoduG, ale protože neposkytuje implementaciF,Bmusí být deklarována také abstraktní. TřídaCpřepisujeFa poskytuje skutečnou implementaci. Vzhledem k tomu, že neexistují žádné abstraktní členyC,Cje povoleno (ale nevyžaduje) být ne abstrakt.konec příkladu
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:
sealedModifiká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
sealedaniabstractmodifiká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 protectedaniprotected internaldeklarovanou 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
Tve namespace_or_type_name formulářeT.I, nebo - Název namespace_or_type je
Tv typeof_expression (§12.8.18) formulářetypeof(T).
Primary_expression (§12.8) je povoleno odkazovat na statickou třídu, pokud
- Primary_expression je
Ev member_access (§12.8.7) formulářeE.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,
sizeofvý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. Naproti tomu argument typu (§8.4.2) je typ, který je nahrazen parametrem typu při vytvoření vytvořeného typu.
type_parameter_list
: '<' decorated_type_parameter (',' decorated_type_parameter)* '>'
;
decorated_type_parameter
: 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í (§19) 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
Aje řečeno, že je přímá základní třídaB, aBje řečeno, že je odvozena zA. Vzhledem k tomuA, že explicitně nezadává přímou základní třídu, její přímá základní třída je implicitněobject.konec příkladu
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 bylaB<string,int[]>.konec příkladu
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> {}konec příkladu
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í o třídě navíc nesmí být používáno System.Attribute jako přímá nebo nepřímá základní třída (§23.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řídaZpovažována zaobject, a proto (podle pravidel §7.8)Znení považována za členaY.konec příkladu
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>jsouC<int[]>,B<IComparable<int[]>>,Aaobject.konec příkladu
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 typu, ve kterém je bezprostředně vnořena (pokud existuje).
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í naB(její bezprostředně uzavřená třída), která cyklicky závisí naA.konec příkladu
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í naA(protožeAje její přímá základní třída i její okamžitě uzavřená třída), aleAnezávisí na (protožeBse nejedná oBzákladní třídu ani ohraničující tříduA). Proto je příklad platný.konec příkladu
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 classTřída
Bje chybná, protože se pokouší odvodit z zapečetěné třídyA.konec příkladu
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í může být pojmenováno pouze jednou v každé části, 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
CjeIA,IBaIC.konec příkladu
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 { ... }konec příkladu
Základní rozhraní zadaná v deklaraci třídy mohou být vytvořena typy rozhraní (§8.4, §19.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> {}konec příkladu
Implementace rozhraní jsou dále popsány v §19.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_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_annotation 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 : classneboT : BaseClass), ale vT?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 lze ?použít pouze u parametru typu typu, který má omezení typu hodnoty, omezení typu odkazu bez nullable_type_annotation nebo omezení typu třídy bez nullable_type_annotation.
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.konec příkladu
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í.
Protože notnull není klíčovým slovem, v primary_constraint je omezení not null vždy syntakticky nejednoznačné s class_type. Z důvodu kompatibility je-li název vyhledávání (§12.8.4) jména notnull úspěšné, musí být považován za class_type. V opačném případě se považuje za omezení, které není null.
Příklad: Následující třída ukazuje použití různých argumentů typu proti různým omezením, což označuje upozornění, která mohou být vydána kompilátorem.
#nullable enable public class C { } public class A<T> where T : notnull { } public class B1<T> where T : C { } public class B2<T> where T : C? { } class Test { static void M() { // nonnull constraint allows nonnullable struct type argument A<int> x1; // possible warning: nonnull constraint prohibits nullable struct type argument A<int?> x2; // nonnull constraint allows nonnullable class type argument A<C> x3; // possible warning: nonnull constraint prohibits nullable class type argument A<C?> x4; // nonnullable base class requirement allows nonnullable class type argument B1<C> x5; // possible warning: nonnullable base class requirement prohibits nullable // class type argument B1<C?> x6; // nullable base class requirement allows nonnullable class type argument B2<C> x7; // nullable base class requirement allows nullable class type argument B2<C?> x8; } }
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 proT. Proto rekurzivně konstruované typy formulářůT??aNullable<Nullable<T>>jsou zakázány. koncová poznámka
Omezení nespravovaného typu určuje, že argument typu použitý pro parametr typu je nespravovatelný nespravovaný typ (§8.8).
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í.
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.ArrayneboSystem.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é
whereklauzuli 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é
whereklauzuli 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
Ttypu použije jako omezení parametru typuS,Szávisí naTtom. - Pokud parametr
Stypu závisí na parametruTtypu aTzávisí na parametruUtypu,Szávisí naUtom.
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:
-
Tnesmí mít omezení typu hodnoty. V opačném případě je účinně zapečetěno,TtakžeSby bylo nuceno být stejný typ jakoT, eliminuje potřebu dvou parametrů typu. - Pokud
Smá omezení typu hodnoty,Tnesmí mít class_type omezení. - Pokud
Smá aAmáT, bude existovat převod identity nebo implicitní převod odkazu zBnebo implicitní převod odkazu zABna . - Pokud
Staké závisí na parametruUtypu aUmá aAmáT, musí existovat převod identity nebo implicitní převod odkazu zBnebo implicitní převod odkazu zAnaB.
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 { ... }konec příkladu
Dynamické vymazání typu C je Cₓ typu vytvořené takto:
- Pokud
Cje vnořený typOuter.Inner,Cₓjedná se o vnořený typOuterₓ.Innerₓ. - Pokud
CCₓje konstruovaný typG<A¹, ..., Aⁿ>s argumentyA¹, ..., Aⁿtypu, pakCₓje konstruovaný typG<A¹ₓ, ..., Aⁿₓ>. - Pokud
Cje typE[]pole, jednáCₓse o typEₓ[]pole . - Pokud
Cje dynamická, pakCₓjeobject. -
CₓV opačném případě jeC.
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,Robsahuje jeho efektivní základní třídu. - Pro každé omezení
T, které je typu struktury,RobsahujeSystem.ValueType. - Pro každé omezení, které je typu výčtu
T,RobsahujeSystem.Enum. - Pro každé omezení
T, které je typu delegáta,Robsahuje jeho dynamické vymazání. - Pro každé omezení
T, které je typu pole,RobsahujeSystem.Array. - Pro každé omezení
T, které je typem třídy,Robsahuje jeho dynamické vymazání.
Pak...
- Pokud
Tmá omezení typu hodnoty, jeho efektivní základní třída jeSystem.ValueType. - V opačném případě, pokud
Rje prázdná, efektivní základní třída jeobject. - V opačném případě je účinná základní třída
Tnejzahrnující typ (§10.5.3) množinyR. Pokud sada neobsahuje žádný typ, je platná základní třídaT.objectPravidla 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
Tnemá žádné secondary_constraints, jeho efektivní sada rozhraní je prázdná. - Pokud
Tmá interface_type omezení, ale žádná omezení type_parameter, je jeho platná sada rozhraní sadou dynamických vymazání interface_type omezení. - Pokud
Tnemá žá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
Tmá 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
IPrintablelze vyvolat přímo,xprotožeTje omezena na vždy implementovatIPrintable.konec příkladu
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.
konec příkladu
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 Deklarace částečného typu
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 bezprostředně před klíčovými slovy class, structa interface. (Částečný typ může obsahovat částečné deklarace metod (§15.6.9).
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 shodují, 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 mohou být deklarovány 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; }konec příkladu
Zpracování atributů určených pro parametry typu nebo typu různých částí částečné deklarace typu je popsáno v §23.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
partialdeklarace 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í ,outaref.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
refpodle aout.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 nemá partial modifikátor.
Příklad:
partial class A { int x; // Error, cannot declare x more than once partial void M(); // Ok, defining partial method declaration partial class Inner // Ok, Inner is a partial type { int y; } } partial class A { int x; // Error, cannot declare x more than once partial void M() { } // Ok, implementing partial method declaration partial class Inner // Ok, Inner is a partial type { int z; } }konec příkladu
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: Dkonec příkladu
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
aobecné třídy je "dvojrozměrné poleGen", takže typ členuTv vytvořeném typu výše je "dvojrozměrné pole jednorozměrného polea" neboint.int[,][]konec příkladu
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 } }konec příkladu
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
Codvozen z , aBje odvozen zBA, pakCdědí členy deklarované,Bstejně jako členy deklarované vA.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 argumentuinttypu parametru typuT.D<int>má také zděděný člen z deklaraceBtřídy . Tento zděděný člen je určen prvním určením základního typuB<int[]>D<int>třídy nahrazenímintTve specifikaciB<T[]>základní třídy . Potom jako typ argumentuBint[], je nahrazenUvpublic U F(long index), výnos zděděný členpublic int[] F(long index).konec příkladu
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í typydaného č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é členskés, nebo instance člens.
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
Modkazován v member_access (§12.8.7) formulářeE.M,Eoznačuje typ, který má členaM. Jedná se o chybu v době kompilace,Ekterá 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
Minstance odkazováno v member_access (§12.8.7) formulářeE.M,Eoznačí se instance typu, který má členaM. 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
Fukazuje, ž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. MetodaGukazuje, že ve statickém členu funkce se jedná o chybu v době kompilace pro přístup k členu instance prostřednictvím simple_name. MetodaMainukazuje, ž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ů.konec příkladu
15.3.9 Vnořené typy
15.3.9.1 Obecné
Typ deklarovaný v rámci třídy, struktury nebo rozhraní 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
Bje vnořený typ, protože je deklarován v rámci třídyAa třídaAje nenořený typ, protože je deklarován v rámci kompilační jednotky.konec příkladu
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, strukturu nebo rozhraní:
- 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
privatedeklarovanou přístupnost. - Vnořený typ, který je deklarován ve struktuře, může mít libovolnou ze tří forem deklarované přístupnosti (
public,internalneboprivate) a stejně jako ostatní členy struktury se ve výchozím nastaveníprivatedeklaruje 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.konec příkladu
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 metoduMdefinovanou vBase.konec příkladu
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
CinstanceNesteda předá své vlastníNesteddo konstruktoru s cílem poskytnout následný přístup keCčlenům instance.konec příkladu
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říduNested. VNestedrámci metodyGvolá statickou metoduFdefinovanou vCaFmá privátní deklarovanou přístupnost.konec příkladu
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.Nestedpřistupuje k chráněné metoděFdefinované vDerived'základní třídě ,Basevoláním prostřednictvím instanceDerived.konec příkladu
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 } }konec příkladu
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 } }konec příkladu
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:
- 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#.
- 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#.
- 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
Adefinuje vlastnostPjen pro čtení , a proto si rezervuje podpisy proget_Paset_Pmetody.AtřídaBje odvozena zAobou těchto rezervovaných podpisů a skryje je. Příklad vytvoří výstup:123 123 456konec příkladu
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ů (§23), 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ý pojmenuje člena a za ním následuje token "=" následovaný constant_expression (§12.25), 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ří
casepopisky,goto casepříkazy,enumdeklarace členů, atributy a další deklarace konstant. koncová poznámka
Poznámka: Jak je popsáno v §12.25, 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ž
stringje použitínewoperátoru, a protoženewoperátor není povolen v constant_expression, jediná možná hodnota pro konstanty reference_typejiné nežstringjenull. 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
constsprávy verzí areadonlyodliš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; }konec příkladu
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 vyhodnotitB.Za nakonec vyhodnotitA.X, vytvořit hodnoty10,11a12.konec příkladu
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
AaBbyly deklarovány v samostatných programech, by bylo možnéA.Xzáviset naB.Z, aleB.Znemohl by pak současně záviset naA.Y. konec příkladu
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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
Field_declaration může obsahovat sadu atributů (§23), 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.23). Hodnotu nečteného pole lze získat i upravit pomocí operátorů přírůstku a dekrementace přípony (§12.8.16) a operátory inkrementace a dekrementace předpony (§12.9.7).
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; }konec příkladu
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 s. Statická pole a pole instancí jsou dvěma druhy proměnných (§9) podporovaných jazykem C# a v časech 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, s výjimkou místních funkcí a anonymních funkcí, a pouze na instanci, která je vytvořena. U statického pole v statickém konstruktoru nebo inicializátoru statického pole nebo vlastnosti ve třídě, která obsahuje deklaraci pole s výjimkou místních funkcí a anonymních funkcí. 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,RedGreenaBluečleny nelze deklarovat jako členy const, protože jejich hodnoty nelze vypočítat v době kompilace. Deklarování jestatic readonlyale má mnohem stejný účinek.konec příkladu
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); } } }
Program1OboryProgram2názvů označují dva programy, které jsou kompilovány samostatně. ProtožeProgram1.Utils.Xje deklarován jakostatic readonlypole, výstup hodnoty příkazemConsole.WriteLinenení znám v době kompilace, ale spíše je získán za běhu. Proto pokud je hodnotaXzměněna aProgram1je rekompilována,Console.WriteLinepříkaz vypíše novou hodnotu i v případěProgram2, že není rekompilován. BylaXby však konstanta, hodnotaXby byla získána v doběProgram2kompilace a zůstala by nedotčena změnami,Program1dokudProgram2nebude znovu zkompilována.konec příkladu
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, ,ushortneboint.
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 = 143V tomto příkladu metoda
Mainspustí nové vlákno, které spustí metoduThread2. Tato metoda ukládá hodnotu do nevolatelní pole volanéresult, pak ukládátruedo nestálého polefinished. Hlavní vlákno čeká na nastavenífinishedpoletruea pak přečte poleresult. Vzhledem k tomufinished, že bylo deklarovánovolatile, musí hlavní vlákno číst hodnotu143z poleresult.finishedPokud pole nebylo deklarovánovolatile, pak by bylo možné, abyresultúložiště bylo viditelné pro hlavní vlákno po uloženífinisheddo , a proto hlavní vlákno číst hodnotu 0 z poleresult. Deklarovánífinishedjakovolatilepole brání jakékoli takové nekonzistence.konec příkladu
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 = 0a
bioba jsou automaticky inicializovány na výchozí hodnoty.konec příkladu
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 = Hellovzhledem k tomu, že přiřazení, ke kterému dojde
x, když inicializátory statických polí provádějí aispři provádění inicializátorů polí instance.konec příkladu
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
aab, program je platný. Výsledkem je výstup.a = 1, b = 2vzhledem k tomu, že statická pole
aabinicializují se do0(výchozí hodnota proint) před spuštěním jejich inicializátorů. Při inicializátoru spuštěníaje hodnotabnula, a protoaje inicializována na1. Při inicializátoru spuštěníbje hodnota již1a takbje inicializována na2.konec příkladu
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 1nebo výstup:
Init B Init A 1 1vzhledem k tomu, že spuštění inicializátoru
XaYinicializá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 1vzhledem k tomu, že pravidla pro provádění statických konstruktorů (jak je definováno v §15.12) stanoví, že
Bstatický konstruktor (a protoBinicializátory statických polí) se spustí předAstatickým konstruktorem a inicializátory polí.konec příkladu
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á
yza následek chybu v době kompilace, protože odkazuje na člena vytvářené instance.konec příkladu
15.6 Metody
15.6.1 Obecné
§15.6 a jeho dílčí seznamy zahrnují deklarace metod ve třídách. Tento text je rozšířen informacemi o deklarování metod ve strukturách (§16.4) a rozhraní (§19.4.3).
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'
| 'readonly' // direct struct members only
| 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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
- 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 mohou zahrnovat sadu atributů (§23) a jeden z povolených druhů deklarované přístupnosti (§15.3.6), new (§15.3.5), (§15.3.5), static (§15.6.3), (§15.6.3), (§15.6.3), (§15.6.3), (§15.6.3), (§15.6.3), (§15.6.3) virtual15.6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7), extern (§15.6.8) a async (§15.14). Kromě toho method_declaration, která je přímo obsažena v struct_declaration, může obsahovat readonly modifikátor (§16.4.12).
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,virtualaoverride. - Deklarace obsahuje nejvýše jeden z následujících modifikátorů:
newaoverride. - Pokud deklarace obsahuje
abstractmodifikátor, deklarace neobsahuje žádný z následujících modifikátorů:static,virtual,sealedneboextern. - Deklarace může obsahovat
abstractaoverridemodifikátory tak, aby abstraktní člen mohl přepsat virtuální člen. - Pokud deklarace obsahuje
privatemodifikátor, deklarace neobsahuje žádný z následujících modifikátorů:virtual,overrideneboabstract. - Pokud deklarace obsahuje
sealedmodifikátor, pak deklarace obsahujeoverridetaké modifikátor. - Pokud deklarace obsahuje
partialmodifikátor, neobsahuje žádný z následujících modifikátorů:new,public, ,protectedinternal,private,virtualsealed,override, , neboabstractextern.
Metody jsou klasifikovány podle toho, co, pokud něco, vrátí:
- Pokud
refje 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
voida 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.14.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, buď s modifikátorem override, nebo pro explicitní implementaci člena rozhraní, dědí omezení parametrů typu z přepsané metody nebo člena rozhraní. Takové prohlášení mohou mít pouze type_parameter_constraints_clauseobsahující primary_constraints class a structvýznam, jehož význam je v tomto kontextu definován v §15.6.5 a §19.6.2 pro přepisování metod a explicitní implementace rozhraní.
Member_name určuje název metody. Není-li metodou implementace explicitního člena rozhraní (§19.6.2), je member_name jednoduše identifikátorem.
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).
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?
| 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ů (§23); volitelný in, , out, refnebo this modifikátor; typ; identifikátor; a volitelný 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(), kdeSje typ hodnoty - výraz formuláře
default(S), kdeSje 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 (§15.6.9) dojde k volitelným parametrům, měl by kompilátor explicitní implementaci člena rozhraní (§19.6.2), deklaraci indexeru s jedním parametrem (§15.9) nebo v deklaraci operátoru (§15.10.1) 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ů (§23), 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ýrefparametrd, je povinný parametrbhodnoty , ,soatjsou volitelné parametry hodnoty aaje pole parametrů.konec příkladu
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ů:
- Parametry hodnot (§15.6.2.2).
- Vstupní parametry (§15.6.2.3.2).
- Výstupní parametry (§15.6.2.3.4).
- Referenční parametry (§15.6.2.3.3).
- Pole parametrů (§15.6.2.4).
Poznámka: Jak je popsáno v §7.6,
inmodifikátory ,outarefmodifikátory jsou součástí podpisu metody, aleparamsmodifiká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.
U funkcí deklarovaných jako iterátor (§15.15) nebo asynchronní funkce (§15.14) nejsou povoleny referenční parametry.
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 = 1Pro vyvolání
SwapinMain,xpředstavujeiaypředstavujej. Vyvolání tedy má vliv na prohození hodnot aij.konec příkladu
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í
FinGpředá odkaz nasobojíaib. Proto pro toto vyvolání, názvys,aabvšechny odkazují na stejné umístění úložiště a tři přiřazení všechny upravují polesinstance .konec příkladu
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.txtVšimněte si, že
dirpřed předánímnameproměnné aSplitPathproměnné mohou být nepřiřazeny a že jsou považovány za rozhodně přiřazené po volání.konec příkladu
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[]astring[][]lze je použít jako typ pole parametrů, ale typstring[,]nemůže. konec příkladu
Poznámka: Modifikátor není možné kombinovat
paramss modifikátoryin,outneboref. 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í
Fjednoduše předá polearrjako parametr hodnoty. Druhé vyvolání jazyka F automaticky vytvoří čtyři prvkyint[]s danými hodnotami elementu a předá instanci pole jako parametr hodnoty. Stejně tak třetí vyvoláníFvytvoří prvekint[]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[] {});konec příkladu
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ů.
konec příkladu
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 FalseDruhé vyvolání vznikne
Falsetak, jak je ekvivalentníF(new string[] { null })a předává pole obsahující jediný odkaz s hodnotou null.konec příkladu
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.DoubleV prvním a posledním vyvolání
F, normální formaFje použitelná, protože implicitní převod existuje z typu argumentu na typ parametru (oba jsou typuobject[]). Rozlišení přetížení tedy vybere normální formuFa argument se předá jako normální parametr hodnoty. Ve druhém a třetím vyvolání není normální formaFpoužitelná, protože neexistuje žádný implicitní převod z typu argumentu na typ parametru (typobjectnelze implicitně převést na typobject[]). Rozšířená formaFje však použitelná, takže je vybrána překladem přetížení. Výsledkem je vytvoření jednoho prvkuobject[]vyvoláním a jeden prvek pole je inicializován s danou hodnotou argumentu (což je samotný odkaz naobject[]).konec příkladu
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
Cze sady metod deklarovaných a zděděnýchN.AMCToto je popsáno v §12.8.10.2. - Pak za běhu:
- Pokud
Mje jiná než virtuální metoda,Mje vyvolána. -
MV opačném případě je virtuální metoda a je vyvolána nejvíce odvozená implementaceMs ohledem naR.
- Pokud
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
Robsahuje zavedení virtuální deklaraceM, pak je to nejvýraznější implementaceMs ohledem naR. - Jinak, pokud
Robsahuje přepsáníM, pak je to nejvýraznější implementaceMs ohledem naR. - V opačném případě je nejvýraznější implementace
Ms ohledem naRnejvíce odvozenou implementaciMs ohledem na přímou základní tříduR.
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
Apředstavuje ne-virtuální metoduFa virtuální metoduG. TřídaBzavádí novou ne-virtuální metoduF, čímž skryje zděděnýF, a také přepíše zděděnou metoduG. Příklad vytvoří výstup:A.F B.F B.G B.GVšimněte si, že příkaz
a.G()vyvoláB.G, neA.G. Důvodem je to, že typ spuštění instance (což jeB), nikoli typ kompilace instance (což jeA), určuje skutečnou implementaci metody, která se má vyvolat.konec příkladu
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
CDobsahují dvě virtuální metody se stejným podpisem: jeden zavedenýAa druhý zaveden .CMetoda zavedená skrytímCmetody zděděné zA. Proto přepsání deklarace vDpřepsání metody zavedenéC, a není možnéDpřepsat metodu zavedenou metodouA. Příklad vytvoří výstup:B.F B.F D.F D.FVšimněte si, že je možné vyvolat skrytou virtuální metodu přístupem k instanci
Dprostřednictvím méně odvozeného typu, ve kterém metoda není skrytá.konec příkladu
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.
Přepsání metody dědí libovolnou type_parameter_constraints_clause přepsané základní metody.
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.
-
type_parameter_constraints_clause se mohou skládat pouze z
classstruct, které jsou aplikovány na type_parameter a jsou známé podle zděděných omezení, aby byly buď odkazové, nebo hodnotové typy, respektive. Jakýkoli typ ve forměT?v podpisu přepisující metody, kdeTje parametr typu, se interpretuje takto:-
classPokud je pro parametrTtypu přidáno omezení,T?je odkazový typ s možnou hodnotou null; jinak - Pokud není přidané žádné omezení nebo je přidáno omezení
struct, pak pro typový parametrTjeT?nullable typ hodnoty.
-
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> }konec příkladu
Příklad: Následující ukazuje, jak fungují pravidla přepisování, když jsou zapojeny parametry typu:
#nullable enable class A { public virtual void Foo<T>(T? value) where T : class { } public virtual void Foo<T>(T? value) where T : struct { } } class B: A { public override void Foo<T>(T? value) where T : class { } public override void Foo<T>(T? value) where T : struct { } }Bez omezení parametru typu
where T : classnelze přepsat metodu základu, která má parametr typu s referenčním typem. konec příkladu
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áBmetodu PrintFields deklarovanou vA. Base_access zakáže virtuální mechanismus vyvolání a jednoduše považuje základní metodu za metodu bezvirtualmetody. Bylo-li vyvolání zapsánoB, by rekurzivně vyvolá metodu((A)this).PrintFields()deklarovanou vPrintFields, nikoli vB, protožeAje virtuální a typPrintFieldsběhu je((A)this).Bkonec příkladu
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
FBneobsahujeoverridemodifikátor, a proto nepřepíše metoduFvA. Metoda vFskrytí metody vBa upozornění je hlášena,Aprotože deklarace neobsahuje nový modifikátor.konec příkladu
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
FvBskrytí virtuálníFmetody zděděné zA. Vzhledem k tomu, že novýFvBmá soukromý přístup, jeho rozsah zahrnuje pouze těloBtřídy a neprovádí se naC.FDeklarace inCje proto povolena k přepsáníFzděděného zděděného zA.konec příkladu
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
Bposkytuje dvě metody přepsání:Fmetoda, která másealedmodifikátor a metoduG, která není.Bpoužití modifikátorusealedzabraňujeCdalšímu přepsáníF.konec příkladu
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. Method_body abstraktní metody se jednoduše skládá z středníku.
Prohlášení abstraktní metody jsou povolena pouze v abstraktních třídách (§15.2.2.2) a rozhraní (§19.4.3).
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
Shapedefinuje abstraktní pojem geometrického objektu obrazce, který může malovat sám. MetodaPaintje abstraktní, protože neexistuje smysluplná záložní implementace pro abstraktní koncept tvaru.EllipseABoxtřídy jsou konkrétníShapeimplementace. Vzhledem k tomu, že tyto třídy nejsou abstraktní, jsou vyžadovány k přepsáníPaintmetody a poskytnutí skutečné implementace.konec příkladu
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.konec příkladu
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
Adeklaruje virtuální metodu, třídaBpřepíše tuto metodu abstraktní metodou a třídaCpřepíše abstraktní metodu tak, aby poskytovala vlastní implementaci.konec příkladu
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í
externmodifikátoru a atributuDllImport: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); }konec příkladu
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. Identifikátor partial je rozpoznán 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 musí existovat pouze jedna definice částečné deklarace metody s daným podpisem a musí existovat maximálně jedna prováděcí deklarace částečné 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
Mv 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
partialnení 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 z
M§12.8.17.5).Jedná se o chybu v době kompilace odkazující na
Muvnitř 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.Mnemůž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.
- Může to být pouze referenční parametr, pokud má typ hodnoty nebo má obecný typ omezený na strukturu.
- Nesmí se jednat o výstupní parametr.
- 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; } }konec příkladu
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
Sliceje k dispozici nastring[]aToInt32metoda je k dispozici nastring, 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)); } } }konec příkladu
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.14.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.14.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í
Fhodnotu je chyba v době kompilace, protože řízení může dojít mimo konec těla metody.GAHmetody 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. MetodaIje správná, protože její tělo je ekvivalentní bloku pouze s jedním návratovým příkazem v něm.konec příkladu
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'
| 'readonly' // direct struct members only
| 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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
Property_declaration mohou obsahovat sadu atributů (§23) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new (§15.3.5), static (§15.7.2),virtual (§15.6.4, §15.7.6), override (§15.6.5, §15.7.6), sealed (§15.6.6), abstract (§15.6.7, §15.7.6) a extern (§15.6.8). Kromě toho deklarace_vlastnosti, která je obsažena přímo v deklaraci_struktury, může obsahovat readonly modifikátor (§16.4.11).
- 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ů (§23) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new (§15.3.5), static (§15.7.2),virtual (§15.6.4, §15.7.6), override (§15.6.5, §15.7.6), sealed (§15.6.6), abstract (§15.6.7, §15.7.6) a extern (§15.6.8) 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í (§19.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,outneborefargument, 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: Tento pododstavec se vztahuje na obě vlastnosti (§15.7) i indexery (§15.9). Dílčí část je sepsána ve smyslu vlastností; při čtení indexerů nahraďte indexer/indexery za vlastnost/vlastnosti a prozkoumejte seznam rozdílů mezi vlastnostmi a indexery uvedený 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'
| 'readonly' // direct struct members only
;
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
readonlyje povolen pouze ve property_declaration nebo indexer_declaration, které jsou přímo obsaženy ve struct_declaration (§16.4.11, §16.4.13). - U vlastnosti nebo indexeru, který nemá žádný
overridemodifiká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 protectedbuď , ,protected internal,internal,protectedneboprivate. - Pokud má vlastnost nebo indexer deklarovanou přístupnost
protected internal, může být přístupnost deklarovanáprivate protectedbuď , ,protected private,internal,protectedneboprivate. - Pokud má vlastnost nebo indexer deklarovanou přístupnost
internalprotectednebo , musí být přístupnost deklarovaná accessor_modifier buďprivate protectedneboprivate. - 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 .
- Pokud má vlastnost nebo indexer deklarovanou přístupnost
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éhoref, 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 } }konec příkladu
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.23) nebo jako operand nebo ++–- (§12.8.16, §12.9.7), je přistupující sada vyvolána argumentem, který poskytuje novou hodnotu (§12.23.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í
Buttonprvek deklaruje veřejnouCaptionvlastnost. Get accessor of the Caption vlastnost vrátístringuložené v privátnímcaptionpoli. 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ženouprivatev poli a objekt set změní totoprivatepole a pak provede všechny další akce potřebné k aktualizaci stavu objektu. Výše uvedenáButtontřída představuje příklad použitíCaptionvlastnosti:Button okButton = new Button(); okButton.Caption = "OK"; // Invokes set accessor string s = okButton.Caption; // Invokes get accessorV 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.
konec příkladu
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.
konec příkladu
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
PBskryjePvlastnostAv souvislosti s čtením i zápisem. Proto v příkazechB b = new B(); b.P = 1; // Error, B.P is read-only ((A)b).P = 1; // Ok, reference to A.Ppřiřazení, které
b.Pzpůsobí nahlášenou chybu v době kompilace, protože vlastnostPjen proBčtení skryje vlastnost jenPpro zápis vA. Všimněte si však, že přetypování lze použít pro přístup ke skrytéPvlastnosti.konec příkladu
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á
Pointstrukturu 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; }
LabelV této třídě se používají dvěintpolexayk uložení jeho umístění. Umístění je veřejně vystaveno jak jako vlastnostXY, tak jakoLocationvlastnost typuPoint. Pokud se v budoucí verziLabel, stává se pohodlnější uložit umístění jakoPointinterně, 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
xaymísto toho bylapublic readonlypole, bylo by nemožné provést takovou změnuLabeltřídy.konec příkladu
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
Nextvlastnosti 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.
konec příkladu
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
Consoleobsahuje tři vlastnosti, ,InOutaError, 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,Consoledokud se skutečně nepoužívají. Například při prvním odkazování naOutvlastnost, jako vConsole.Out.WriteLine("hello, world");vytvoří se podklad
TextWriterpro výstupní zařízení. Pokud však aplikace nebude odkazovat na vlastnostiInaErrorvlastnosti, nebudou se pro tato zařízení vytvářet žádné objekty.konec příkladu
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; } } }konec příkladu
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.
konec příkladu
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
NonSerializedcí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; } } }konec příkladu
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.23.2), musí existovat a být přístupný.
- Pokud je použití jako cíl složeného přiřazení (§12.23.4) nebo jako cíl
++operátorů (--§12.8.16, §12.9.7), 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 vlastnostB.Countnení přístupná pro tříduM, takže je použita přístupná vlastnostA.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 } }konec příkladu
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 } }konec příkladu
15.7.6 Virtuální, zapečetěné, přepsání a abstraktní přístupové objekty
Poznámka: Tento pododstavec se vztahuje na obě vlastnosti (§15.7) i indexery (§15.9). Dílčí část je sepsána ve smyslu vlastností; při čtení indexerů nahraďte indexer/indexery za vlastnost/vlastnosti a prozkoumejte seznam rozdílů mezi vlastnostmi a indexery uvedený 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í.
Prohlášení o abstraktním majetku jsou povolena pouze v abstraktních třídách (§15.2.2.2) a rozhraní (§19.4.4). 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; } }
Xje virtuální vlastnost jen pro čtení,Yje virtuální vlastností pro čtení i zápis aZje abstraktní vlastností pro čtení i zápis. Vzhledem k tomuZ, ž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,YaZjsou 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 ofXa set accessor of use theYbase keyword to access the zděděné přístupové objekty. Deklarace přepsání jak abstraktních přístupovýchZobjektů, tak neexistují žádné nevyřízenéabstractčlenyBfunkce aBje povoleno být ne-abstraktní třídou.konec příkladu
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 } }konec příkladu
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é rutiny 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'
| 'readonly' // direct struct members only
| 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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
Event_declaration může obsahovat sadu atributů (§23) 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. Kromě toho event_declaration , která je obsažena přímo struct_declaration může obsahovat readonly modifikátor (§16.4.12).
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í.
V případě, x += yx –= yx že je výsledkem operace typ void (§12.23.5) (na rozdíl od typu xza přiřazením , s hodnotou x za přiřazením, stejně jako u jiných +=-= operátorů definovaných u typů mimo události). 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
Buttontří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 } }
LoginDialogZde konstruktor instance vytvoří dvěButtoninstance a připojí obslužné rutiny událostí k událostemClick.konec příkladu
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 (§21), který odkazuje na seznam obslužných rutin událostí, které byly přidány do události. 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; }
Clickslouží jako pole v rámciButtontřídy. Jak ukazuje příklad, pole lze prozkoumat, upravit a použít ve výrazech vyvolání delegáta. MetodaOnClickveButtontřídě vyvoláClickudá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
ButtontřídyClicklze člen použít pouze na levé straně+=operátorů a–=operátorů, jak je uvedeno v částib.Click += new EventHandler(...);který připojí delegáta k seznamu
Clickvyvolání události aClick –= new EventHandler(...);která odebere delegáta ze seznamu
Clickvyvolání události.konec příkladu
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 naEvlevou stranu+=a–=operátory způsobí vyvolání doplňků pro přidání a odebrání. Všechny ostatní odkazy jsouEvkompilová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
Buttonvýš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
Controlimplementuje interní mechanismus úložiště pro události. MetodaAddEventHandlerpřidruží hodnotu delegáta ke klíči,GetEventHandlermetoda vrátí delegáta, který je aktuálně přidružený ke klíči, aRemoveEventHandlermetoda 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ě.konec příkladu
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í prohlášení o událostech jsou povolena pouze v abstraktních třídách (§15.2.2.2) a rozhraní (§19.4.5).
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'
| 'readonly' // direct struct members only
| 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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
Indexer_declaration může obsahovat sadu atributů (§23) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new (§15.3.5), (§15.3.5), virtual (§15.15.5). 6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7) a extern (§15.6.8) modifikátory. Kromě toho indexer_declaration , která je obsažena přímo struct_declaration může obsahovat readonly modifikátor (§16.4.12).
- 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ů (§23) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new (§15.3.5), (§15.3.5), virtual (§15.15.0). 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 ,outneborefargument, 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
BitArraytří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
BitArraytří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 tohobytedruhého), ale umožňuje stejné operace jakobool[].Následující
CountPrimestřída používáBitArraya 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
BitArrayje přesně stejná jako ubool[].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; } } }konec příkladu
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.
- K nemovitosti se přistupuje 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.4).
- 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ázvemvalue. - 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, kdePje název vlastnosti. V deklaraci přepsání indexeru je zděděný indexer přístupný pomocí syntaxebase[E], kdeEje čá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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
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
publicstaticzahrnuje 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 typuTneboT?může vrátit libovolný typ. - Unární
++nebo--operátor přijme jeden parametr typuTneboT?vrátí stejný typ nebo typ odvozený z něj. - Unární
truenebofalseoperátor použije jeden parametr typuTneboT?vrátí typbool.
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 jsou false dále popsány v §12.26.
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 přírůstku a dekrementace předpony (§12.9.7). 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).
konec příkladu
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
TneboT?, a může vrátit jakýkoli typ. - Binární nebo
<<>>operátor (§12.13) musí mít dva parametry, z nichž první musí mít typTneboT?druhý typ nebointdruhý typint?a může vracet jakýkoli 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₀aT₀jsou různé typy.Buď
S₀neboT₀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
SdoTnebo zTdoS.
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
Tintastring, v uvedeném pořadí jsou považovány za jedinečné typy bez relace. Třetí operátor je však chyba, protožeC<T>je základní třídouD<T>.konec příkladu
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
Cje možné definovat převod zCintdo a zintdoC, ale ne zintdobool. konec příkladu
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
objectzadán jako typ argument proT, druhý operátor deklaruje převod, který již existuje (implicitní, a proto také explicitní převod existuje z libovolného typu na objekt typu).konec příkladu
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
Sna typT, 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
Sna typT, budou ignorovány všechny explicitní převodySTdefinované uživatelem. Mimoto:- Pokud se jedná
SoTtyp rozhraní, budou se ignorovat implicitní převodySTdefinované uživatelem. - V opačném případě se budou i nadále zvažovat
Simplicitní převodyTdefinované uživatelem.
- Pokud se jedná
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 }konec příkladu
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
Digitnabyteje implicitní, protože nikdy nevyvolá výjimky nebo ztratí informace, ale převod zbytenaDigitje explicitní, protožeDigitmůže představovat pouze podmnožinu možných hodnotbyte.konec příkladu
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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
Constructor_declaration může obsahovat sadu atributů (§23), 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) {} }konec příkladu
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 mimo externí nemá žádný konstruktor inicializátor nebo má konstruktor inicializátor formuláře base(...), který 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).
Inicializátory proměnných nemusí být spouštěny externími konstruktory instancí.
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 = 0Hodnota
xje 1, protože inicializátor proměnné se spustí před vyvolání konstruktoru instance základní třídy. Hodnotayje však 0 (výchozí hodnotaint) protože přiřazeníynení 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říkladclass 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ářů (
baseathis). 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; } }konec příkladu
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
Cje 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() {} }konec příkladu
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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
Static_constructor_declaration může obsahovat sadu atributů (§23) 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.
Pro inicializaci nového uzavřeného typu třídy musí být nejprve vytvořena nová sada statických polí (§15.5.2) pro daný uzavřený typ. Všechna statická pole musí být inicializována na výchozí hodnotu (§15.5.5). Toto:
- Pokud neexistuje žádný statický konstruktor nebo ne extern statický konstruktor, pak:
- inicializátory statického pole (§15.5.6.2) se provádějí pro tato statická pole;
- pak se provede ne extern statický konstruktor, pokud existuje.
- V opačném případě se provede externí statický konstruktor. Inicializátory statických proměnných nejsou nutné provádět externími statickými konstruktory.
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.Fvzhledem k tomu, že spuštění statického
Akonstruktoru 'je aktivováno volánímA.Fa spuštěníBstatického konstruktoru 'je aktivováno volánímB.F.konec příkladu
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 = 2Chcete-li spustit metodu
Main, systém nejprve spustí inicializátor proB.Y, před statický konstruktor třídyB.YInicializátor způsobíAstaticspuštění konstruktoruA.X, protože na hodnotu se odkazuje. Statický konstruktorAnásledně vypočítá hodnotuXa tím načte výchozí hodnotuY, která je nula.A.Xinicializuje 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í hodnotyY, jehož výsledkem je 2.konec příkladu
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"); } } }konec příkladu
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 (§24.2) je k dispozici pouze v nebezpečném kódu (§24).
Finalizer_declaration může obsahovat sadu atributů (§23).
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(); } }je
B's finalizer A's finalizervzhledem 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é.
konec příkladu
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.
konec příkladu
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.ObjectmetoduFinalize.konec příkladu
Diskuzi o chování při vyvolání výjimky z finalizátoru naleznete v §22.4.
15.14 Asynchronní funkce
15.14.1 Obecné
Metoda (§15.6), anonymní funkce (§12.21) nebo místní funkce (§13.6.4) s modifikátorem async se nazývá asynchronní funkce. 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, typ úkolu, nebo asynchronní typ iterátoru (§15.15). Pro asynchronní metodu, která vytváří výslednou hodnotu, musí být typ úkolu nebo asynchronní typ iterátoru (§15.15.3) 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> a System.Threading.Tasks.ValueTask<T> 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. Záznamy A úspěšné«TaskType»<T> zaznamenávají výsledek typu T. Typy úkolů jsou očekávané a úkoly mohou být operandy výrazů await (§12.9.9).
Příklad: Typ
MyTask<T>úlohy je přidružen k typuMyTaskMethodBuilder<T>tvůrce úloh a typuAwaiter<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() { ... } }konec příkladu
Typ tvůrce úkolů je typ třídy nebo struktury, který odpovídá určitému typu úkolu (§15.14.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áninternala definován ve stejném sestavení. Pokud je typ úkolu vnořený do jiného typu, musí být typ tvůrce úloh také vnořený do stejného typu. koncová poznámka
Asynchronní funkce má schopnost pozastavit vyhodnocování pomocí výrazů await (§12.9.9) 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.14.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ébuilderv 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ď inStart(), nebo potéStart(), co se vrátí, aby přešel do stavového stroje.
- Tvůrce zavolá
- Po
Start()vráceníasyncvyvolá metodabuilder.Taskpro ú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
eve stavovém počítačibuilder.SetException(e). - Pokud stavový počítač dosáhne výrazu
await expr,expr.GetAwaiter()vyvolá se. - Pokud awaiter implementuje
ICriticalNotifyCompletionaIsCompletedje false, stavový počítač vyvolábuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine).-
AwaitUnsafeOnCompleted()by měla volatawaiter.UnsafeOnCompleted(action)s tímto volánímActionstateMachine.MoveNext(), jakmile se operátor awaiter dokončí.
-
- Jinak stavový počítač vyvolá
builder.AwaitOnCompleted(ref awaiter, ref stateMachine).-
AwaitOnCompleted()by měla volatawaiter.OnCompleted(action)s tímto volánímActionstateMachine.MoveNext(), jakmile se operátor awaiter dokončí.
-
-
SetStateMachine(IAsyncStateMachine)může být volána kompilátorem vygenerovanouIAsyncStateMachineimplementací 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).
- Pokud tvůrce volá
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í naT. 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.14.3 Vyhodnocení asynchronní funkce, která vrací ú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.14.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.
15.15 Synchronní a asynchronní iterátory
15.15.1 Obecné
Člen funkce (§12.6) nebo místní funkce (§13.6.4) implementovaný pomocí bloku iterátoru (§13.3) se nazývá iterátor. Blok iterátoru lze použít jako tělo funkce, pokud návratový typ odpovídající funkce je jedním z rozhraní enumerátoru (§15.15.2) nebo některého z výčtových rozhraní (§15.15.3).
Asynchronní funkce (§15.14) nebo místní funkce (§13.6.4) implementovaná pomocí bloku iterátoru (§13.3) se nazývá asynchronní iterátor. Asynchronní blok iterátoru lze použít jako tělo funkce, pokud návratový typ odpovídající funkce je asynchronní enumerační rozhraní (§15.15.2) nebo asynchronní výčtové rozhraní (§15.15.3).
Blok iterátoru může nastat jako method_body, operator_body nebo accessor_body, zatímco události, konstruktory instancí, statické konstruktory a finalizátory se neimplementují jako synchronní nebo asynchronní iterátory.
Pokud je funkce implementována pomocí bloku iterátoru, jedná se o chybu v době kompilace pro seznam parametrů funkce k určení libovolného inparametru , , outnebo ref parametrů nebo parametru typu ref struct .
Asynchronní iterátor podporuje zrušení asynchronní operace. Toto je popsáno v §23.5.8.
15.15.2 Rozhraní enumerátoru
Rozhraní enumerátorujsou ne generické rozhraní System.Collections.IEnumerator a obecné rozhraní System.Collections.Generic.IEnumerator<T>.
Asynchronní enumerátor rozhraní je obecné rozhraní System.Collections.Generic.IAsyncEnumerator<T>.
Pro účely stručnosti se v tomto pododstavci a jeho souvisejících částech tyto rozhraní označují jako IEnumerator, IEnumerator<T> a IAsyncEnumerator<T> v uvedeném pořadí.
15.15.3 Výčtové rozhraní
Výčet rozhraní jsou negenerickérozhraní System.Collections.IEnumerable a obecná rozhraní System.Collections.Generic.IEnumerable<T>.
Asynchronní výčet rozhraní je obecné rozhraní System.Collections.Generic.IAsyncEnumerable<T>.
Pro účely stručnosti se v tomto pododstavci a jeho souvisejících částech tyto rozhraní označují jako IEnumerable, IEnumerable<T> a IAsyncEnumerable<T> v uvedeném pořadí.
15.15.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í
IEnumeratorneboIEnumerablejeobject. - Výnosový typ iterátoru, který vrátí
IEnumerator<T>,IAsyncEnumerator<T>,IEnumerable<T>neboIAsyncEnumerable<T>, jeT.
15.15.5 Enumerator – objekty
15.15.5.1 Obecné
Pokud je člen funkce nebo místní funkce vracející typ rozhraní enumerátoru nebo asynchronní typ rozhraní enumerátoru implementován pomocí bloku iterátoru, vyvolání 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í objektu MoveNext nebo MoveNextAsync metody enumerátoru. Objekt enumerátoru má následující charakteristiky:
- Implementuje
System.IDisposable,IEnumeratora , neboIEnumerator<T>System.IAsyncDisposableaIAsyncEnumerator<T>, kdeTje typ výnosu iterátoru. - Inicializuje se pomocí kopie hodnot argumentů (pokud existuje) a hodnoty instance předané funkci.
- 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í funkci, 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í pododstavce popisují požadované chování člena pro posunutí enumerátoru, načtení aktuální hodnoty z enumerátoru a uvolnění prostředků používaných enumerátorem. Jsou definovány v následujících členech pro synchronní a asynchronní enumerátory, v uvedeném pořadí:
- K přechodu na enumerátor:
MoveNextaMoveNextAsync. - Pro načtení aktuální hodnoty:
Current. - K zlikvidování prostředků:
DisposeaDisposeAsync.
Objekty enumerátoru nepodporují metodu IEnumerator.Reset . Vyvolání této metody způsobí System.NotSupportedException vyvolání.
Synchronní a asynchronní bloky iterátoru se liší v tom, že asynchronní členové iterátoru vracejí typy úloh a mohou být očekávána.
Posuňte enumerátor vpřed
Metody MoveNext a MoveNextAsync objektu enumerátoru zapouzdřují kód bloku iterátoru. Vyvolání metody MoveNext nebo MoveNextAsync spustí kód v bloku iterátoru a nastaví vlastnost Current objektu enumerátoru v případě potřeby.
MoveNext
bool vrátí hodnotu, jejíž význam je popsán níže.
MoveNextAsync vrátí ValueTask<bool> (§15.14.3). Výsledná hodnota úkolu vráceného z MoveNextAsync má stejný význam jako výsledná hodnota z MoveNext. V následujícím popisu platí akce popsané pro MoveNext u MoveNextAsync s následujícím rozdílem: Pokud je uvedeno, že MoveNext vrací true nebo false, MoveNextAsync nastaví svůj úkol na dokončený stav a nastaví výslednou hodnotu úkolu na odpovídající hodnotu true nebo false.
Přesná akce provedená MoveNext nebo MoveNextAsync závisí na stavu objektu enumerátoru při 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í
MoveNextnení 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í
MoveNextvrá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.
Poznámka:
MoveNextAsyncje pozastavena, pokud vyhodnotíawaitvýraz, který čeká na typ úkolu, který nebyl dokončen. koncová poznámka
-
yield returnPokud 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
Currentvlastnosti 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í tohotoyield returnpříkazu.yield returnPokud je příkaz v jednom nebo vícetryblocích, přidružené bloky finally se v tuto chvíli nespustí. - Stav objektu enumerátoru se změní na pozastavený.
- Metoda
MoveNextse vrátítruedo svého volajícího, což znamená, že iterace úspěšně pokročila na další hodnotu.
- Výraz zadaný v příkazu je vyhodnocen, implicitně převeden na typ výnosu a přiřazen k
-
yield breakPokud je zjištěn výrok (§9.4.4.20):-
yield breakPokud je příkaz v jednom nebo vícetryblocích, spustí se přidruženéfinallybloky. - Stav objektu enumerátoru se změní na následující.
- Metoda
MoveNextse vrátífalsedo 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
MoveNextse vrátífalsedo 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é
finallybloky v těle iterátoru. - Stav objektu enumerátoru se změní na následující.
- Šíření výjimek pokračuje volajícím
MoveNextmetody.
- Šíření výjimky provede příslušné
15.15.5.3 Načtení aktuální hodnoty
Vlastnost objektu Current enumerátoru je ovlivněna yield return příkazy v bloku iterátoru.
Poznámka: Vlastnost
Currentje synchronní vlastnost pro synchronní i asynchronní objekty iterátoru. koncová poznámka
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.15.5.4 Likvidace zdrojů
Nebo DisposeDisposeAsync metoda slouží k vyčištění iterace přenesením objektu enumerátoru do after stavu.
- Pokud je stav objektu enumerátoru před tím, vyvolání
Disposezmění stav na za. - Pokud je spuštěn stav objektu enumerátoru, výsledek vyvolání
Disposenení 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 returnpříkaz bylyield breakpří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í metodyDispose. - Změní stav na následující.
- Pokud je stav objektu enumerátoru po, vyvolání
Disposenemá žádný vliv.
15.15.6 Výčtové objekty
15.15.6.1 Obecné
Pokud je člen funkce nebo místní funkce vracející typ enumerovatelného rozhraní nebo asynchronní typ rozhraní implementován pomocí bloku iterátoru, vyvolání funkce okamžitě nespustí kód v bloku iterátoru. Místo toho se vytvoří a vrátí výčtový objekt.
Synchronní enumerable objekt implementuje IEnumerable a IEnumerable<T>, kde T je typ výnosu iterátoru. Jeho GetEnumerator metoda vrátí objekt enumerátoru (§15.15.5). Asynchronní enumerovatelný objekt implementuje IAsyncEnumerable<T> , kde T je typ výnosu iterátoru. Jeho GetAsyncEnumerator metoda vrátí asynchronní objekt enumerátoru (§15.15.5).
Výčtový objekt je inicializován kopií hodnot argumentů (pokud existuje) a hodnota instance předaná funkci.
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 vnořena přímo nebo nepřímo do třídy obsahující funkci, 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
IEnumeratoraIEnumerator<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í .GetEnumeratorNá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. koncová poznámka
15.15.6.2 Metoda GetEnumerator nebo GetAsyncEnumerator
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.15.5.
Asynchronní výčtový objekt poskytuje implementaci GetAsyncEnumerator metody IAsyncEnumerable<T> rozhraní. Tato metoda vrátí dostupný asynchronní enumerátor objektu. Objekt enumerátoru se inicializuje s hodnotami argumentu a hodnotou instance uloženou při inicializaci výčtového objektu, včetně volitelného tokenu zrušení, ale jinak objekt enumerátoru funguje, jak je popsáno v §15.15.5. Asynchronní metoda iterátoru může označit jeden parametr jako token zrušení pomocí System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.8). Implementace poskytuje mechanismus pro kombinování tokenů zrušení tak, aby asynchronní iterátor byl zrušen, pokud buď dojde ke GetAsyncEnumerator zrušení tokenu zrušení (argumentu nebo argumentu přiřazeného atributem System.Runtime.CompilerServices.EnumeratorCancellationAttribute).
ECMA C# draft specification