15 tříd
15.1 Obecné
Třída je datová struktura, která může obsahovat datové členy (konstanty a pole), členy funkce (metody, vlastnosti, události, indexery, operátory, konstruktory instancí, finalizátory a statické konstruktory) a vnořené typy. Typy tříd podporují dědičnost, mechanismus, kdy odvozená třída může rozšířit a specializovat základní třídu.
15.2 Deklarace třídy
15.2.1 Obecné
Class_declaration je type_declaration (§14.7), která deklaruje novou třídu.
class_declaration
: attributes? class_modifier* 'partial'? 'class' identifier
type_parameter_list? class_base? type_parameter_constraints_clause*
class_body ';'?
;
Class_declaration se skládá z volitelné sady atributů (§22), po níž následuje volitelná sada class_modifiers (§15.2.2), za kterým následuje volitelný partial
modifikátor (§15.2.7), za kterým následuje klíčové slovo class
a identifikátor, který pojmenuje třídu, po ní následuje nepovinný type_parameter_list (§15.2.3), za nímž následuje volitelná specifikace class_base (§15.2.4)), po níž následuje volitelná sada type_parameter_constraints_clause s (§15.2.5), za kterou následuje class_body (§15.2.6), případněnásleduje středník.
Prohlášení o třídě nesmí poskytovat type_parameter_constraints_clauses, pokud nenabídá ani type_parameter_list.
Deklarace třídy, která poskytuje type_parameter_list je obecná deklarace třídy. Kromě toho každá třída vnořená uvnitř obecné deklarace třídy nebo obecná deklarace struktury je sama o sobě obecnou deklarací třídy, protože argumenty typu pro obsahující typ musí být zadány k vytvoření konstruovaného typu (§8.4).
15.2.2 Modifikátory tříd
15.2.2.1 Obecné
Class_declaration může volitelně obsahovat posloupnost modifikátorů tříd:
class_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'abstract'
| 'sealed'
| 'static'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
Jedná se o chybu v době kompilace, aby se stejný modifikátor zobrazoval vícekrát v deklaraci třídy.
Modifikátor new
je povolen u vnořených tříd. Určuje, že třída skryje zděděný člen se stejným názvem, jak je popsáno v §15.3.5. Jedná se o chybu v době kompilace, new
která se má modifikátor objevit v deklaraci třídy, která není vnořenou deklarací třídy.
public
Modifikátory , , protected
internal
a 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
, internal
a 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
, sealed
a static
modifikátory jsou popsány v následujících podklidách.
15.2.2.2 Abstraktní třídy
abstract
Modifikátor se používá k označení, že třída není úplná a že je určena pouze jako základní třída. Abstraktní třída se liší od abstraktní třídy následujícími způsoby:
- Abstraktní třídu nelze vytvořit přímo a jedná se o chybu v době kompilace pro použití operátoru
new
v abstraktní třídě. I když je možné mít proměnné a hodnoty, jejichž typy kompilátor-čas jsou abstraktní, takové proměnné a hodnoty budou nutně buď buď býtnull
nebo obsahují odkazy na instance ne abstraktních tříd odvozených z abstraktních typů. - Abstraktní třída je povolena (ale nevyžaduje) obsahovat abstraktní členy.
- Abstraktní třídu nelze zapečetit.
Je-li abstraktní třída odvozena z abstraktní třídy, třída, která není abstraktní, zahrnuje skutečné implementace všech zděděných abstraktních členů, čímž tyto abstraktní členy přepíše.
Příklad: V následujícím kódu
abstract class A { public abstract void F(); } abstract class B : A { public void G() {} } class C : B { public override void F() { // Actual implementation of F } }
abstraktní třída
A
zavádí abstraktní metoduF
. TřídaB
zavádí další metoduG
, ale protože neposkytuje implementaciF
,B
musí být deklarována také abstraktní. TřídaC
přepisujeF
a poskytuje skutečnou implementaci. Vzhledem k tomu, že neexistují žádné abstraktní členyC
,C
je povoleno (ale nevyžaduje) být ne abstrakt.end example
Pokud jedna nebo více částí částečné deklarace typu (§15.2.7) třídy zahrnuje abstract
modifikátor, třída je abstraktní. V opačném případě třída není abstraktní.
15.2.2.3 Zapečetěné třídy
Modifikátor sealed
se používá k zabránění odvození z třídy. K chybě v době kompilace dochází, pokud je zapečetěná třída určena jako základní třída jiné třídy.
Zapečetěná třída nemůže být také abstraktní třídou.
Poznámka:
sealed
Modifikátor se primárně používá k prevenci nechtěných odvození, ale také umožňuje určité optimalizace za běhu. Konkrétně, protože zapečetěná třída je známo, že nikdy nemají žádné odvozené třídy, je možné transformovat volání členů virtuální funkce na zapečetěné instance třídy na ne-virtuální vyvolání. koncová poznámka
Pokud jedna nebo více částí částečné deklarace typu (§15.2.7) třídy zahrnuje sealed
modifikátor, je třída zapečetěna. Jinak je třída nezapečetěná.
15.2.2.4 Statické třídy
15.2.2.4.1 Obecné
static
Modifikátor se používá k označení třídy deklarované jako statická třída. Statická třída nesmí být vytvořena, nesmí být použita jako typ a musí obsahovat pouze statické členy. Pouze statická třída může obsahovat deklarace rozšiřujících metod (§15.6.10).
Deklarace statické třídy podléhá následujícím omezením:
- Statická třída nesmí obsahovat
sealed
aniabstract
modifikátor. (Vzhledem k tomu, že statickou třídu nelze vytvořit instanci ani odvodit, chová se, jako by byla zapečetěná i abstraktní.) - Statická třída nesmí obsahovat specifikaci class_base (§15.2.4) a nesmí explicitně určit základní třídu ani seznam implementovaných rozhraní. Statická třída implicitně dědí z typu
object
. - Statická třída musí obsahovat pouze statické členy (§15.3.8).
Poznámka: Všechny konstanty a vnořené typy jsou klasifikovány jako statické členy. koncová poznámka
- Statická třída nesmí mít členy s
protected
,private protected
aniprotected internal
deklarovanou přístupnost.
Jedná se o chybu v době kompilace, která porušuje některá z těchto omezení.
Statická třída nemá žádné konstruktory instance. Nelze deklarovat konstruktor instance ve statické třídě a pro statickou třídu není k dispozici žádný výchozí konstruktor instance (§15.11.5).
Členy statické třídy nejsou automaticky statické a deklarace členů musí explicitně obsahovat static
modifikátor (s výjimkou konstant a vnořených typů). Pokud je třída vnořena do statické vnější třídy, vnořená třída není statickou třídou, pokud explicitně neobsahuje static
modifikátor.
Pokud jedna nebo více částí částečné deklarace typu (§15.2.7) třídy obsahuje static
modifikátor, je třída statická. Jinak není třída statická.
15.2.2.4.2 Odkazování na statické typy tříd
Namespace_or_type_name (§7.8) je povoleno odkazovat na statickou třídu, pokud
- Namespace_or_type_name je
T
ve namespace_or_type_name formulářeT.I
, nebo - Název namespace_or_type je
T
v typeof_expression (§12.8.18) formulářetypeof(T)
.
Primary_expression (§12.8) je povoleno odkazovat na statickou třídu, pokud
- Primary_expression je
E
v 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,
sizeof
výraz nebo výchozí výraz hodnoty. koncová poznámka
15.2.3 Parametry typu
Parametr typu je jednoduchý identifikátor, který označuje zástupný symbol pro argument typu zadaný k vytvoření vytvořeného typu. Argument typu (§8.4.2) je podle constrastu typ, který se při vytvoření vytvořeného typu nahradí parametrem typu.
type_parameter_list
: '<' type_parameters '>'
;
type_parameters
: attributes? type_parameter
| type_parameters ',' attributes? type_parameter
;
type_parameter je definován v §8.5.
Každý parametr typu v deklaraci třídy definuje název v prostoru deklarace (§7.3) dané třídy. Proto nemůže mít stejný název jako jiný parametr typu této třídy nebo člen deklarovaný v této třídě. Parametr typu nemůže mít stejný název jako samotný typ.
Dvě částečné deklarace obecného typu (ve stejném programu) přispívají ke stejnému nevázaným obecným typům, pokud mají stejný plně kvalifikovaný název (který zahrnuje generic_dimension_specifier (§12.8.18) pro počet parametrů typu) (§7.8.3). Dvě takové částečné deklarace typu musí určovat stejný název pro každý parametr typu v pořadí.
Základní specifikace třídy 15.2.4
15.2.4.1 Obecné
Deklarace třídy může obsahovat specifikaci class_base , která definuje přímou základní třídu třídy a rozhraní (§18) přímo implementovanou třídou.
class_base
: ':' class_type
| ':' interface_type_list
| ':' class_type ',' interface_type_list
;
interface_type_list
: interface_type (',' interface_type)*
;
15.2.4.2 Základní třídy
Pokud je class_type součástí class_base, určuje přímou základní třídu deklarované třídy. Pokud deklarace třídy, která není částečná, nemá žádnou class_base nebo pokud class_base uvádí pouze typy rozhraní, předpokládá se, že přímá základní třída je object
. Pokud deklarace částečné třídy obsahuje specifikaci základní třídy, bude tato specifikace základní třídy odkazovat na stejný typ jako všechny ostatní části tohoto částečného typu, které obsahují specifikaci základní třídy. Pokud žádná část částečné třídy neobsahuje specifikaci základní třídy, základní třída je object
. Třída dědí členy ze své přímé základní třídy, jak je popsáno v §15.3.4.
Příklad: V následujícím kódu
class A {} class B : A {}
Třída
A
je řečeno, že je přímá základní třídaB
, aB
je ř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
.end example
Pro konstruovaný typ třídy, včetně vnořeného typu deklarovaného v rámci obecné deklarace typu (§15.3.9.7), je-li základní třída určena v deklaraci obecné třídy, základní třída konstruovaného typu je získána nahrazením každé type_parameter v deklaraci základní třídy, odpovídající type_argument konstruovaného typu.
Příklad: Při deklaraci obecné třídy
class B<U,V> {...} class G<T> : B<string,T[]> {...}
základní třída vytvořeného typu
G<int>
by bylaB<string,int[]>
.end example
Základní třídou zadanou v deklaraci třídy může být konstruovaný typ třídy (§8.4). Základní třída nemůže být parametrem typu sám (§8.5), i když může zahrnovat parametry typu, které jsou v oboru.
Příklad:
class Base<T> {} // Valid, non-constructed class with constructed base class class Extend1 : Base<int> {} // Error, type parameter used as base class class Extend2<V> : V {} // Valid, type parameter used as type argument for base class class Extend3<V> : Base<V> {}
end example
Přímá základní třída typu třídy musí být alespoň tak přístupná jako samotný typ třídy (§7.5.5). Jedná se například o chybu v době kompilace, která má veřejnou třídu odvodit z privátní nebo interní třídy.
Přímá základní třída typu třídy nesmí být žádným z následujících typů: System.Array
, System.Delegate
, System.Enum
System.ValueType
ani dynamic
typu. Obecné prohlášení třídy navíc nesmí být používáno System.Attribute
jako přímá nebo nepřímá základní třída (§22.2.1).
Při určování významu přímé specifikace A
základní třídy třídy B
je přímá základní třída B
dočasně považována object
za , 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řídaZ
považována zaobject
, a proto (podle pravidel §7.8)Z
není považována za členaY
.end example
Základní třídy třídy jsou přímou základní třídou a jejími základními třídami. Jinými slovy, sada základních tříd je tranzitivní uzavření přímé relace základní třídy.
Příklad: V následujícím příkladu:
class A {...} class B<T> : A {...} class C<T> : B<IComparable<T>> {...} class D<T> : C<T[]> {...}
základní třídy
D<int>
jsouC<int[]>
,B<IComparable<int[]>>
,A
aobject
.end example
Kromě třídy object
má každá třída přesně jednu přímou základní třídu. Třída object
nemá žádnou přímou základní třídu a je nejvyšší základní třídou všech ostatních tříd.
Jedná se o chybu v době kompilace, kdy třída závisí na sobě. Pro účely tohoto pravidla třída přímo závisí na své přímé základní třídě (pokud existuje) a přímo závisí na nejbližší nadřazené třídě, ve které je vnořena (pokud existuje). Vzhledem k této definici je kompletní sada tříd, na kterých třída závisí, tranzitivní uzavření přímo závisí na relaci.
Příklad: Příklad
class A : A {}
je chybná, protože třída závisí na sobě. Stejně tak příklad
class A : B {} class B : C {} class C : A {}
je chybná, protože třídy jsou cyklicky závislé na sobě. Nakonec příklad
class A : B.C {} class B : A { public class C {} }
výsledkem chyby v době kompilace, protože A závisí na
B.C
(její přímé základní třídě), která závisí naB
(její bezprostředně uzavřená třída), která cyklicky závisí naA
.end example
Třída nezávisí na třídách, které jsou vnořené do ní.
Příklad: V následujícím kódu
class A { class B : A {} }
B
závisí naA
(protožeA
je její přímá základní třída i její okamžitě uzavřená třída), aleA
nezávisí na (protožeB
se nejedná oB
základní třídu ani ohraničující tříduA
). Proto je příklad platný.end example
Není možné odvodit z zapečetěné třídy.
Příklad: V následujícím kódu
sealed class A {} class B : A {} // Error, cannot derive from a sealed class
Třída
B
je chybná, protože se pokouší odvodit z zapečetěné třídyA
.end example
15.2.4.3 Implementace rozhraní
Specifikace class_base může obsahovat seznam typů rozhraní, v takovém případě se říká, že třída implementuje dané typy rozhraní. Pro konstruovaný typ třídy, včetně vnořeného typu deklarovaného v rámci obecné deklarace typu (§15.3.9.7), získá každý typ implementovaného rozhraní nahrazením každého type_parameter v daném rozhraní odpovídající type_argument konstruovaného typu.
Sada rozhraní pro typ deklarovaný ve více částech (§15.2.7) je sjednocení rozhraní uvedených v každé části. Konkrétní rozhraní lze na každé části pojmenovat pouze jednou, ale více částí může pojmenovat stejná základní rozhraní. Existuje pouze jedna implementace každého člena každého daného rozhraní.
Příklad: V následujícím příkladu:
partial class C : IA, IB {...} partial class C : IC {...} partial class C : IA, IB {...}
sada základních rozhraní pro třídu
C
jeIA
,IB
aIC
.end example
Každá část obvykle poskytuje implementaci rozhraní deklarovaných v této části; to však není požadavek. Část může poskytnout implementaci pro rozhraní deklarované v jiné části.
Příklad:
partial class X { int IComparable.CompareTo(object o) {...} } partial class X : IComparable { ... }
end example
Základní rozhraní zadaná v deklaraci třídy mohou být vytvořena typy rozhraní (§8.4, §18.2). Základní rozhraní nemůže být parametr typu sám, i když může zahrnovat parametry typu, které jsou v oboru.
Příklad: Následující kód ukazuje, jak může třída implementovat a rozšířit konstruované typy:
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}
end example
Implementace rozhraní jsou popsány dále v §18.6.
15.2.5 Omezení parametru typu
Deklarace obecného typu a metody mohou volitelně zadat omezení parametru typu zahrnutím type_parameter_constraints_clauses.
type_parameter_constraints_clauses
: type_parameter_constraints_clause
| type_parameter_constraints_clauses type_parameter_constraints_clause
;
type_parameter_constraints_clause
: 'where' type_parameter ':' type_parameter_constraints
;
type_parameter_constraints
: primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
| secondary_constraints (',' constructor_constraint)?
| constructor_constraint
;
primary_constraint
: class_type nullable_type_annotation?
| 'class' nullable_type_annotation?
| 'struct'
| 'notnull'
| 'unmanaged'
;
secondary_constraint
: interface_type nullable_type_annotation?
| type_parameter nullable_type_annotation?
;
secondary_constraints
: secondary_constraint (',' secondary_constraint)*
;
constructor_constraint
: 'new' '(' ')'
;
Každý type_parameter_constraints_clause se skládá z tokenu where
ná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ístruct
typu . Typ třídy a omezení typu odkazu mohou zahrnovat nullable_type_annotation.
Sekundárním omezením může být interface_type nebo type_parameter, případně za ním následuje nullable_type_annotation. Přítomnost nullable_type_annotatione* značí, že argument typu může být odkazový typ s možnou hodnotou null, který odpovídá nenulovatelnému typu odkazu, který splňuje omezení.
Omezení typu odkazu určuje, že argument typu použitý pro parametr typu musí být odkazovým typem. Toto omezení splňují všechny typy tříd, typy rozhraní, typy delegátů, typy polí a parametry typu, které jsou známé jako odkazový typ (jak je definováno níže).
Typ třídy, omezení typu odkazu a sekundární omezení mohou obsahovat poznámku typu s možnou hodnotou null. Přítomnost nebo absence této poznámky u parametru typu indikuje očekávání nulové hodnoty argumentu typu:
- Pokud omezení neobsahuje anotaci typu s možnou hodnotou null, očekává se, že argument typu nebude mít hodnotu null. Kompilátor může vydat upozornění, pokud je argument typu odkazem s možnou hodnotou null.
- Pokud omezení obsahuje anotaci typu s možnou hodnotou null, je omezení splněno jak typem odkazu, který není nullable, tak typem odkazu s možnou hodnotou null.
Hodnota nullability argumentu typu se nemusí shodovat s hodnotou null parametru typu. Kompilátor může vydat upozornění, pokud hodnota null parametru typu neodpovídá hodnotě nullability argumentu typu.
Poznámka: Chcete-li určit, že argument typu je typ odkazu s možnou hodnotou null, nepřidávejte poznámku typu s možnou hodnotou null jako omezení (použít
T : class
neboT : 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 nelze použít u argumentu typu bez omezení.
Pro parametr T
typu, pokud je argument typu nullable typ C?
, instance T?
jsou interpretovány jako C?
, nikoli C??
.
Příklad: Následující příklady ukazují, jak nullability argumentu typu ovlivňuje hodnotu nullability deklarace typu parametru:
public class C { } public static class Extensions { public static void M<T>(this T? arg) where T : notnull { } } public class Test { public void M() { C? mightBeNull = new C(); C notNull = new C(); int number = 5; int? missing = null; mightBeNull.M(); // arg is C? notNull.M(); // arg is C? number.M(); // arg is int? missing.M(); // arg is int? } }
Pokud je argument typu nenulový, znamená to,
?
že parametr je odpovídajícím typem s možnou hodnotou null. Pokud je argument typu již typu odkazu s možnou hodnotou null, parametr je stejný typ s možnou hodnotou null.end example
Omezení not null určuje, že argument typu použitý pro parametr typu by měl být nenulový typ hodnoty nebo nenulový odkazový typ. Argument typu, který není typem hodnoty null nebo nenulovým odkazovým typem, je povolen, ale kompilátor může vytvořit diagnostické upozornění.
Omezení typu hodnoty určuje, že argument typu použitý pro parametr typu musí být nenulový typ hodnoty. Všechny typy struktury, typy výčtu a parametry typu, které nemají hodnotu null, splňují toto omezení. Všimněte si, že i když je klasifikovaný jako typ hodnoty, typ hodnoty null (§8.3.12) nevyhovuje omezení typu hodnoty. Parametr typu s omezením typu hodnoty nesmí mít také constructor_constraint, i když jej lze použít jako argument typu pro jiný parametr typu s constructor_constraint.
Poznámka: Typ
System.Nullable<T>
určuje omezení typu hodnoty bez hodnoty null proT
. Proto rekurzivně konstruované typy formulářůT??
aNullable<Nullable<T>>
jsou zakázány. koncová poznámka
Protože unmanaged
není klíčové slovo, v primary_constraint nespravované omezení je vždy syntakticky nejednoznačné s class_type. Z důvodu kompatibility je-li vyhledávání názvu (§12.8.4) názvu unmanaged
úspěšné, je považováno class_type
za . Jinak se považuje za nespravované omezení.
Omezení nespravovaného typu určuje, že argument typu použitý pro parametr typu je nespravovatelný nespravovaný typ (§8.8).
Typy ukazatelů nikdy nesmí být argumenty typu a nesplňují žádná omezení typu, ani nespravované, i když jsou nespravované typy.
Pokud je omezení typ třídy, typ rozhraní nebo parametr typu, tento typ určuje minimální "základní typ", který musí podporovat každý argument typu použitý pro tento parametr typu. Při každém použití vytvořeného typu nebo obecné metody je argument typu kontrolován proti omezením parametru typu v době kompilace. Zadaný argument typu splňuje podmínky popsané v §8.4.5.
Omezení class_type musí splňovat následující pravidla:
- Typ musí být typ třídy.
- Typ nesmí být
sealed
. - Typ nesmí být jedním z následujících typů:
System.Array
neboSystem.ValueType
. - Typ nesmí být
object
. - Nejméně jedno omezení pro daný parametr typu může být typ třídy.
Typ určený jako omezení interface_type musí splňovat následující pravidla:
- Typ musí být typ rozhraní.
- V dané
where
klauzuli nesmí být typ určen více než jednou.
V obou případech může omezení zahrnovat kterýkoli z parametrů typu přidruženého typu nebo deklarace metody jako součást konstruovaného typu a může zahrnovat deklarovaný typ.
Každá třída nebo typ rozhraní určený jako omezení parametru typu musí být alespoň tak přístupný (§7.5.5) jako obecný typ nebo metoda deklarovaná.
Typ určený jako omezení type_parameter musí splňovat následující pravidla:
- Typ musí být parametr typu.
- V dané
where
klauzuli nesmí být typ určen více než jednou.
Kromě toho nesmí existovat žádné cykly v grafu závislostí parametrů typu, kde závislost představuje tranzitivní vztah definovaný:
- Pokud se parametr
T
typu použije jako omezení parametru typuS
,S
závisí naT
tom. - Pokud parametr
S
typu závisí na parametruT
typu aT
závisí na parametruU
typu,S
závisí naU
tom.
Vzhledem k tomuto vztahu se jedná o chybu v době kompilace, kdy parametr typu závisí na sobě (přímo nebo nepřímo).
Všechna omezení musí být konzistentní mezi závislými parametry typu. Pokud parametr S
typu závisí na parametru T
typu, pak:
-
T
nesmí mít omezení typu hodnoty. V opačném případě je účinně zapečetěno,T
takžeS
by bylo nuceno být stejný typ jakoT
, eliminuje potřebu dvou parametrů typu. - Pokud
S
má omezení typu hodnoty,T
nesmí mít class_type omezení. - Pokud
S
má aA
máT
, bude existovat převod identity nebo implicitní převod odkazu zB
nebo implicitní převod odkazu zA
B
na . - Pokud
S
také závisí na parametruU
typu aU
má aA
máT
, musí existovat převod identity nebo implicitní převod odkazu zB
nebo implicitní převod odkazu zA
naB
.
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.Enum
a všechny typy rozhraní.
where
Pokud klauzule parametru typu obsahuje omezení konstruktoru (který má formulářnew()
), je možné pomocí new
operátoru vytvořit instance typu (§12.8.17.2). Libovolný argument typu použitý pro parametr typu s omezením konstruktoru musí být typ hodnoty, ne abstraktní třída s veřejným konstruktorem bez parametrů nebo parametr typu s omezením typu nebo konstruktorem.
Jedná se o chybu v době kompilace pro type_parameter_constraints s primary_constraintstruct
nebo unmanaged
také constructor_constraint.
Příklad: Tady jsou příklady omezení:
interface IPrintable { void Print(); } interface IComparable<T> { int CompareTo(T value); } interface IKeyProvider<T> { T GetKey(); } class Printer<T> where T : IPrintable {...} class SortedList<T> where T : IComparable<T> {...} class Dictionary<K,V> where K : IComparable<K> where V : IPrintable, IKeyProvider<K>, new() { ... }
Následující příklad je chybný, protože způsobuje cykličnost v grafu závislostí parametrů typu:
class Circular<S,T> where S: T where T: S // Error, circularity in dependency graph { ... }
Následující příklady ilustrují další neplatné situace:
class Sealed<S,T> where S : T where T : struct // Error, `T` is sealed { ... } class A {...} class B {...} class Incompat<S,T> where S : A, T where T : B // Error, incompatible class-type constraints { ... } class StructWithClass<S,T,U> where S : struct, T where T : U where U : A // Error, A incompatible with struct { ... }
end example
Dynamické vymazání typu C
je Cₓ
typu vytvořené takto:
- Pokud
C
je vnořený typOuter.Inner
,Cₓ
jedná se o vnořený typOuterₓ.Innerₓ
. - Pokud
C
Cₓ
je konstruovaný typG<A¹, ..., Aⁿ>
s argumentyA¹, ..., Aⁿ
typu, pakCₓ
je konstruovaný typG<A¹ₓ, ..., Aⁿₓ>
. - Pokud
C
je typE[]
pole, jednáCₓ
se o typEₓ[]
pole . - Pokud
C
je 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,R
obsahuje jeho efektivní základní třídu. - Pro každé omezení
T
, které je typu struktury,R
obsahujeSystem.ValueType
. - Pro každé omezení, které je typu výčtu
T
,R
obsahujeSystem.Enum
. - Pro každé omezení
T
, které je typu delegáta,R
obsahuje jeho dynamické vymazání. - Pro každé omezení
T
, které je typu pole,R
obsahujeSystem.Array
. - Pro každé omezení
T
, které je typem třídy,R
obsahuje jeho dynamické vymazání.
Pak...
- Pokud
T
má omezení typu hodnoty, jeho efektivní základní třída jeSystem.ValueType
. - V opačném případě, pokud
R
je prázdná, efektivní základní třída jeobject
. - V opačném případě je účinná základní třída
T
nejzahrnující typ (§10.5.3) množinyR
. Pokud sada neobsahuje žádný typ, je platná základní třídaT
.object
Pravidla konzistence zajišťují, že existuje nejobsáhodnější typ.
Pokud je parametr typu parametrem metody, jehož omezení jsou zděděna ze základní metody, efektivní základní třída se vypočítá po nahrazení typu.
Tato pravidla zajišťují, aby efektivní základní třída byla vždy class_type.
Efektivní sada rozhraní parametru T
typu je definována takto:
- Pokud
T
nemá žádné secondary_constraints, jeho efektivní sada rozhraní je prázdná. - Pokud
T
má interface_type omezení, ale žádná omezení type_parameter, je jeho platná sada rozhraní sadou dynamických vymazání interface_type omezení. - Pokud
T
nemá žádná interface_type omezení, ale má omezení type_parameter , je její platná sada rozhraní sjednocením efektivních sad rozhraní jejích type_parameter omezení. - Pokud
T
má omezení interface_type i omezení type_parameter, její účinná sada rozhraní je sjednocení sady dynamických vymazání omezení interface_type a efektivní sady rozhraní omezení type_parameter.
Parametr typu je známý jako referenční typ , pokud má omezení typu odkazu nebo jeho efektivní základní třída není object
nebo System.ValueType
. Parametr typu je známý jako nenulový odkazový typ , pokud je známo, že jde o typ odkazu a má omezení typu odkaz s nenulovou hodnotou.
Hodnoty typu omezeného typu lze použít pro přístup ke členům instance odvozeným z omezení.
Příklad: V následujícím příkladu:
interface IPrintable { void Print(); } class Printer<T> where T : IPrintable { void PrintOne(T x) => x.Print(); }
metody
IPrintable
lze vyvolat přímo,x
protožeT
je omezena na vždy implementovatIPrintable
.end example
Pokud částečná deklarace obecného typu obsahuje omezení, musí omezení souhlasit se všemi ostatními částmi, které obsahují omezení. Konkrétně každá část, která obsahuje omezení, musí mít omezení pro stejnou sadu parametrů typu a pro každý parametr typu musí být sady primárních, sekundárních a konstruktorových omezení ekvivalentní. Dvě sady omezení jsou ekvivalentní, pokud obsahují stejné členy. Pokud žádná část částečného obecného typu specifikuje omezení parametru typu, parametry typu se považují za nekonstruované.
Příklad:
partial class Map<K,V> where K : IComparable<K> where V : IKeyProvider<K>, new() { ... } partial class Map<K,V> where V : IKeyProvider<K>, new() where K : IComparable<K> { ... } partial class Map<K,V> { ... }
je správná, protože ty části, které obsahují omezení (první dva), efektivně určují stejnou sadu primárních, sekundárních a konstruktorových omezení pro stejnou sadu parametrů typu.
end example
Tělo třídy 15.2.6
Class_body třídy definuje členy této třídy.
class_body
: '{' class_member_declaration* '}'
;
15.2.7 Částečné deklarace
Modifikátor partial
se používá při definování třídy, struktury nebo typu rozhraní ve více částech.
partial
Modifikátor je kontextové klíčové slovo (§6.4.4) a má zvláštní význam pouze bezprostředně před jedním z klíčových slov class
, struct
nebo interface
.
Každá část částečné deklarace typu musí obsahovat partial
modifikátor a musí být deklarována ve stejném oboru názvů nebo obsahujícím typ jako ostatní části.
partial
Modifikátor označuje, že další části deklarace typu mohou existovat jinde, ale existence těchto dodatečných částí není požadavkem; je platná pouze pro deklaraci typu, která má obsahovat partial
modifikátor. Platí pouze pro jednu deklaraci částečného typu, která zahrnuje základní třídu nebo implementovaná rozhraní. Všechny deklarace základní třídy nebo implementovaných rozhraní se však musí shodovat, včetně nullability všech zadaných argumentů typu.
Všechny části částečného typu se kompilují společně tak, aby se části mohly sloučit v době kompilace. Částečné typy výslovně neumožňují rozšíření již kompilovaných typů.
Vnořené typy lze deklarovat ve více částech pomocí modifikátoru partial
. Typ obsahující se obvykle deklaruje také pomocí partial
a každá část vnořeného typu je deklarována v jiné části obsahujícího typu.
Příklad: Následující částečná třída je implementována ve dvou částech, které se nacházejí v různých kompilačních jednotkách. První část je stroj generovaný nástrojem pro mapování databáze, zatímco druhá část je ručně vytvořena:
public partial class Customer { private int id; private string name; private string address; private List<Order> orders; public Customer() { ... } } // File: Customer2.cs public partial class Customer { public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted); public bool HasOutstandingOrders() => orders.Count > 0; }
Když jsou obě výše uvedené části zkompilovány dohromady, výsledný kód se chová tak, jako kdyby byla třída napsána jako jedna jednotka, následujícím způsobem:
public class Customer { private int id; private string name; private string address; private List<Order> orders; public Customer() { ... } public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted); public bool HasOutstandingOrders() => orders.Count > 0; }
end example
Zpracování atributů určených pro parametry typu nebo typu různých částí částečné deklarace je popsáno v §22.3.
15.3 Členové třídy
15.3.1 Obecné
Členy třídy se skládají z členů zavedených jeho class_member_declarationa členy zděděné z přímé základní třídy.
class_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| finalizer_declaration
| static_constructor_declaration
| type_declaration
;
Členové třídy jsou rozděleni do následujících kategorií:
- Konstanty, které představují konstantní hodnoty spojené s třídou (§15.4).
- Pole, která jsou proměnnými třídy (§15.5).
- Metody, které implementují výpočty a akce, které mohou být provedeny třídou (§15.6).
- Vlastnosti, které definují pojmenované charakteristiky a akce spojené se čtením a zápisem těchto charakteristik (§15.7).
- Události, které definují oznámení, která mohou být generována třídou (§15.8).
- Indexery, které umožňují indexování instancí třídy stejným způsobem (syntakticky) jako pole (§15.9).
- Operátory, které definují operátory výrazů, které lze použít u instancí třídy (§15.10).
- Konstruktory instancí, které implementují akce potřebné k inicializaci instancí třídy (§15.11)
- Finalizační metody, které implementují akce, které mají být provedeny před instancemi třídy, jsou trvale zahozeny (§15.13).
- Statické konstruktory, které implementují akce potřebné k inicializaci samotné třídy (§15.12).
- Typy, které představují typy, které jsou místní pro třídu (§14.7).
Class_declaration vytvoří nový prostor deklarace (§7.3) a type_parametera class_member_declarations okamžitě obsažené class_declaration nové členy do tohoto prostoru prohlášení. Pro class_member_declarations platí následující pravidla:
Konstruktory instancí, finalizátory a statické konstruktory mají stejný název jako bezprostředně uzavřená třída. Všichni ostatní členové mají názvy, které se liší od názvu bezprostředně ohraničující třídy.
Název parametru typu v type_parameter_list deklarace třídy se liší od názvů všech ostatních parametrů typu ve stejném type_parameter_list a musí se lišit od názvu třídy a názvů všech členů třídy.
Název typu se liší od názvů všech netypových členů deklarovaných ve stejné třídě. Pokud dvě nebo více deklarací typu sdílejí stejný plně kvalifikovaný název, musí mít
partial
deklarace modifikátor (§15.2.7) a tyto deklarace kombinují tak, aby definovaly jeden typ.
Poznámka: Vzhledem k tomu, že plně kvalifikovaný název deklarace typu kóduje počet parametrů typu, mohou dva odlišné typy sdílet stejný název, pokud mají jiný počet parametrů typu. koncová poznámka
Název konstanty, pole, vlastnosti nebo události se liší od názvů všech ostatních členů deklarovaných ve stejné třídě.
Název metody se liší od názvů všech ostatních metod, které nejsou deklarované ve stejné třídě. Kromě toho se podpis (§7.6) metody liší od podpisů všech ostatních metod deklarovaných ve stejné třídě a dvě metody deklarované ve stejné třídě nesmí obsahovat podpisy, které se liší pouze
in
pomocí ,out
aref
.Podpis konstruktoru instance se liší od podpisů všech ostatních konstruktorů instancí deklarovaných ve stejné třídě a dva konstruktory deklarované ve stejné třídě nemají podpisy, které se liší pouze
ref
podle 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 není typem, který má partial
modifikátor.
Příklad:
partial class A { int x; // Error, cannot declare x more than once partial class Inner // Ok, Inner is a partial type { int y; } } partial class A { int x; // Error, cannot declare x more than once partial class Inner // Ok, Inner is a partial type { int z; } }
end example
Pořadí inicializace polí může být v kódu jazyka C# významné a některé záruky jsou poskytovány, jak je definováno v §15.5.6.1. V opačném případě je řazení členů v rámci typu zřídka významné, ale může být významné při vzájemném propojení s jinými jazyky a prostředími. V těchto případech není pořadí členů v rámci typu deklarovaného ve více částech definováno.
15.3.2 Typ instance
Každá deklarace třídy má přidružený typ instance. Pro obecnou deklaraci třídy je typ instance vytvořen vytvořením vytvořeného typu (§8.4) z deklarace typu, přičemž každý zadaný argument typu je odpovídající parametr typu. Vzhledem k tomu, že typ instance používá parametry typu, lze jej použít pouze tam, kde jsou parametry typu v oboru; to znamená, že uvnitř deklarace třídy. Typ instance je typ this
kódu napsaného uvnitř deklarace třídy. Pro negenerické třídy je typ instance jednoduše deklarovanou třídou.
Příklad: Následující příklad ukazuje několik deklarací tříd spolu s jejich typy instancí:
class A<T> // instance type: A<T> { class B {} // instance type: A<T>.B class C<U> {} // instance type: A<T>.C<U> } class D {} // instance type: D
end example
15.3.3 Členy konstruovaných typů
Nezděděné členy konstruovaného typu jsou získány nahrazením každé type_parameter v deklaraci člena odpovídající type_argument typu konstrukce. Proces nahrazení je založen na sémantickém významu deklarací typů a není to jen textová náhrada.
Příklad: Při deklaraci obecné třídy
class Gen<T,U> { public T[,] a; public void G(int i, T t, Gen<U,T> gt) {...} public U Prop { get {...} set {...} } public int H(double d) {...} }
konstruovaný typ
Gen<int[],IComparable<string>>
má následující členy:public int[,][] a; public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...} public IComparable<string> Prop { get {...} set {...} } public int H(double d) {...}
Typ členu v deklaraci
a
obecné třídy je "dvojrozměrné poleGen
", takže typ členuT
v vytvořeném typu výše je "dvojrozměrné pole jednorozměrného polea
" neboint
.int[,][]
end example
V rámci členů funkce instance je typ this
instance (§15.3.2) obsahující deklarace.
Všichni členové obecné třídy mohou použít parametry typu z jakékoli uzavřené třídy, a to buď přímo, nebo jako součást vytvořeného typu. Při použití určitého uzavřeného typu konstrukce (§8.4.3) za běhu se každé použití parametru typu nahradí argumentem typu zadaným pro konstruovaný typ.
Příklad:
class C<V> { public V f1; public C<V> f2; public C(V x) { this.f1 = x; this.f2 = this; } } class Application { static void Main() { C<int> x1 = new C<int>(1); Console.WriteLine(x1.f1); // Prints 1 C<double> x2 = new C<double>(3.1415); Console.WriteLine(x2.f1); // Prints 3.1415 } }
end example
15.3.4 Dědičnost
Třída dědí členy své přímé základní třídy. Dědičnost znamená, že třída implicitně obsahuje všechny členy své přímé základní třídy s výjimkou konstruktorů instancí, finalizátorů a statických konstruktorů základní třídy. Mezi důležité aspekty dědičnosti patří:
Dědičnost je tranzitivní. Je-li
C
odvozen z , aB
je odvozen zB
A
, pakC
dědí členy deklarované,B
stejně 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ěint
G(string s)
nezděděný člen získaný nahrazením argumentuint
typu parametru typuT
.D<int>
má také zděděný člen z deklaraceB
třídy . Tento zděděný člen je určen prvním určením základního typuB<int[]>
D<int>
třídy nahrazenímint
T
ve specifikaciB<T[]>
základní třídy . Potom jako typ argumentuB
int[]
, je nahrazenU
vpublic U F(long index)
, výnos zděděný členpublic int[] F(long index)
.end example
15.3.5 Nový modifikátor
Class_member_declaration je povoleno deklarovat člena se stejným názvem nebo podpisem jako zděděný člen. Když k tomu dojde, odvozený člen třídy je řečeno skrýt člen základní třídy. Přesné určení, kdy člen skryje zděděný člen, viz §7.7.2.3 .
Zděděný člen M
je považován za a neexistuje žádný jiný zděděný přístupný člen N, který již skryje . Implicitně skrytí zděděného členu se nepovažuje za chybu, ale kompilátor vydá upozornění, pokud deklarace odvozeného členu třídy neobsahuje modifikátor new
, který explicitně indikuje, že odvozený člen je určen ke skrytí základního členu. Pokud jedna nebo více částí částečné deklarace (§15.2.7) vnořeného typu obsahuje new
modifikátor, nevystaví se žádné upozornění, pokud vnořený typ skryje dostupný zděděný člen.
new
Pokud je modifikátor součástí deklarace, která neskryje dostupný zděděný člen, zobrazí se upozornění na tento účinek.
15.3.6 Modifikátory accessu
Class_member_declaration může mít některý z povolených druhů deklarované přístupnosti (§7.5.2): public
, , protected internal
, protected
, private protected
, , internal
nebo private
.
protected internal
S výjimkou kombinace a private protected
kombinace se jedná o chybu kompilátoru, která určuje více než jeden modifikátor přístupu.
Pokud class_member_declaration neobsahuje žádné modifikátory přístupu, private
předpokládá se.
15.3.7 Typy složek
Typy, které se používají v deklaraci členu, se nazývají základní typy tohoto členu. Možné typy složek jsou typ konstanty, pole, vlastnosti, události nebo indexeru, návratového typu metody nebo operátoru a typy parametrů metody, indexeru, operátoru nebo konstruktoru instance. Základní typy členů musí být alespoň tak přístupné jako člen sám (§7.5.5).
15.3.8 Statické členy a členy instance
Členy třídy jsou buď statické členy, nebo členy instance.
Poznámka: Obecně řečeno, je užitečné si představit statické členy jako patří do tříd a členů instance, jako patří k objektům (instance tříd). koncová poznámka
Pokud pole, metoda, vlastnost, událost, operátor nebo deklarace konstruktoru static
obsahuje modifikátor, deklaruje statický člen. Deklarace konstanty nebo typu navíc implicitně deklaruje statický člen. Statické členy mají následující charakteristiky:
- Je-li na statický člen
M
odkazován v member_access (§12.8.7) formulářeE.M
,E
označuje typ, který má členaM
. Jedná se o chybu v době kompilace,E
která označuje instanci. - Statické pole v negenerické třídě identifikuje přesně jedno umístění úložiště. Bez ohledu na to, kolik instancí negenerické třídy je vytvořeno, existuje pouze jedna kopie statického pole. Každý samostatný uzavřený konstruovaný typ (§8.4.3) má svou vlastní sadu statických polí bez ohledu na počet instancí uzavřeného typu konstrukce.
- Člen statické funkce (metoda, vlastnost, událost, operátor nebo konstruktor) nepracuje s konkrétní instancí a jedná se o chybu v době kompilace, která se na toto odkazuje v takovém členu funkce.
Pokud pole, metoda, vlastnost, událost, indexer, konstruktor nebo finalizační deklarace neobsahuje statický modifikátor, deklaruje člen instance. (Člen instance se někdy označuje jako nestatický člen.) Členové instance mají následující charakteristiky:
- Je-li na člena
M
instance odkazováno v member_access (§12.8.7) formulářeE.M
,E
označí 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
F
ukazuje, že v členu funkce instance lze použít simple_name (§12.8.4) pro přístup ke členům instance i statickým členům. MetodaG
ukazuje, že ve statickém členu funkce se jedná o chybu v době kompilace pro přístup k členu instance prostřednictvím simple_name. MetodaMain
ukazuje, že v member_access (§12.8.7) musí být členy instance přístupné prostřednictvím instancí a statické členy jsou přístupné prostřednictvím typů.end example
15.3.9 Vnořené typy
15.3.9.1 Obecné
Typ deklarovaný v rámci třídy nebo struktury se nazývá vnořený typ. Typ, který je deklarován v rámci kompilační jednotky nebo oboru názvů, se nazývá nenořený typ.
Příklad: V následujícím příkladu:
class A { class B { static void F() { Console.WriteLine("A.B.F"); } } }
třída
B
je vnořený typ, protože je deklarován v rámci třídyA
a třídaA
je nenořený typ, protože je deklarován v rámci kompilační jednotky.end example
15.3.9.2 Plně kvalifikovaný název
Plně kvalifikovaný název (§7.8.3) pro deklaraci vnořeného typu je S.N
, kde S
je plně kvalifikovaný název deklarace typu, ve které je deklarován typ N
, a N
je nekvalifikovaný název (§7.8.2) deklarace vnořeného typu (včetně všech generic_dimension_specifier (§12.8.18)).
15.3.9.3 Deklarovaná přístupnost
Nenořené typy můžou mít public
nebo internal
deklarovat přístupnost a ve výchozím nastavení deklarovaly internal
přístupnost. Vnořené typy můžou mít také tyto formy deklarované přístupnosti a jednu nebo více dalších forem deklarované přístupnosti v závislosti na tom, jestli je typ obsahující třídu nebo strukturu:
- Vnořený typ, který je deklarován ve třídě, může mít některý z povolených typů deklarované přístupnosti a stejně jako ostatní členy třídy má výchozí hodnotu
private
deklarovanou přístupnost. - Vnořený typ, který je deklarován ve struktuře, může mít libovolnou ze tří forem deklarované přístupnosti (
public
,internal
neboprivate
) a stejně jako ostatní členy struktury se ve výchozím nastaveníprivate
deklaruje přístupnost.
Příklad: Příklad
public class List { // Private data structure private class Node { public object Data; public Node? Next; public Node(object data, Node? next) { this.Data = data; this.Next = next; } } private Node? first = null; private Node? last = null; // Public interface public void AddToFront(object o) {...} public void AddToBack(object o) {...} public object RemoveFromFront() {...} public object RemoveFromBack() {...} public int Count { get {...} } }
deklaruje soukromou vnořenou třídu
Node
.end example
15.3.9.4 Skrytí
Vnořený typ může skrýt (§7.7.2.2) základní člen.
new
Modifikátor (§15.3.5) je povolen u vnořených deklarací typu, aby bylo možné skrýt explicitně.
Příklad: Příklad
class Base { public static void M() { Console.WriteLine("Base.M"); } } class Derived: Base { public new class M { public static void F() { Console.WriteLine("Derived.M.F"); } } } class Test { static void Main() { Derived.M.F(); } }
zobrazuje vnořenou třídu
M
, která skryje metoduM
definovanou vBase
.end example
15.3.9.5 tento přístup
Vnořený typ a jeho typ neobsahuje zvláštní vztah s ohledem na this_access (§12.8.14). Konkrétně this
v rámci vnořeného typu nelze použít k odkazování na členy instance obsahujícího typu. V případech, kdy vnořený typ potřebuje přístup k členům instance jeho obsahujícího typu, je možné přístup poskytnout poskytnutím this
instance obsahujícího typu jako argumentu konstruktoru pro vnořený typ.
Příklad: Následující příklad
class C { int i = 123; public void F() { Nested n = new Nested(this); n.G(); } public class Nested { C this_c; public Nested(C c) { this_c = c; } public void G() { Console.WriteLine(this_c.i); } } } class Test { static void Main() { C c = new C(); c.F(); } }
ukazuje tuto techniku. Instance vytvoří instanci
C
instanceNested
a předá své vlastníNested
do konstruktoru s cílem poskytnout následný přístup keC
členům instance.end example
15.3.9.6 Přístup k soukromým a chráněným členům obsahujícího typu
Vnořený typ má přístup ke všem členům, které jsou přístupné pro jeho typ obsahující, včetně členů obsahujícího typu, které mají private
a protected
deklarovaly přístupnost.
Příklad: Příklad
class C { private static void F() => Console.WriteLine("C.F"); public class Nested { public static void G() => F(); } } class Test { static void Main() => C.Nested.G(); }
zobrazuje třídu
C
, která obsahuje vnořenou tříduNested
. VNested
rámci metodyG
volá statickou metoduF
definovanou vC
aF
má privátní deklarovanou přístupnost.end example
Vnořený typ může také přistupovat k chráněným členům definovaným v základním typu jeho obsahujícího typu.
Příklad: V následujícím kódu
class Base { protected void F() => Console.WriteLine("Base.F"); } class Derived: Base { public class Nested { public void G() { Derived d = new Derived(); d.F(); // ok } } } class Test { static void Main() { Derived.Nested n = new Derived.Nested(); n.G(); } }
vnořená třída
Derived.Nested
přistupuje k chráněné metoděF
definované vDerived
'základní třídě ,Base
voláním prostřednictvím instanceDerived
.end example
15.3.9.7 Vnořené typy v obecných třídách
Deklarace obecné třídy může obsahovat vnořené deklarace typu. Parametry typu ohraničující třídy lze použít v rámci vnořených typů. Deklarace vnořeného typu může obsahovat další parametry typu, které se vztahují pouze na vnořený typ.
Každá deklarace typu obsažená v deklaraci obecné třídy je implicitně deklarace obecného typu. Při zápisu odkazu na typ vnořený do obecného typu se pojmenuje obsahující konstruovaný typ, včetně argumentů jeho typu. Z vnější třídy však lze vnořený typ použít bez kvalifikace; Typ instance vnější třídy lze implicitně použít při vytváření vnořeného typu.
Příklad: Následující příklad ukazuje tři různé správné způsoby, jak odkazovat na vytvořený typ vytvořený z
Inner
; první dva jsou ekvivalentní:class Outer<T> { class Inner<U> { public static void F(T t, U u) {...} } static void F(T t) { Outer<T>.Inner<string>.F(t, "abc"); // These two statements have Inner<string>.F(t, "abc"); // the same effect Outer<int>.Inner<string>.F(3, "abc"); // This type is different Outer.Inner<string>.F(t, "abc"); // Error, Outer needs type arg } }
end example
I když je to špatný programovací styl, parametr typu v vnořeném typu může skrýt člen nebo typ parametr deklarovaný ve vnějším typu.
Příklad:
class Outer<T> { class Inner<T> // Valid, hides Outer's T { public T t; // Refers to Inner's T } }
end example
15.3.10 Názvy rezervovaných členů
15.3.10.1 Obecné
Pro usnadnění provádění podkladového spuštění jazyka C# pro každou deklaraci zdrojového členu, která je vlastností, událostí nebo indexerem, si implementace vyhrazuje dva podpisy metody založené na druhu deklarace člena, jeho názvu a jeho typu (§15.3.10.2, §15.3.10.3, §15.3.10.4). Jedná se o chybu v době kompilace pro program, který deklaruje člena, jehož podpis odpovídá podpisu rezervovanému členem deklarovanému ve stejném oboru, i když základní implementace za běhu tyto rezervace nevyužívá.
Rezervované názvy nezavádějí deklarace, proto se nezúčastní vyhledávání členů. Podpisy vyhrazené metody prohlášení se však účastní dědičnosti (§15.3.4) a mohou být skryty modifikátorem (new
).
Poznámka: Rezervace těchto názvů slouží třem účelům:
- 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 T
jsou vyhrazeny následující podpisy:
T get_P();
void set_P(T value);
Oba podpisy jsou vyhrazeny, i když je vlastnost jen pro čtení nebo jen pro zápis.
Příklad: V následujícím kódu
class A { public int P { get => 123; } } class B : A { public new int get_P() => 456; public new void set_P(int value) { } } class Test { static void Main() { B b = new B(); A a = b; Console.WriteLine(a.P); Console.WriteLine(b.P); Console.WriteLine(b.get_P()); } }
Třída
A
definuje vlastnostP
jen pro čtení , a proto si rezervuje podpisy proget_P
aset_P
metody.A
třídaB
je odvozena zA
obou těchto rezervovaných podpisů a skryje je. Příklad vytvoří výstup:123 123 456
end example
15.3.10.3 Názvy členů vyhrazené pro události
Pro akci E
(§15.8) typu T
delegá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 L
parametrů jsou vyhrazeny následující podpisy:
T get_Item(L);
void set_Item(L, T value);
Oba podpisy jsou rezervované, i když je indexer jen pro čtení nebo jen pro zápis.
Kromě toho je název Item
člena vyhrazen.
15.3.10.5 Názvy členů vyhrazené pro finalizátory
Pro třídu obsahující finalizátor (§15.13) je vyhrazen následující podpis:
void Finalize();
15.3.10.6 Názvy metod vyhrazených pro operátory
Následující názvy metod jsou vyhrazeny. Zatímco mnoho z nich má v této specifikaci odpovídající operátory, některé jsou vyhrazené pro použití v budoucích verzích, zatímco některé jsou vyhrazeny pro spolupráci s jinými jazyky.
Název metody | Operátor jazyka C# |
---|---|
op_Addition |
+ (binární) |
op_AdditionAssignment |
(rezervováno) |
op_AddressOf |
(rezervováno) |
op_Assign |
(rezervováno) |
op_BitwiseAnd |
& (binární) |
op_BitwiseAndAssignment |
(rezervováno) |
op_BitwiseOr |
\| |
op_BitwiseOrAssignment |
(rezervováno) |
op_CheckedAddition |
(vyhrazeno pro budoucí použití) |
op_CheckedDecrement |
(vyhrazeno pro budoucí použití) |
op_CheckedDivision |
(vyhrazeno pro budoucí použití) |
op_CheckedExplicit |
(vyhrazeno pro budoucí použití) |
op_CheckedIncrement |
(vyhrazeno pro budoucí použití) |
op_CheckedMultiply |
(vyhrazeno pro budoucí použití) |
op_CheckedSubtraction |
(vyhrazeno pro budoucí použití) |
op_CheckedUnaryNegation |
(vyhrazeno pro budoucí použití) |
op_Comma |
(rezervováno) |
op_Decrement |
-- (předpona a přípona) |
op_Division |
/ |
op_DivisionAssignment |
(rezervováno) |
op_Equality |
== |
op_ExclusiveOr |
^ |
op_ExclusiveOrAssignment |
(rezervováno) |
op_Explicit |
explicitní (zužující) převod |
op_False |
false |
op_GreaterThan |
> |
op_GreaterThanOrEqual |
>= |
op_Implicit |
implicitní (rozšiřující) převod |
op_Increment |
++ (předpona a přípona) |
op_Inequality |
!= |
op_LeftShift |
<< |
op_LeftShiftAssignment |
(rezervováno) |
op_LessThan |
< |
op_LessThanOrEqual |
<= |
op_LogicalAnd |
(rezervováno) |
op_LogicalNot |
! |
op_LogicalOr |
(rezervováno) |
op_MemberSelection |
(rezervováno) |
op_Modulus |
% |
op_ModulusAssignment |
(rezervováno) |
op_MultiplicationAssignment |
(rezervováno) |
op_Multiply |
* (binární) |
op_OnesComplement |
~ |
op_PointerDereference |
(rezervováno) |
op_PointerToMemberSelection |
(rezervováno) |
op_RightShift |
>> |
op_RightShiftAssignment |
(rezervováno) |
op_SignedRightShift |
(rezervováno) |
op_Subtraction |
- (binární) |
op_SubtractionAssignment |
(rezervováno) |
op_True |
true |
op_UnaryNegation |
- (unární) |
op_UnaryPlus |
+ (unární) |
op_UnsignedRightShift |
(vyhrazeno pro budoucí použití) |
op_UnsignedRightShiftAssignment |
(rezervováno) |
15.4 Konstanty
Konstanta je člen třídy, který představuje konstantní hodnotu: hodnotu, kterou lze vypočítat v době kompilace. Constant_declaration zavádí jednu nebo více konstant daného typu.
constant_declaration
: attributes? constant_modifier* 'const' type constant_declarators ';'
;
constant_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
;
Constant_declaration může obsahovat sadu atributů (§22), new
modifikátor (§15.3.5) a kterýkoli z povolených druhů deklarované přístupnosti (§15.3.6). Atributy a modifikátory platí pro všechny členy deklarované constant_declaration. I když jsou konstanty považovány za statické členy, constant_declaration nevyžaduje ani neumožňuje static
modifikátor. Jedná se o chybu, která se u stejného modifikátoru zobrazí vícekrát v deklaraci konstanty.
Typ constant_declaration určuje typ členů zavedených deklarací. Za typem následuje seznam constant_declarator s (§13.6.3), z nichž každý zavádínového člena. Constant_declarator se skládá z identifikátoru, který člena pojmenuje a za=
ním "" token následovaný constant_expression (§12.23), který dává hodnotu člena.
Typ uvedený v konstantní deklaraci musí být , , sbyte
, byte
short
, , ushort
, int
, uint
long
ulong
char
float
, double
, decimal
, , enum_type bool
string
nebo reference_type. Každý constant_expression poskytne hodnotu cílového typu nebo typu, který lze převést na cílový typ implicitním převodem (§10.2).
Typ konstanty musí být alespoň tak přístupný jako samotná konstanta (§7.5.5).
Hodnota konstanty se získá ve výrazu pomocí simple_name (§12.8.4) nebo member_access (§12.8.7).
Konstanta se může účastnit constant_expression. Konstanta se tedy může použít v libovolném konstruktoru , který vyžaduje constant_expression.
Poznámka: Mezi příklady takových konstruktorů patří
case
popisky,goto case
příkazy,enum
deklarace členů, atributy a další deklarace konstant. koncová poznámka
Poznámka: Jak je popsáno v §12.23, constant_expression je výraz, který lze plně vyhodnotit v době kompilace. Vzhledem k tomu, že jediný způsob, jak vytvořit jinou hodnotu než null reference_type , než
string
je použitínew
operátoru, a protoženew
operátor není povolen v constant_expression, jediná možná hodnota pro konstanty reference_typejiné nežstring
jenull
. koncová poznámka
Je-li žádoucí symbolický název konstantní hodnoty, ale pokud typ této hodnoty není povolen v deklaraci konstanty nebo pokud hodnotu nelze vypočítat v době kompilace constant_expression, lze místo toho použít pole jen pro čtení (§15.5.3).
Poznámka: Sémantika
const
správy verzí areadonly
odlišná (§15.5.3.3). koncová poznámka
Deklarace konstanty, která deklaruje více konstant, je ekvivalentní více deklarací jednoduchých konstant se stejnými atributy, modifikátory a typem.
Příklad:
class A { public const double X = 1.0, Y = 2.0, Z = 3.0; }
je ekvivalentem
class A { public const double X = 1.0; public const double Y = 2.0; public const double Z = 3.0; }
end example
Konstanty mohou záviset na jiných konstantách ve stejném programu, pokud závislosti nejsou cyklický charakter.
Příklad: V následujícím kódu
class A { public const int X = B.Z + 1; public const int Y = 10; } class B { public const int Z = A.Y + 1; }
kompilátor musí nejprve vyhodnotit
A.Y
, pak vyhodnotitB.Z
a nakonec vyhodnotitA.X
, vytvořit hodnoty10
,11
a12
.end example
Deklarace konstant můžou záviset na konstantách z jiných programů, ale tyto závislosti jsou možné pouze v jednom směru.
Příklad: Odkazování na výše uvedený příklad, pokud
A
aB
byly deklarovány v samostatných programech, by bylo možnéA.X
záviset naB.Z
, aleB.Z
nemohl by pak současně záviset naA.Y
. end example
15.5 Pole
15.5.1 Obecné
Pole je člen, který představuje proměnnou přidruženou k objektu nebo třídě. Field_declaration zavádí jedno nebo více polí daného typu.
field_declaration
: attributes? field_modifier* type variable_declarators ';'
;
field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| unsafe_modifier // unsafe code support
;
variable_declarators
: variable_declarator (',' variable_declarator)*
;
variable_declarator
: identifier ('=' variable_initializer)?
;
unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
Field_declaration může obsahovat sadu atributů (§22), new
modifikátor (§15.3.5), platnou kombinaci čtyř modifikátorů přístupu (§15.3.6) a static
modifikátoru (§15.5.2). Kromě toho může field_declaration obsahovat readonly
modifikátor (§15.5.3) nebo volatile
modifikátor (§15.5.4), ale ne obojí. Atributy a modifikátory se vztahují na všechny členy deklarované field_declaration. Jedná se o chybu, která se u stejného modifikátoru zobrazí vícekrát v field_declaration.
Typ field_declaration určuje typ členů zavedených deklarací. Za typem následuje seznam variable_declarators, z nichž každý představuje nového člena. Variable_declarator se skládá z identifikátoru, který označuje tento člen, volitelně následovaný tokenem "=
" a variable_initializer (§15.5.6), který dává počáteční hodnotu tohoto člena.
Typ pole musí být alespoň tak přístupný jako pole samotné (§7.5.5).
Hodnota pole je získána ve výrazu pomocí simple_name (§12.8.4), member_access (§12.8.7) nebo base_access (§12.8.15). Hodnota pole bez čtení je změněna pomocí přiřazení (§12.21). Hodnotu nečteného pole lze získat i upravit pomocí operátorů přírůstku přípony a dekrementace (§12.8.16) a operátory inkrementace a dekrementace předpony (§12.9.6).
Deklarace pole, která deklaruje více polí, je ekvivalentní více deklarací jednoho pole se stejnými atributy, modifikátory a typem.
Příklad:
class A { public static int X = 1, Y, Z = 100; }
je ekvivalentem
class A { public static int X = 1; public static int Y; public static int Z = 100; }
end example
15.5.2 Statická pole a pole instance
Pokud deklarace pole obsahuje static
modifikátor, pole zavedená deklarací jsou statická pole. Pokud není k dispozici žádný static
modifikátor, pole zavedená deklarací jsou pole instance. Statická pole a pole instancí jsou dvěma druhy proměnných (§9), které jazyk C# podporuje, a v některých případech se označují jako statické proměnné a proměnné instancí.
Jak je vysvětleno v §15.3.8, každá instance třídy obsahuje úplnou sadu polí instance třídy, zatímco existuje pouze jedna sada statických polí pro každou ne generickou třídu nebo uzavřený konstruovaný typ bez ohledu na počet instancí třídy nebo uzavřeného typu konstrukce.
15.5.3 Pole jen pro čtení
15.5.3.1 Obecné
Pokud field_declaration obsahuje readonly
modifikátor, pole zavedená deklarací jsou pole jen pro čtení. Přímá přiřazení pro pole jen pro čtení mohou nastat pouze v rámci této deklarace nebo v konstruktoru instance nebo statickém konstruktoru ve stejné třídě. (Pole jen pro čtení lze v těchto kontextech přiřadit vícekrát.) Konkrétně přímé přiřazení k poli jen pro čtení jsou povolená pouze v následujících kontextech:
- V variable_declarator, která zavádí pole (zahrnutím variable_initializer do deklarace).
- V případě pole instance v konstruktorech instance třídy, která obsahuje deklaraci pole; pro statické pole ve statickém konstruktoru třídy, která obsahuje deklaraci pole. Jedná se také o jediné kontexty, ve kterých je platné předat pole jen pro čtení jako výstupní nebo referenční parametr.
Pokus o přiřazení k poli jen pro čtení nebo jeho předání jako výstupní nebo referenční parametr v jakémkoli jiném kontextu je chyba v době kompilace.
15.5.3.2 Použití statických polí jen pro čtení pro konstanty
Statické pole jen pro čtení je užitečné, pokud je žádoucí symbolický název konstantní hodnoty, ale pokud typ hodnoty není povolen v deklaraci const nebo pokud hodnotu nelze vypočítat v době kompilace.
Příklad: V následujícím kódu
public class Color { public static readonly Color Black = new Color(0, 0, 0); public static readonly Color White = new Color(255, 255, 255); public static readonly Color Red = new Color(255, 0, 0); public static readonly Color Green = new Color(0, 255, 0); public static readonly Color Blue = new Color(0, 0, 255); private byte red, green, blue; public Color(byte r, byte g, byte b) { red = r; green = g; blue = b; } }
Black
,White
,Red
Green
aBlue
členy nelze deklarovat jako členy const, protože jejich hodnoty nelze vypočítat v době kompilace. Deklarování jestatic readonly
ale má mnohem stejný účinek.end example
15.5.3.3 Správa verzí konstant a statických polí pro čtení
Konstanty a pole jen pro čtení mají různé sémantiky binární správy verzí. Když výraz odkazuje na konstantu, hodnota konstanty se získá v době kompilace, ale když výraz odkazuje na pole jen pro čtení, hodnota pole se nezískne, dokud neběží za běhu.
Příklad: Představte si aplikaci, která se skládá ze dvou samostatných programů:
namespace Program1 { public class Utils { public static readonly int x = 1; } }
a
namespace Program2 { class Test { static void Main() { Console.WriteLine(Program1.Utils.X); } } }
Program1
OboryProgram2
názvů označují dva programy, které jsou kompilovány samostatně. ProtožeProgram1.Utils.X
je deklarován jakostatic readonly
pole, výstup hodnoty příkazemConsole.WriteLine
není znám v době kompilace, ale spíše je získán za běhu. Proto pokud je hodnotaX
změněna aProgram1
je rekompilována,Console.WriteLine
příkaz vypíše novou hodnotu i v případěProgram2
, že není rekompilován. BylaX
by však konstanta, hodnotaX
by byla získána v doběProgram2
kompilace a zůstala by nedotčena změnami,Program1
dokudProgram2
nebude znovu zkompilována.end example
15.5.4 Nestálá pole
Pokud field_declaration obsahuje volatile
modifikátor, pole zavedená danou deklarací jsou nestálá pole. V případě nestálých polí můžou techniky optimalizace, které změní pořadí instrukcí, vést k neočekávaným a nepředvídatelným výsledkům ve vícevláknových programech, které přistupují k polím bez synchronizace, jako je například ta poskytovaná lock_statement (§13.13). Tyto optimalizace může provádět kompilátor, systém za běhu nebo hardware. U nestálých polí jsou taková optimalizace změna pořadí omezena:
- Čtení nestálého pole se nazývá nestálé čtení. Volatilní čtení má "získání sémantiky"; to znamená, že je zaručeno, že dojde před všemi odkazy na paměť, ke kterým dojde v pořadí instrukcí.
- Zápis nestálého pole se nazývá těkavý zápis. Těkavý zápis má "sémantiku uvolnění"; to znamená, že je zaručeno, že dojde po jakékoli odkazy na paměť před instrukcí zápisu v pořadí instrukcí.
Tato omezení zajišťují, že všechna vlákna budou sledovat nestálé zápisy prováděné jakýmkoli jiným vláknem v pořadí, v jakém byly provedeny. Odpovídající implementace není nutná k poskytnutí jediného celkového pořadí nestálých zápisů, jak je vidět ze všech vláken provádění. Typ nestálého pole musí být jedním z následujících:
- Reference_type.
- Type_parameter, o které je známo, že jde o referenční typ (§15.2.5).
- Typ
byte
, , ,sbyte
,short
ushort
int
uint
char
float
,bool
, nebo .System.IntPtr
System.UIntPtr
- Enum_type typu ,
byte
,sbyte
short
, ,ushort
neboint
.
Příklad: Příklad
class Test { public static int result; public static volatile bool finished; static void Thread2() { result = 143; finished = true; } static void Main() { finished = false; // Run Thread2() in a new thread new Thread(new ThreadStart(Thread2)).Start(); // Wait for Thread2() to signal that it has a result // by setting finished to true. for (;;) { if (finished) { Console.WriteLine($"result = {result}"); return; } } } }
vytvoří výstup:
result = 143
V tomto příkladu metoda
Main
spustí nové vlákno, které spustí metoduThread2
. Tato metoda ukládá hodnotu do nevolatelní pole volanéresult
, pak ukládátrue
do nestálého polefinished
. Hlavní vlákno čeká na nastavenífinished
poletrue
a pak přečte poleresult
. Vzhledem k tomufinished
, že bylo deklarovánovolatile
, musí hlavní vlákno číst hodnotu143
z poleresult
.finished
Pokud pole nebylo deklarovánovolatile
, pak by bylo možné, abyresult
úložiště bylo viditelné pro hlavní vlákno po uloženífinished
do , a proto hlavní vlákno číst hodnotu 0 z poleresult
. Deklarovánífinished
jakovolatile
pole brání jakékoli takové nekonzistence.end example
Inicializace pole 15.5.5
Počáteční hodnota pole, ať už jde o statické pole nebo pole instance, je výchozí hodnota (§9.3) typu pole. Před provedením této výchozí inicializace není možné sledovat hodnotu pole a pole proto nikdy není inicializováno.
Příklad: Příklad
class Test { static bool b; int i; static void Main() { Test t = new Test(); Console.WriteLine($"b = {b}, i = {t.i}"); } }
vytvoří výstup.
b = False, i = 0
a
b
i
oba jsou automaticky inicializovány na výchozí hodnoty.end example
15.5.6 Inicializátory proměnných
15.5.6.1 Obecné
Deklarace polí mohou zahrnovat variable_initializers. Pro statická pole inicializátory proměnných odpovídají příkazům přiřazení, které se provádějí během inicializace třídy. Například pole proměnných inicializátory odpovídají příkazům přiřazení, které se spustí při vytvoření instance třídy.
Příklad: Příklad
class Test { static double x = Math.Sqrt(2.0); int i = 100; string s = "Hello"; static void Main() { Test a = new Test(); Console.WriteLine($"x = {x}, i = {a.i}, s = {a.s}"); } }
vytvoří výstup.
x = 1.4142135623730951, i = 100, s = Hello
vzhledem k tomu, že přiřazení, ke kterému dojde
x
, když inicializátory statických polí provádějí ai
s
při provádění inicializátorů polí instance.end example
Výchozí inicializace hodnoty popsaná v §15.5.5 se vyskytuje pro všechna pole včetně polí s inicializátory proměnných. Proto při inicializaci třídy jsou všechna statická pole v této třídě nejprve inicializována na jejich výchozí hodnoty a pak jsou inicializátory statických polí prováděny v textovém pořadí. Podobně při vytvoření instance třídy jsou všechna pole instance v této instanci nejprve inicializována na jejich výchozí hodnoty a potom inicializátory pole instance se spustí v textovém pořadí. Pokud existují deklarace polí ve více deklarací částečného typu pro stejný typ, pořadí částí není zadáno. V každé části se však inicializátory polí provádějí v pořadí.
Statická pole s inicializátory proměnných je možné pozorovat ve výchozím stavu hodnoty.
Příklad: Toto se však důrazně nedoporučuje v rámci stylu. Příklad
class Test { static int a = b + 1; static int b = a + 1; static void Main() { Console.WriteLine($"a = {a}, b = {b}"); } }
vykazuje toto chování. Navzdory cyklických definicích
a
ab
, program je platný. Výsledkem je výstup.a = 1, b = 2
vzhledem k tomu, že statická pole
a
ab
inicializují se do0
(výchozí hodnota proint
) před spuštěním jejich inicializátorů. Při inicializátoru spuštěnía
je hodnotab
nula, a protoa
je inicializována na1
. Při inicializátoru spuštěníb
je hodnota již1
a takb
je inicializována na2
.end example
15.5.6.2 Inicializace statického pole
Inicializátory proměnných statického pole třídy odpovídají sekvenci přiřazení, která jsou provedena v textovém pořadí, ve kterém jsou uvedeny v deklaraci třídy (§15.5.6.1). V rámci částečné třídy je význam "textového pořadí" určen §15.5.6.1. Pokud ve třídě existuje statický konstruktor (§15.12), dojde k provedení inicializátorů statických polí bezprostředně před provedením tohoto statického konstruktoru. Jinak se inicializátory statických polí provádějí v době závislé na implementaci před prvním použitím statického pole této třídy.
Příklad: Příklad
class Test { static void Main() { Console.WriteLine($"{B.Y} {A.X}"); } public static int F(string s) { Console.WriteLine(s); return 1; } } class A { public static int X = Test.F("Init A"); } class B { public static int Y = Test.F("Init B"); }
může vytvořit výstup:
Init A Init B 1 1
nebo výstup:
Init B Init A 1 1
vzhledem k tomu, že spuštění inicializátoru
X
aY
inicializátoru může dojít v jiném pořadí; jsou omezeny pouze před odkazy na tato pole. V tomto příkladu však:class Test { static void Main() { Console.WriteLine($"{B.Y} {A.X}"); } public static int F(string s) { Console.WriteLine(s); return 1; } } class A { static A() {} public static int X = Test.F("Init A"); } class B { static B() {} public static int Y = Test.F("Init B"); }
výstupem musí být:
Init B Init A 1 1
vzhledem k tomu, že pravidla pro provádění statických konstruktorů (jak je definováno v §15.12) stanoví, že
B
statický konstruktor (a protoB
inicializátory statických polí) se spustí předA
statickým konstruktorem a inicializátory polí.end example
Inicializace pole instance 15.5.6.3
Inicializátory proměnných pole instance odpovídají sekvenci přiřazení, která jsou provedena okamžitě po vstupu do některé z konstruktorů instance (§15.11.3) této třídy. V rámci částečné třídy je význam "textového pořadí" určen §15.5.6.1. Inicializátory proměnných se provádějí v textovém pořadí, ve kterém jsou uvedeny v deklaraci třídy (§15.5.6.1). Proces vytvoření a inicializace instance třídy je dále popsán v §15.11.
Inicializátor proměnné pro pole instance nemůže odkazovat na instanci, která se vytváří. Jedná se tedy o chybu v době kompilace odkazování this
v inicializátoru proměnné, protože se jedná o chybu v době kompilace pro inicializátor proměnné odkazovat na libovolný člen instance prostřednictvím simple_name.
Příklad: V následujícím kódu
class A { int x = 1; int y = x + 1; // Error, reference to instance member of this }
Inicializátor proměnné má
y
za následek chybu v době kompilace, protože odkazuje na člena vytvářené instance.end example
15.6 Metody
15.6.1 Obecné
Metoda je člen, který implementuje výpočet nebo akci, která může být provedena objektem nebo třídou. Metody jsou deklarovány pomocí method_declarations:
method_declaration
: attributes? method_modifiers return_type method_header method_body
| attributes? ref_method_modifiers ref_kind ref_return_type method_header
ref_method_body
;
method_modifiers
: method_modifier* 'partial'?
;
ref_kind
: 'ref'
| 'ref' 'readonly'
;
ref_method_modifiers
: ref_method_modifier*
;
method_header
: member_name '(' parameter_list? ')'
| member_name type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
method_modifier
: ref_method_modifier
| 'async'
;
ref_method_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
return_type
: ref_return_type
| 'void'
;
ref_return_type
: type
;
member_name
: identifier
| interface_type '.' identifier
;
method_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
| ';'
;
ref_method_body
: block
| '=>' 'ref' variable_reference ';'
| ';'
;
Gramatické poznámky:
- unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
- při uznání method_body , pokud se použijí alternativy null_conditional_invocation_expression i výrazu , zvolí se první možnost.
Poznámka: Překrývající se alternativy a priority mezi těmito alternativami jsou určeny výhradně pro popisné pohodlí; gramatická pravidla by mohla být propracovaná tak, aby se překrývala. ANTLR a další gramatické systémy přijímají stejné pohodlí, takže method_body má zadanou sémantiku automaticky. koncová poznámka
Method_declaration může obsahovat sadu atributů (§22) a jeden z povolených druhů deklarované přístupnosti (§15.3.6), new
(§15.3.5), (§15.6.3), (static
), (virtual
). 6.4), override
(§15.6.5), sealed
(§15.6.6), abstract
(§15.6.7), extern
(§15.6.8) a async
(§15.15) modifikátory.
Deklarace má platnou kombinaci modifikátorů, pokud jsou splněny všechny následující podmínky:
- Prohlášení zahrnuje platnou kombinaci modifikátorů přístupu (§15.3.6).
- Deklarace neobsahuje vícekrát stejný modifikátor.
- Deklarace obsahuje nejvýše jeden z následujících modifikátorů:
static
,virtual
aoverride
. - Deklarace obsahuje nejvýše jeden z následujících modifikátorů:
new
aoverride
. - Pokud deklarace obsahuje
abstract
modifikátor, deklarace neobsahuje žádný z následujících modifikátorů:static
,virtual
,sealed
neboextern
. - Pokud deklarace obsahuje
private
modifikátor, deklarace neobsahuje žádný z následujících modifikátorů:virtual
,override
neboabstract
. - Pokud deklarace obsahuje
sealed
modifikátor, pak deklarace obsahujeoverride
také modifikátor. - Pokud deklarace obsahuje
partial
modifikátor, neobsahuje žádný z následujících modifikátorů:new
,public
, ,protected
internal
,private
,virtual
sealed
,override
, , neboabstract
extern
.
Metody jsou klasifikovány podle toho, co, pokud něco, vrátí:
- Pokud
ref
je k dispozici, metoda je vrátí-by-ref a vrátí proměnnou odkaz, který je volitelně jen pro čtení; - V opačném případě, pokud , metoda je
void
a nevrací hodnotu; - V opačném případě metoda vrátí hodnotu po hodnotě a vrátí hodnotu.
Return_type deklarace metody return-by-value nebo return-no-value určuje typ výsledku, pokud existuje, vrácená metodou. Modifikátorpartial
) může obsahovat pouze metodu vrácení bez hodnoty. Pokud deklarace obsahuje async
modifikátor,return_type musí být void
nebo metoda vrátí po hodnotě a návratový typ je typ úkolu (§15.15.1).
Ref_return_type deklarace metody return-by-ref určuje typ proměnné odkazované variable_reference vrácenou metodou.
Obecná metoda je metoda, jejíž deklarace obsahuje type_parameter_list. Určuje parametry typu pro metodu. Volitelné type_parameter_constraints_clauseurčují omezení parametrů typu.
Obecná method_declaration pro explicitní implementaci člena rozhraní nesmí mít žádné type_parameter_constraints_clause; deklarace dědí všechna omezení z omezení metody rozhraní.
Podobně deklarace metody s modifikátorem override
nesmí mít žádné type_parameter_constraints_clausea omezení parametrů typu metody se dědí z virtuální metody, která se přepisuje.
Member_name určuje název metody. Není-li metoda explicitní implementací člena rozhraní (§18.6.2), member_name je jednoduše identifikátor.
Pro explicitní implementaci člena rozhraní se member_name skládá z interface_type následované ".
" a identifikátorem. V tomto případě prohlášení neobsahuje žádné jiné modifikátory než (případně) extern
nebo async
.
Nepovinný parameter_list určuje parametry metody (§15.6.2).
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 in
pomocí , out
a ref
.
Type_parameter metody jsou v oboru v celém method_declaration a lze je použít k formování typů v celém oboru v return_type nebo ref_return_type, method_body nebo ref_method_body a type_parameter_constraints_clause, ale ne v atributech.
Všechny parametry a parametry typu musí mít různé názvy.
Parametry metody 15.6.2
15.6.2.1 Obecné
Parametry metody, pokud existují, jsou deklarovány parameter_list metody.
parameter_list
: fixed_parameters
| fixed_parameters ',' parameter_array
| parameter_array
;
fixed_parameters
: fixed_parameter (',' fixed_parameter)*
;
fixed_parameter
: attributes? parameter_modifier? type identifier default_argument?
;
default_argument
: '=' expression
;
parameter_modifier
: parameter_mode_modifier
| 'this'
;
parameter_mode_modifier
: 'ref'
| 'out'
| 'in'
;
parameter_array
: attributes? 'params' array_type identifier
;
Seznam parametrů se skládá z jednoho nebo více parametrů oddělených čárkami, z nichž pouze poslední může být parameter_array.
Fixed_parameter se skládá z volitelné sady atributů (§22), volitelného in
, out
, , ref
nebo this
modifikátoru; typu; identifikátoru; a volitelného default_argument. Každý fixed_parameter deklaruje parametr daného typu s daným názvem.
this
Modifikátor určuje metodu jako rozšiřující metodu a je povolena pouze u prvního parametru statické metody v ne generické statické třídě, která není vnořená. Pokud je struct
parametr typem nebo parametrem struct
typu 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 ref
out
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()
, kdeS
je typ hodnoty - výraz formuláře
default(S)
, kdeS
je typ hodnoty
Výraz se implicitně konvertibilní identitou nebo převodem s možnou hodnotou null na typ parametru.
Pokud v prováděcí deklaraci částečné metody dojde k nepovinným parametrům (§15.6.9), explicitní implementace člena rozhraní (§18.6.2), deklarace indexeru s jedním parametrem (§ 15.9) nebo v deklaraci operátoru (§15.10.1) by kompilátor měl dát upozornění, protože tyto členy nelze vyvolat způsobem, který umožňuje vynechat argumenty.
Parameter_array se skládá z volitelné sady atributů (§22), params
modifikátoru, array_type a identifikátoru. Pole parametrů deklaruje jeden parametr daného typu pole s daným názvem.
Array_type pole parametru musí být jednorozměrný typ matice (§17.2). Při vyvolání metody pole parametrů umožňuje zadat jeden argument daného typu pole, nebo umožňuje zadat nula nebo více argumentů typu prvku pole. Pole parametrů jsou podrobněji popsána v §15.6.2.4.
K parameter_array může dojít po volitelném parametru, ale nemůže mít výchozí hodnotu – vynechání argumentů pro parameter_array by místo toho vedlo k vytvoření prázdného pole.
Příklad: Následující příklad znázorňuje různé druhy parametrů:
void M<T>( ref int i, decimal d, bool b = false, bool? n = false, string s = "Hello", object o = null, T t = default(T), params int[] a ) { }
V parameter_list pro
M
i
je povinnýref
parametrd
, je povinný parametrb
hodnoty , ,s
o
at
jsou volitelné parametry hodnoty aa
je pole parametrů.end example
Deklarace metody vytvoří samostatný prostor deklarace (§7.3) pro parametry a parametry typu. Názvy se do tohoto prostoru deklarací zavádějí seznamem parametrů typu a seznamem parametrů metody. Tělo metody, pokud existuje, je považováno za vnořené v rámci tohoto prostoru deklarace. Jedná se o chybu, že dva členové prostoru deklarace metody mají stejný název.
Vyvolání metody (§12.8.10.2) vytvoří kopii specifickou pro toto vyvolání, parametrů a místních proměnných metody a seznam argumentů vyvolání přiřadí hodnoty nebo proměnné odkazy na nově vytvořené parametry. V rámci bloku metody mohou být parametry odkazovány jejich identifikátory ve výrazech simple_name (§12.8.4).
Existují následující typy parametrů:
- 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,
in
modifikátory ,out
aref
modifikátory jsou součástí podpisu metody, aleparams
modifikátor není. koncová poznámka
15.6.2.2 Parametry hodnoty
Parametr deklarovaný bez modifikátorů je parametr hodnoty. Parametr hodnoty je místní proměnná, která získá počáteční hodnotu z odpovídajícího argumentu zadaného v vyvolání metody.
Určitá pravidla přiřazení naleznete v §9.2.5.
Odpovídající argument v vyvolání metody je výraz, který je implicitně konvertibilní (§10.2) na typ parametru.
Metoda má povoleno přiřazovat nové hodnoty k parametru hodnoty. Taková přiřazení mají vliv pouze na umístění místního úložiště reprezentované parametrem hodnoty – nemají žádný vliv na skutečný argument zadaný při vyvolání metody.
15.6.2.3 Parametry podle odkazu
15.6.2.3.1 Obecné
Vstupní, výstupní a referenční parametry jsou parametrypodle odkazu. Parametr by-reference je místní referenční proměnná (§9.7); počáteční odkaz je získán z odpovídajícího argumentu zadaného při vyvolání metody.
Poznámka: Odkaz na parametr by-reference lze změnit pomocí operátoru přiřazení odkazu (
= ref
).
Pokud je parametr parametrem podle odkazu, odpovídající argument vyvolání metody se skládá z odpovídajícího klíčového slova , in
nebo ref
out
, následovaného variable_reference (§9.5) stejného typu jako parametr. Pokud je parametr parametrem in
, může být argument výrazem , pro který existuje implicitní převod (§10.2) z tohoto výrazu argumentu na typ odpovídajícího parametru.
Referenční parametry nejsou povoleny pro funkce deklarované jako iterátor (§15.14) nebo asynchronní funkce (§15.15).
V metodě, která přebírá více parametrů podle odkazu, je možné, aby více názvů představovalo stejné umístění úložiště.
15.6.2.3.2 Vstupní parametry
Parametr deklarovaný pomocí modifikátoru je vstupní parametr.in
Argument odpovídající vstupnímu parametru je buď proměnná existující v okamžiku vyvolání metody, nebo proměnná vytvořená implementací (§12.6.2.3) při vyvolání metody. Určitá pravidla přiřazení naleznete v §9.2.8.
Jedná se o chybu v době kompilace pro úpravu hodnoty vstupního parametru.
Poznámka: Primárním účelem vstupních parametrů je efektivita. Pokud je typ parametru metody velkou strukturou (z hlediska požadavků na paměť), je užitečné se vyhnout kopírování celé hodnoty argumentu při volání metody. Vstupní parametry umožňují metodám odkazovat na existující hodnoty v paměti a zároveň poskytovat ochranu proti nežádoucím změnám těchto hodnot. koncová poznámka
15.6.2.3.3 Referenční parametry
Parametr deklarovaný pomocí modifikátoru je referenční parametr.ref
Pro pravidla určitého přiřazení viz §9.2.6.
Příklad: Příklad
class Test { static void Swap(ref int x, ref int y) { int temp = x; x = y; y = temp; } static void Main() { int i = 1, j = 2; Swap(ref i, ref j); Console.WriteLine($"i = {i}, j = {j}"); } }
vytvoří výstup.
i = 2, j = 1
Pro vyvolání
Swap
inMain
,x
představujei
ay
představujej
. Vyvolání tedy má vliv na prohození hodnot ai
j
.end example
Příklad: V následujícím kódu
class A { string s; void F(ref string a, ref string b) { s = "One"; a = "Two"; b = "Three"; } void G() { F(ref s, ref s); } }
vyvolání
F
inG
předá odkaz nas
obojía
ib
. Proto pro toto vyvolání, názvys
,a
ab
všechny odkazují na stejné umístění úložiště a tři přiřazení všechny upravují poles
instance .end example
struct
Pro typ v rámci metody instance se objekt instance (§12.2.1) nebo konstruktor instance s inicializátorem this
konstruktoru chová klíčové slovo přesně jako referenční parametr typu struktury (§12.8.14).
15.6.2.3.4 Výstupní parametry
Parametr deklarovaný s modifikátorem out
je výstupní parametr. Určitá pravidla přiřazení naleznete v §9.2.7.
Metoda deklarovaná jako částečná metoda (§15.6.9) nesmí mít výstupní parametry.
Poznámka: Výstupní parametry se obvykle používají v metodách, které vytvářejí více návratových hodnot. koncová poznámka
Příklad:
class Test { static void SplitPath(string path, out string dir, out string name) { int i = path.Length; while (i > 0) { char ch = path[i - 1]; if (ch == '\\' || ch == '/' || ch == ':') { break; } i--; } dir = path.Substring(0, i); name = path.Substring(i); } static void Main() { string dir, name; SplitPath(@"c:\Windows\System\hello.txt", out dir, out name); Console.WriteLine(dir); Console.WriteLine(name); } }
Příklad vytvoří výstup:
c:\Windows\System\ hello.txt
Všimněte si, že
dir
před předánímname
proměnné aSplitPath
proměnné mohou být nepřiřazeny a že jsou považovány za rozhodně přiřazené po volání.end example
15.6.2.4 Pole parametrů
Parametr deklarovaný pomocí modifikátoru params
je pole parametrů. Pokud seznam parametrů obsahuje pole parametrů, musí být posledním parametrem v seznamu a musí se jednat o jednorozměrný typ pole.
Příklad: Typy
string[]
astring[][]
lze je použít jako typ pole parametrů, ale typstring[,]
nemůže. end example
Poznámka: Modifikátor není možné kombinovat
params
s modifikátoryin
,out
neboref
. koncová poznámka
Pole parametrů umožňuje zadat argumenty jedním ze dvou způsobů volání metody:
- Argumentem zadaným pro pole parametrů může být jediný výraz, který je implicitně konvertibilní (§10.2) na typ pole parametru. V tomto případě pole parametrů funguje přesně jako parametr hodnoty.
- Volání může také zadat nula nebo více argumentů pro pole parametrů, kde každý argument je výraz, který je implicitně konvertibilní (§10.2) na typ prvku pole parametru. V tomto případě vyvolání vytvoří instanci typu pole parametru s délkou odpovídající počtu argumentů, inicializuje prvky instance pole s danými hodnotami argumentu a použije nově vytvořenou instanci matice jako skutečný argument.
S výjimkou povolení proměnného počtu argumentů ve vyvolání je pole parametrů přesně ekvivalentní parametru parametru (§15.6.2.2) stejného typu.
Příklad: Příklad
class Test { static void F(params int[] args) { Console.Write($"Array contains {args.Length} elements:"); foreach (int i in args) { Console.Write($" {i}"); } Console.WriteLine(); } static void Main() { int[] arr = {1, 2, 3}; F(arr); F(10, 20, 30, 40); F(); } }
vytvoří výstup.
Array contains 3 elements: 1 2 3 Array contains 4 elements: 10 20 30 40 Array contains 0 elements:
První vyvolání
F
jednoduše předá polearr
jako 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íF
vytvoří 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[] {});
end example
Při řešení přetížení lze použít metodu s polem parametrů, a to buď v normální podobě, nebo v rozšířené podobě (§12.6.4.2). Rozšířená forma metody je k dispozici pouze v případě, že normální forma metody není použitelná a pouze v případě, že příslušná metoda se stejným podpisem jako rozbalený formulář není již deklarována ve stejném typu.
Příklad: Příklad
class Test { static void F(params object[] a) => Console.WriteLine("F(object[])"); static void F() => Console.WriteLine("F()"); static void F(object a0, object a1) => Console.WriteLine("F(object,object)"); static void Main() { F(); F(1); F(1, 2); F(1, 2, 3); F(1, 2, 3, 4); } }
vytvoří výstup.
F() F(object[]) F(object,object) F(object[]) F(object[])
V příkladu jsou dvě z možných rozšířených forem metody s polem parametrů již zahrnuty do třídy jako běžné metody. Tyto rozšířené formuláře se proto při provádění řešení přetížení nepovažují a první a třetí vyvolání metody tak vyberte běžné metody. Když třída deklaruje metodu s polem parametrů, není neobvyklé zahrnout také některé rozšířené formuláře jako běžné metody. Tímto způsobem je možné zabránit přidělení instance pole, ke kterému dochází při vyvolání rozšířené formy metody s polem parametrů.
end example
Matice je referenční typ, takže hodnota předaná pro pole parametrů může být
null
.Příklad: Příklad:
class Test { static void F(params string[] array) => Console.WriteLine(array == null); static void Main() { F(null); F((string) null); } }
vytvoří výstup:
True False
Druhé vyvolání vznikne
False
tak, jak je ekvivalentníF(new string[] { null })
a předává pole obsahující jediný odkaz s hodnotou null.end example
Pokud je object[]
typ pole parametrů , může dojít k nejednoznačnosti mezi normální formou metody a rozbaleným formulářem pro jeden object
parametr. Důvodem nejednoznačnosti je, že object[]
se sám implicitně konvertibilní na typ object
. Nejednoznačnost ale nemá žádný problém, protože ji lze v případě potřeby vyřešit vložením přetypování.
Příklad: Příklad
class Test { static void F(params object[] args) { foreach (object o in args) { Console.Write(o.GetType().FullName); Console.Write(" "); } Console.WriteLine(); } static void Main() { object[] a = {1, "Hello", 123.456}; object o = a; F(a); F((object)a); F(o); F((object[])o); } }
vytvoří výstup.
System.Int32 System.String System.Double System.Object[] System.Object[] System.Int32 System.String System.Double
V prvním a posledním vyvolání
F
, normální formaF
je 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í formuF
a argument se předá jako normální parametr hodnoty. Ve druhém a třetím vyvolání není normální formaF
použitelná, protože neexistuje žádný implicitní převod z typu argumentu na typ parametru (typobject
nelze implicitně převést na typobject[]
). Rozšířená formaF
je 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[]
).end example
15.6.3 Statické metody a metody instancí
Pokud deklarace metody obsahuje static
modifikátor, říká se, že tato metoda je statickou metodou. Pokud není k dispozici žádný static
modifikátor, říká se, že metoda instance.
Statická metoda nepracuje s konkrétní instancí a jedná se o chybu v době kompilace odkazující na this
statickou metodu.
Metoda instance pracuje s danou instancí třídy a k této instanci lze přistupovat jako this
(§12.8.14).
Rozdíly mezi statickými členy a členy instance jsou popsány dále v §15.3.8.
15.6.4 Virtuální metody
Když deklarace metody instance obsahuje virtuální modifikátor, říká se, že tato metoda je virtuální metoda. Pokud není k dispozici žádný virtuální modifikátor, říká se, že metoda není virtuální metoda.
Implementace jiné než virtuální metody je invariantní: Implementace je stejná, zda metoda je vyvolána na instanci třídy, ve které je deklarována, nebo instance odvozené třídy. Naproti tomu implementace virtuální metody může být nahrazena odvozenými třídami. Proces převedení implementace zděděné virtuální metody se označuje jako přepsání této metody (§15.6.5).
Při vyvolání virtuální metody určuje typ běhu instance, pro kterou probíhá vyvolání, skutečnou implementaci metody, která se má vyvolat. Při vyvolání jiné než virtuální metody je určujícím faktorem typ kompilace instance. Pokud je metoda pojmenovaná N
volána se seznamem A
argumentů v instanci s typem C
kompilace a typem R
za běhu (kde R
je buď C
nebo třída odvozena), C
vyvolání se zpracuje takto:
- V době vazby se rozlišení přetížení použije na , a , vybrat konkrétní metodu
C
ze sady metod deklarovaných a zděděnýchN
.A
M
C
Toto je popsáno v §12.8.10.2. - Pak za běhu:
- Pokud
M
je jiná než virtuální metoda,M
je vyvolána. -
M
V opačném případě je virtuální metoda a je vyvolána nejvíce odvozená implementaceM
s 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
R
obsahuje zavedení virtuální deklaraceM
, pak je to nejvýraznější implementaceM
s ohledem naR
. - Jinak, pokud
R
obsahuje přepsáníM
, pak je to nejvýraznější implementaceM
s ohledem naR
. - V opačném případě je nejvýraznější implementace
M
s ohledem naR
nejvíce odvozenou implementaciM
s 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
A
představuje ne-virtuální metoduF
a virtuální metoduG
. TřídaB
zavá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.G
Vš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.end example
Protože metody mohou skrýt zděděné metody, je možné, aby třída obsahovala několik virtuálních metod se stejným podpisem. To nepředstavuje nejednoznačnost problém, protože všechny, ale nejvíce odvozené metody jsou skryté.
Příklad: V následujícím kódu
class A { public virtual void F() => Console.WriteLine("A.F"); } class B : A { public override void F() => Console.WriteLine("B.F"); } class C : B { public new virtual void F() => Console.WriteLine("C.F"); } class D : C { public override void F() => Console.WriteLine("D.F"); } class Test { static void Main() { D d = new D(); A a = d; B b = d; C c = d; a.F(); b.F(); c.F(); d.F(); } }
třídy
C
D
obsahují dvě virtuální metody se stejným podpisem: jeden zavedenýA
a druhý zaveden .C
Metoda zavedená skrytímC
metody zděděné zA
. Proto přepsání deklarace vD
přepsání metody zavedenéC
, a není možnéD
přepsat metodu zavedenou metodouA
. Příklad vytvoří výstup:B.F B.F D.F D.F
Všimněte si, že je možné vyvolat skrytou virtuální metodu přístupem k instanci
D
prostřednictvím méně odvozeného typu, ve kterém metoda není skrytá.end example
15.6.5 Přepsání metod
Když deklarace metody instance obsahuje override
modifikátor, metoda je řečeno, že je přepsána metoda. Metoda přepsání přepíše zděděnou virtuální metodu se stejným podpisem. Zatímco deklarace virtuální metody zavádí novou metodu, deklarace metody přepsání se specializuje na existující zděděnou virtuální metodu tím, že poskytuje novou implementaci této metody.
Metoda přepsáná deklarací přepsání je známá jako přepsaná základní metoda Pro přepsání metody M
deklarované ve třídě C
, přepsáná základní metoda je určena prozkoumáním každé základní třídy C
, počínaje přímou základní třídou C
a pokračováním s každou následnou přímou základní třídou, dokud v daném typu základní třídy není umístěna alespoň jedna přístupná metoda, která má stejný podpis jako M
po nahrazení argumentů typu. Pro účely vyhledání přepsané základní metody je metoda považována za přístupnou, pokud je public
, pokud je , pokud je protected
, pokud protected internal
je , nebo je , nebo pokud je buď internal
nebo private protected
a deklarována ve stejném programu jako C
.
K chybě v době kompilace dochází, pokud nejsou splněny všechny následující podmínky pro deklaraci přepsání:
- Přepsanou základní metodu lze najít, jak je popsáno výše.
- Existuje přesně jedna taková přepsaná základní metoda. Toto omezení platí pouze v případě, že typ základní třídy je konstruovaný typ, kde nahrazení argumentů typu vytváří podpis dvou metod stejně.
- Přepsaná základní metoda je virtuální, abstraktní nebo přepsáná metoda. Jinými slovy, přepsaná základní metoda nemůže být statická nebo ne virtuální.
- Přepsaná základní metoda není zapečetěná metoda.
- Mezi návratový typ přepsané základní metody a metodou přepsání existuje převod identity.
- Deklarace přepsání a přepsaná základní metoda mají stejnou deklarovanou přístupnost. Jinými slovy, deklarace přepsání nemůže změnit přístupnost virtuální metody. Je-li však přepsaná základní metoda chráněna vnitřní a je deklarována v jiném sestavení než sestavení obsahující deklaraci přepsání, bude deklarována deklarace přepsání přístupnosti chráněna.
- Deklarace přepsání nezadá žádné type_parameter_constraints_clauses. Místo toho jsou omezení zděděna z přepsáné základní metody. Omezení, která jsou parametry typu v přepsáné metodě mohou být nahrazeny argumenty typu v zděděné omezení. To může vést k omezením, která nejsou platná při explicitně zadaném typu, jako jsou typy hodnot nebo zapečetěné typy.
Příklad: Následující příklad ukazuje, jak přepsání pravidel funguje pro obecné třídy:
abstract class C<T> { public virtual T F() {...} public virtual C<T> G() {...} public virtual void H(C<T> x) {...} } class D : C<string> { public override string F() {...} // Ok public override C<string> G() {...} // Ok public override void H(C<T> x) {...} // Error, should be C<string> } class E<T,U> : C<U> { public override U F() {...} // Ok public override C<U> G() {...} // Ok public override void H(C<T> x) {...} // Error, should be C<U> }
end example
Přepsání deklarace může přistupovat k přepisované základní metodě pomocí base_access (§12.8.15).
Příklad: V následujícím kódu
class A { int x; public virtual void PrintFields() => Console.WriteLine($"x = {x}"); } class B : A { int y; public override void PrintFields() { base.PrintFields(); Console.WriteLine($"y = {y}"); } }
base.PrintFields()
vyvolání vyvoláB
metodu PrintFields deklarovanou vA
. Base_access zakáže virtuální mechanismus vyvolání a jednoduše považuje základní metodu za metodu bezvirtual
metody. Bylo-li vyvolání zapsánoB
, by rekurzivně vyvolá metodu((A)this).PrintFields()
deklarovanou vPrintFields
, nikoli vB
, protožeA
je virtuální a typPrintFields
běhu je((A)this)
.B
end example
Pouze zahrnutím modifikátoru override
může metoda přepsat jinou metodu. Ve všech ostatních případech metoda se stejným podpisem jako zděděná metoda jednoduše skryje zděděnou metodu.
Příklad: V následujícím kódu
class A { public virtual void F() {} } class B : A { public virtual void F() {} // Warning, hiding inherited F() }
metoda
F
B
neobsahujeoverride
modifikátor, a proto nepřepíše metoduF
vA
. Metoda vF
skrytí metody vB
a upozornění je hlášena,A
protože deklarace neobsahuje nový modifikátor.end example
Příklad: V následujícím kódu
class A { public virtual void F() {} } class B : A { private new void F() {} // Hides A.F within body of B } class C : B { public override void F() {} // Ok, overrides A.F }
metoda
F
vB
skrytí virtuálníF
metody zděděné zA
. Vzhledem k tomu, že novýF
vB
má soukromý přístup, jeho rozsah zahrnuje pouze těloB
třídy a neprovádí se naC
.F
Deklarace inC
je proto povolena k přepsáníF
zděděného zděděného zA
.end example
15.6.6 Zapečetěné metody
Když deklarace metody instance obsahuje sealed
modifikátor, tato metoda se říká, že je zapečetěná metoda. Zapečetěná metoda přepíše zděděnou virtuální metodu se stejným podpisem. Zapečetěná metoda musí být také označena modifikátorem override
. Použití modifikátoru sealed
zabraňuje odvozené třídě v dalším přepsání metody.
Příklad: Příklad
class A { public virtual void F() => Console.WriteLine("A.F"); public virtual void G() => Console.WriteLine("A.G"); } class B : A { public sealed override void F() => Console.WriteLine("B.F"); public override void G() => Console.WriteLine("B.G"); } class C : B { public override void G() => Console.WriteLine("C.G"); }
třída
B
poskytuje dvě metody přepsání:F
metoda, která másealed
modifikátor a metoduG
, která není.B
použití modifikátorusealed
zabraňujeC
dalšímu přepsáníF
.end example
15.6.7 Abstraktní metody
Když deklarace metody instance obsahuje abstract
modifikátor, tato metoda je řečeno, že je abstraktní metoda. I když abstraktní metoda je implicitně také virtuální metoda, nemůže mít modifikátor virtual
.
Deklarace abstraktní metody zavádí novou virtuální metodu, ale neposkytuje implementaci této metody. Místo toho jsou k poskytnutí vlastní implementace vyžadovány jiné než abstraktní odvozené třídy přepsáním této metody. Vzhledem k tomu, že abstraktní metoda neposkytuje žádnou skutečnou implementaci, tělo metody abstraktní metody se jednoduše skládá z středníku.
Deklarace abstraktní metody jsou povoleny pouze v abstraktních třídách (§15.2.2.2).
Příklad: V následujícím kódu
public abstract class Shape { public abstract void Paint(Graphics g, Rectangle r); } public class Ellipse : Shape { public override void Paint(Graphics g, Rectangle r) => g.DrawEllipse(r); } public class Box : Shape { public override void Paint(Graphics g, Rectangle r) => g.DrawRect(r); }
třída
Shape
definuje abstraktní pojem geometrického objektu obrazce, který může malovat sám. MetodaPaint
je abstraktní, protože neexistuje smysluplná výchozí implementace.Ellipse
ABox
třídy jsou konkrétníShape
implementace. Vzhledem k tomu, že tyto třídy nejsou abstraktní, jsou vyžadovány k přepsáníPaint
metody a poskytnutí skutečné implementace.end example
Jedná se o chybu v době kompilace pro base_access (§12.8.15) odkazující na abstraktní metodu.
Příklad: V následujícím kódu
abstract class A { public abstract void F(); } class B : A { // Error, base.F is abstract public override void F() => base.F(); }
Pro vyvolání se zobrazí
base.F()
chyba v době kompilace, protože odkazuje na abstraktní metodu.end example
Deklarace abstraktní metody je povolena k přepsání virtuální metody. To umožňuje abstraktní třídě vynutit opětovné implementaci metody v odvozených třídách a znepřístupňuje původní implementaci metody.
Příklad: V následujícím kódu
class A { public virtual void F() => Console.WriteLine("A.F"); } abstract class B: A { public abstract override void F(); } class C : B { public override void F() => Console.WriteLine("C.F"); }
Třída
A
deklaruje virtuální metodu, třídaB
přepíše tuto metodu abstraktní metodou a třídaC
přepíše abstraktní metodu tak, aby poskytovala vlastní implementaci.end example
15.6.8 Externí metody
Pokud deklarace metody obsahuje extern
modifikátor, metoda se říká, že se jedná o externí metodu. Externí metody se implementují externě, obvykle používají jiný jazyk než C#. Vzhledem k tomu, že deklarace externí metody neposkytuje žádnou skutečnou implementaci, tělo metody externí metody se jednoduše skládá ze středníku. Externí metoda nesmí být obecná.
Mechanismus, kterým je dosaženo propojení s externí metodou, závisí na implementaci.
Příklad: Následující příklad ukazuje použití
extern
modifikátoru a 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); }
end example
15.6.9 Částečné metody
Pokud deklarace metody obsahuje partial
modifikátor, říká se, že tato metoda je částečnou metodou. Částečné metody lze deklarovat pouze jako členy částečných typů (§15.2.7) a podléhají řadě omezení.
Částečné metody mohou být definovány v jedné části deklarace typu a implementovány v jiné. Implementace je nepovinná; Pokud žádná část neimplementuje částečnou metodu, deklarace částečné metody a všechna volání jsou odebrána z deklarace typu, která je výsledkem kombinace částí.
Částečné metody nedefinují modifikátory přístupu; jsou implicitně soukromé. Jejich návratový typ musí být void
a jejich parametry nesmí být výstupními parametry. Částečný identifikátor se rozpozná jako kontextové klíčové slovo (§6.4.4) v deklaraci metody pouze v případě, že se zobrazí bezprostředně před klíčovým slovem void
. Částečná metoda nemůže explicitně implementovat metody rozhraní.
Existují dva druhy deklarací částečné metody: Pokud tělo deklarace metody je středník, deklarace se říká, že se jedná o definici částečné deklarace metody. Pokud je tělo jiné než středník, deklarace se říká, že se jedná o implementaci částečné deklarace metody. V částech deklarace typu může existovat pouze jedna definující částečná deklarace metody s daným podpisem a může existovat pouze jedna implementace částečné deklarace metody s daným podpisem. Je-li zadána prováděcí deklarace částečné metody, musí existovat odpovídající definice částečné deklarace metody a deklarace se shodují, jak je uvedeno v následujících:
- Deklarace mají stejné modifikátory (i když nemusí nutně ve stejném pořadí), název metody, počet parametrů typu a počet parametrů.
- Odpovídající parametry v deklaracích mají stejné modifikátory (i když nemusí nutně ve stejném pořadí) a stejné typy nebo typy konvertibilních identit (rozdíly moduluo v názvech parametrů typů).
- Odpovídající parametry typu v deklarací mají stejná omezení (rozdíly moduluo v názvech parametrů typu).
Implementace částečné deklarace metody se může objevit ve stejné části jako odpovídající definice částečné deklarace metody.
Pouze definice částečné metody se účastní řešení přetížení. Proto zda je uvedena implementační deklarace, vyvolání výrazy mohou přeložit na vyvolání částečné metody. Vzhledem k tomu, že částečná metoda vždy vrátí void
, takové vyvolání výrazy budou vždy výrazy příkazy. Vzhledem k tomu, že částečná metoda je implicitně private
, takové příkazy budou vždy probíhat v jedné části deklarace typu, ve které je částečná metoda deklarována.
Poznámka: Definice odpovídající definice definování a implementace deklarací částečné metody nevyžaduje, aby se názvy parametrů shodovaly. To může vést k překvapivým, i když dobře definovaným chováním při použití pojmenovaných argumentů (§12.6.2.1). Například při definování částečné deklarace metody pro
M
v jednom souboru a implementaci částečné deklarace metody v jiném souboru:// File P1.cs: partial class P { static partial void M(int x); } // File P2.cs: partial class P { static void Caller() => M(y: 0); static partial void M(int y) {} }
je neplatný , protože vyvolání používá název argumentu z implementace a nikoli definici částečné deklarace metody.
koncová poznámka
Pokud žádná část deklarace částečného typu neobsahuje implementovanou deklaraci pro danou částečnou metodu, jakýkoli příkaz výrazu, který jej vyvolá, je jednoduše odebrán z kombinované deklarace typu. Výraz vyvolání, včetně jakýchkoli dílčích výrazů, tedy nemá žádný účinek za běhu. Samotná částečná metoda je také odstraněna a nebude členem kombinované deklarace typu.
Pokud pro danou částečnou metodu existuje implementační deklarace, zachovají se vyvolání částečných metod. Částečná metoda vede k deklaraci metody podobné implementaci částečné deklarace metody s výjimkou následujících:
Modifikátor
partial
není zahrnut.Atributy ve výsledné deklaraci metody jsou kombinované atributy definice a implementace částečné deklarace metody v nezadaném pořadí. Duplicitní položky se neodeberou.
Atributy parametrů výsledné deklarace metody jsou kombinované atributy odpovídajících parametrů definice a implementace částečné deklarace metody v nezadaném pořadí. Duplicitní položky se neodeberou.
Pokud je pro částečnou metodu M
uvedena definice deklarace, ale nikoli prováděcí deklarace, platí následující omezení:
Jedná se o chybu v době kompilace pro vytvoření delegáta (
M
§12.8.17.6).Jedná se o chybu v době kompilace odkazující na
M
uvnitř anonymní funkce, která je převedena na typ stromu výrazu (§8.6).Výrazy, ke kterým dochází jako součást vyvolání
M
, nemají vliv na určitý stav přiřazení (§9.4), což může potenciálně vést k chybám v době kompilace.M
nemůže být vstupním bodem žádosti (§7.1).
Částečné metody jsou užitečné pro povolení jedné části deklarace typu přizpůsobit chování jiné části, například jednu, která je generována nástrojem. Zvažte následující částečnou deklaraci třídy:
partial class Customer
{
string name;
public string Name
{
get => name;
set
{
OnNameChanging(value);
name = value;
OnNameChanged();
}
}
partial void OnNameChanging(string newName);
partial void OnNameChanged();
}
Pokud je tato třída zkompilována bez jakýchkoli jiných částí, definice částečné deklarace metody a jejich vyvolání budou odebrány a výsledná kombinovaná deklarace třídy bude ekvivalentní následujícímu:
class Customer
{
string name;
public string Name
{
get => name;
set => name = value;
}
}
Předpokládejme však, že je uvedena další část, která poskytuje prováděcí deklarace částečných metod:
partial class Customer
{
partial void OnNameChanging(string newName) =>
Console.WriteLine($"Changing {name} to {newName}");
partial void OnNameChanged() =>
Console.WriteLine($"Changed to {name}");
}
Výsledná kombinovaná deklarace třídy pak bude ekvivalentní následujícímu:
class Customer
{
string name;
public string Name
{
get => name;
set
{
OnNameChanging(value);
name = value;
OnNameChanged();
}
}
void OnNameChanging(string newName) =>
Console.WriteLine($"Changing {name} to {newName}");
void OnNameChanged() =>
Console.WriteLine($"Changed to {name}");
}
15.6.10 Rozšiřující metody
Když první parametr metody obsahuje this
modifikátor, tato metoda se říká, že se jedná o rozšiřující metodu. Rozšiřující metody se deklarují pouze v ne generických statických třídách, které nejsou vnořené. První parametr metody rozšíření je omezen následujícím způsobem:
- Může se jednat pouze o vstupní parametr, pokud má typ hodnoty.
- Parametr odkazu může být pouze v případě, že má typ hodnoty nebo má obecný typ omezený na strukturu.
- Nesmí se jednat o typ ukazatele.
Příklad: Následuje příklad statické třídy, která deklaruje dvě rozšiřující metody:
public static class Extensions { public static int ToInt32(this string s) => Int32.Parse(s); public static T[] Slice<T>(this T[] source, int index, int count) { if (index < 0 || count < 0 || source.Length - index < count) { throw new ArgumentException(); } T[] result = new T[count]; Array.Copy(source, index, result, 0, count); return result; } }
end example
Rozšiřující metoda je běžná statická metoda. Kromě toho, pokud je její uzavření statické třídy v oboru, lze metodu rozšíření vyvolat pomocí syntaxe vyvolání metody instance (§12.8.10.3) pomocí výrazu příjemce jako prvního argumentu.
Příklad: Následující program používá metody rozšíření deklarované výše:
static class Program { static void Main() { string[] strings = { "1", "22", "333", "4444" }; foreach (string s in strings.Slice(1, 2)) { Console.WriteLine(s.ToInt32()); } } }
Metoda
Slice
je k dispozici nastring[]
aToInt32
metoda 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)); } } }
end example
Tělo metody 15.6.11
Tělo metody deklarace metody se skládá z těla bloku, těla výrazu nebo středníku.
Abstraktní a externí deklarace metody neposkytují implementaci metody, takže jejich těla metody se jednoduše skládají z středníku. Pro jakoukoli jinou metodu je tělo metody blok (§13.3), který obsahuje příkazy, které se mají provést při vyvolání této metody.
Účinný návratový typ metody jevoid
, pokud je void
návratový typ , nebo je-li metoda asynchronní a návratový typ je «TaskType»
(§15.15.1). V opačném případě je účinný návratový typ nesynchronní metody jeho návratovým typem a účinný návratový typ asynchronní metody s návratovým typem «TaskType»<T>
(§15.15.1) je T
.
Je-li účinný návratový typ metody void
a metoda má blokový orgán, return
prohlášení (§13.10.5) v bloku nezadá výraz. Pokud se provádění bloku metody void dokončí normálně (tj. řízení toků mimo konec těla metody), tato metoda se jednoduše vrátí do volajícího.
Je-li účinný návratový typ metody void
a metoda má tělo výrazu, výraz E
musí být statement_expression a tělo je přesně ekvivalentní blokové tělu formuláře { E; }
.
Pro metodu return-by-value (§15.6.1) musí každý návratový příkaz v těle této metody zadat výraz, který je implicitně konvertibilní na efektivní návratový typ.
Pro metodu return-by-ref (§15.6.1) musí každý návratový příkaz v těle této metody určit výraz, jehož typem je efektivní návratový typ, a má odkazově bezpečný kontext kontextu volajícího (§9.7.2).
V případě metod vrácení po hodnotě a návratových metod není koncový bod těla metody dosažitelný. Jinými slovy, ovládací prvek není povolen, aby tok z konce těla metody.
Příklad: V následujícím kódu
class A { public int F() {} // Error, return value required public int G() { return 1; } public int H(bool b) { if (b) { return 1; } else { return 0; } } public int I(bool b) => b ? 1 : 0; }
Výsledkem metody vracející
F
hodnotu je chyba v době kompilace, protože řízení může dojít mimo konec těla metody.G
AH
metody jsou správné, protože všechny možné cesty provádění končí v návratovém příkazu, který určuje návratovou hodnotu. MetodaI
je správná, protože její tělo je ekvivalentní bloku pouze s jedním návratovým příkazem v něm.end example
15.7 Možností ubytování
15.7.1 Obecné
Vlastnost je člen, který poskytuje přístup k vlastnosti objektu nebo třídy. Mezi příklady vlastností patří délka řetězce, velikost písma, titulek okna a jméno zákazníka. Vlastnosti jsou přirozené rozšíření polí – oba jsou pojmenované členy s přidruženými typy a syntaxe pro přístup k polím a vlastnostem je stejná. Na rozdíl od polí však vlastnosti neoznačí umístění úložiště. Místo toho mají vlastnosti přístupové objekty , které určují příkazy, které mají být provedeny při čtení nebo zápisu jejich hodnot. Vlastnosti tak poskytují mechanismus pro přidružení akcí ke čtení a zápisu vlastností objektu nebo třídy; navíc umožňují, aby se tyto charakteristiky počítaly.
Vlastnosti jsou deklarovány pomocí property_declarations:
property_declaration
: attributes? property_modifier* type member_name property_body
| attributes? property_modifier* ref_kind type member_name ref_property_body
;
property_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
property_body
: '{' accessor_declarations '}' property_initializer?
| '=>' expression ';'
;
property_initializer
: '=' variable_initializer ';'
;
ref_property_body
: '{' ref_get_accessor_declaration '}'
| '=>' 'ref' variable_reference ';'
;
unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
Existují dva druhy property_declaration:
- První deklaruje nehodnocenou vlastnost. Jeho hodnota má typ typu. Tento druh vlastnosti může být čitelný nebo zapisovatelný.
- Druhá deklaruje vlastnost ref-valued. Jeho hodnota je variable_reference (§9,5 Tento druh vlastnosti je jen čitelný.
Property_declaration může obsahovat sadu atributů (§22) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new
(§15.3.5), static
(§15.7.2virtual
, §15.7.6), (override
, §15.7.6), (sealed
), (abstract
, §15.7.6) a (extern
) modifikátory.
Deklarace vlastností podléhají stejným pravidlům jako deklarace metody (§15.6) s ohledem na platné kombinace modifikátorů.
Member_name (§15.6.1) určuje název nemovitosti. Pokud není vlastnost explicitní implementací člena rozhraní, member_name je jednoduše identifikátor. Pro explicitní implementaci člena rozhraní (§18.6.2) se member_name skládá z interface_type následovaného ".
" a identifikátorem.
Typ nemovitosti musí být alespoň tak přístupný jako vlastní majetek (§7.5.5).
Property_body se může skládat z textu příkazu nebo textu výrazu. V prohlášení , accessor_declarations, který musí být uzavřen v "{
" a "}
" tokeny, deklarujte příslušenství (§15.7.3) nemovitosti. Přístupové objekty určují spustitelné příkazy přidružené ke čtení a zápisu vlastnosti.
V property_body tělo výrazu sestávající z následovaného =>
výrazem E
a středník je přesně ekvivalentní textu příkazu { get { return E; } }
, a proto lze použít pouze k určení vlastností jen pro čtení, kde výsledek přístupového objektu get je dán jediným výrazem.
Property_initializer lze poskytnout pouze pro automaticky implementovanou vlastnost (§15.7.4) a způsobí inicializaci podkladového pole těchto vlastností s hodnotou danou výrazem.
Ref_property_body se může skládat z těla příkazu nebo textu výrazu. V prohlášení orgán get_accessor_declaration deklaruje get příslušenství (§15.7.3) nemovitosti. Přístupové objekty určují spustitelné příkazy přidružené ke čtení vlastnosti.
V ref_property_body
Poznámka: I když syntaxe pro přístup k vlastnosti je stejná jako u pole, vlastnost není klasifikována jako proměnná. Nelze tedy předat vlastnost jako
in
,out
neboref
argument, pokud je vlastnost ref-valued, a proto vrátí proměnnou odkaz (§9.7). koncová poznámka
Pokud deklarace vlastnosti obsahuje extern
modifikátor, vlastnost je řečeno, že je externí vlastností. Vzhledem k tomu, že prohlášení o vnější vlastnosti neposkytuje žádnou skutečnou implementaci, musí být každý z accessor_bodyv jeho accessor_declarations středníkem.
15.7.2 Statické vlastnosti a vlastnosti instance
Pokud deklarace vlastnosti obsahuje static
modifikátor, vlastnost je řečeno, že je statická vlastnost. Pokud není k dispozici žádný static
modifikátor, vlastnost se říká, že je to vlastnost instance.
Statická vlastnost není přidružená ke konkrétní instanci a jedná se o chybu v době kompilace odkazující na this
přístupové objekty statické vlastnosti.
Vlastnost instance je přidružena k dané instanci třídy a k této instanci lze přistupovat jako this
(§12.8.14) v přistupujícími objekty této vlastnosti.
Rozdíly mezi statickými členy a členy instance jsou popsány dále v §15.3.8.
15.7.3 Příslušenství
Poznámka: Tato klauzule se vztahuje na obě vlastnosti (§15.7) a indexery (§15.9). Klauzule se zapisuje z hlediska vlastností, při čtení indexerů nahradí indexer/indexery vlastnost/vlastnosti a prohlédnou seznam rozdílů mezi vlastnostmi a indexery zadanými v §15.9.2. koncová poznámka
Accessor_declarations vlastnosti určují spustitelné příkazy přidružené k zápisu nebo čtení této vlastnosti.
accessor_declarations
: get_accessor_declaration set_accessor_declaration?
| set_accessor_declaration get_accessor_declaration?
;
get_accessor_declaration
: attributes? accessor_modifier? 'get' accessor_body
;
set_accessor_declaration
: attributes? accessor_modifier? 'set' accessor_body
;
accessor_modifier
: 'protected'
| 'internal'
| 'private'
| 'protected' 'internal'
| 'internal' 'protected'
| 'protected' 'private'
| 'private' 'protected'
;
accessor_body
: block
| '=>' expression ';'
| ';'
;
ref_get_accessor_declaration
: attributes? accessor_modifier? 'get' ref_accessor_body
;
ref_accessor_body
: block
| '=>' 'ref' variable_reference ';'
| ';'
;
Accessor_declarations se skládají z get_accessor_declaration, set_accessor_declaration nebo obojího. Každá deklarace přístupového objektu se skládá z volitelných atributů, volitelného accessor_modifier, tokenu nebo get
, set
následovaného accessor_body.
Pro ref-valued vlastnost ref_get_accessor_declaration skládá volitelné atributy, volitelné accessor_modifier, token get
následovaný ref_accessor_body.
Použití accessor_modifierse řídí následujícími omezeními:
- Accessor_modifier se nepoužije v rozhraní ani v explicitní implementaci člena rozhraní.
- U vlastnosti nebo indexeru, který nemá žádný
override
modifikátor, je accessor_modifier povolen pouze v případě, že vlastnost nebo indexer má přístupové objekty get a set, a pak je povolen pouze na jednom z těchto přístupových objektů. - U vlastnosti nebo indexeru
override
, který obsahuje modifikátor, musí přistupovací objekt odpovídat accessor_modifier, pokud existuje, přepsaného přístupového objektu. - Accessor_modifier deklaruje přístupnost, která je přísně omezenější než deklarovaná přístupnost vlastnosti nebo samotného indexeru. Přesné:
- Pokud má vlastnost nebo indexer deklarovanou přístupnost
public
, může být přístupnost deklarovanáprivate protected
buď , ,protected internal
,internal
,protected
neboprivate
. - Pokud má vlastnost nebo indexer deklarovanou přístupnost
protected internal
, může být přístupnost deklarovanáprivate protected
buď , ,protected private
,internal
,protected
neboprivate
. - Pokud má vlastnost nebo indexer deklarovanou přístupnost
internal
protected
nebo , musí být přístupnost deklarovaná accessor_modifier buďprivate protected
neboprivate
. - 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 extern
ref_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 } }
end example
Subjekt get accessor pro vlastnost s ref-hodnotou musí odpovídat pravidlům pro metody ref-valued popsané v §15.6.11.
Objekt set odpovídá metodě s jedním parametrem hodnoty typu vlastnosti a návratovým typem void
. Implicitní parametr přístupového objektu sady je vždy pojmenován value
. Pokud je vlastnost odkazována jako cíl přiřazení (§12.21), nebo jako operand nebo ++
–-
(§12.8.16, §12.9.6), je přistupující sada vyvolána argumentem, který poskytuje novou hodnotu (§12.21.2). Subjekt množiny musí odpovídat pravidlům pro void
metody popsané v §15.6.11. Zejména návratové příkazy v těle objektu set nejsou povoleny k určení výrazu. Vzhledem k tomu, že objekt set accessor má implicitně pojmenovaný value
parametr , jedná se o chybu v době kompilace pro místní proměnnou nebo deklaraci konstanty v přístupovém objektu sady, aby měl tento název.
Na základě přítomnosti nebo nepřítomnosti přístupových objektů get a set se vlastnost klasifikuje takto:
- Vlastnost, která zahrnuje přístupové objekty get i objekt set, se říká, že se jedná o vlastnost pro čtení i zápis.
- Vlastnost, která má pouze přístupové objekty get, se říká, že se jedná o vlastnost jen pro čtení. Jedná se o chybu v době kompilace, kdy vlastnost jen pro čtení představuje cíl přiřazení.
- Vlastnost, která má pouze objekt set, je řečeno, že je jen pro zápis vlastnost. S výjimkou cíle přiřazení se jedná o chybu v době kompilace odkazování na vlastnost jen pro zápis ve výrazu.
Poznámka: U vlastností jen pro zápis nelze použít operátory pre- a postfix
++
a operátory a--
složené operátory přiřazení, protože tyto operátory před zápisem nového operandu čtou starou hodnotu svého operandu. koncová poznámka
Příklad: V následujícím kódu
public class Button : Control { private string caption; public string Caption { get => caption; set { if (caption != value) { caption = value; Repaint(); } } } public override void Paint(Graphics g, Rectangle r) { // Painting code goes here } }
ovládací
Button
prvek deklaruje veřejnouCaption
vlastnost. Get accessor of the Caption vlastnost vrátístring
uložené v privátnímcaption
poli. Objekt set zkontroluje, jestli se nová hodnota liší od aktuální hodnoty, a pokud ano, uloží novou hodnotu a přeformátuje ovládací prvek. Vlastnosti často následují podle výše uvedeného vzoru: Get Accessor jednoduše vrátí hodnotu uloženouprivate
v poli a objekt set změní totoprivate
pole a pak provede všechny další akce potřebné k aktualizaci stavu objektu. Výše uvedenáButton
třída představuje příklad použitíCaption
vlastnosti:Button okButton = new Button(); okButton.Caption = "OK"; // Invokes set accessor string s = okButton.Caption; // Invokes get accessor
V této části je objekt set vyvolán přiřazením hodnoty vlastnosti a get accessor je vyvolán odkazem na vlastnost ve výrazu.
end example
Přístupové objekty get a set vlastnosti nejsou jedinečné členy a není možné deklarovat přístupové objekty vlastnosti samostatně.
Příklad: Příklad
class A { private string name; // Error, duplicate member name public string Name { get => name; } // Error, duplicate member name public string Name { set => name = value; } }
deklaruje jednu vlastnost pro čtení i zápis. Místo toho deklaruje dvě vlastnosti se stejným názvem, jednou jen pro čtení a jen pro zápis. Vzhledem k tomu, že dva členy deklarované ve stejné třídě nemohou mít stejný název, způsobí to, že v příkladu dojde k chybě kompilace.
end example
Když odvozená třída deklaruje vlastnost se stejným názvem jako zděděná vlastnost, odvozená vlastnost skryje zděděnou vlastnost s ohledem na čtení i zápis.
Příklad: V následujícím kódu
class A { public int P { set {...} } } class B : A { public new int P { get {...} } }
vlastnost
P
B
skryjeP
vlastnostA
v 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.P
přiřazení, které
b.P
způsobí nahlášenou chybu v době kompilace, protože vlastnostP
jen proB
čtení skryje vlastnost jenP
pro zápis vA
. Všimněte si však, že přetypování lze použít pro přístup ke skrytéP
vlastnosti.end example
Na rozdíl od veřejných polí poskytují vlastnosti oddělení mezi interním stavem objektu a jeho veřejným rozhraním.
Příklad: Představte si následující kód, který používá
Point
strukturu k reprezentaci umístění:class Label { private int x, y; private string caption; public Label(int x, int y, string caption) { this.x = x; this.y = y; this.caption = caption; } public int X => x; public int Y => y; public Point Location => new Point(x, y); public string Caption => caption; }
Label
V této třídě se používají dvěint
polex
ay
k uložení jeho umístění. Umístění je veřejně vystaveno jak jako vlastnostX
Y
, tak jakoLocation
vlastnost typuPoint
. Pokud se v budoucí verziLabel
, stává se pohodlnější uložit umístění jakoPoint
interně, může být změna provedena, aniž by to mělo vliv na veřejné rozhraní třídy:class Label { private Point location; private string caption; public Label(int x, int y, string caption) { this.location = new Point(x, y); this.caption = caption; } public int X => location.X; public int Y => location.Y; public Point Location => location; public string Caption => caption; }
Kdyby
x
ay
místo toho bylapublic readonly
pole, bylo by nemožné provést takovou změnuLabel
třídy.end example
Poznámka: Zveřejnění stavu prostřednictvím vlastností nemusí nutně být méně efektivní než přímé vystavení polí. Konkrétně platí, že pokud vlastnost není virtuální a obsahuje pouze malé množství kódu, může spouštěcí prostředí nahradit volání přístupových objektů skutečným kódem přístupových objektů. Tento proces se označuje jako vkládání a umožňuje přístup k vlastnostem jako efektivní jako přístup k polím, ale zachovává větší flexibilitu vlastností. koncová poznámka
Příklad: Vzhledem k tomu, že vyvolání přístupového objektu get je koncepčně ekvivalentní čtení hodnoty pole, považuje se za špatný programovací styl pro získání přístupových objektů, které mají pozorovatelné vedlejší účinky. V příkladu
class Counter { private int next; public int Next => next++; }
hodnota
Next
vlastnosti závisí na tom, kolikrát byla vlastnost dříve přístupná. Proto přístup k vlastnosti vytváří pozorovatelný vedlejší účinek a vlastnost by měla být implementována jako metoda.Konvence "žádné vedlejší účinky" pro přístupové objekty get neznamená, že přístupové objekty get by měly být vždy zapsány jednoduše za účelem vrácení hodnot uložených v polích. Získání přístupových objektů často vypočítá hodnotu vlastnosti přístupem k více polím nebo vyvoláním metod. Správně navržený přístupový objekt get však neprovádí žádné akce, které způsobují pozorovatelné změny ve stavu objektu.
end example
Vlastnosti lze použít ke zpoždění inicializace prostředku do okamžiku, kdy se na něj poprvé odkazuje.
Příklad:
public class Console { private static TextReader reader; private static TextWriter writer; private static TextWriter error; public static TextReader In { get { if (reader == null) { reader = new StreamReader(Console.OpenStandardInput()); } return reader; } } public static TextWriter Out { get { if (writer == null) { writer = new StreamWriter(Console.OpenStandardOutput()); } return writer; } } public static TextWriter Error { get { if (error == null) { error = new StreamWriter(Console.OpenStandardError()); } return error; } } ... }
Třída
Console
obsahuje tři vlastnosti, ,In
Out
aError
, které představují standardní vstup, výstup a chybové zařízení, v uvedeném pořadí. Zveřejněním těchto členů jako vlastností může třída zpozdit jejich inicializaci,Console
dokud se skutečně nepoužívají. Například při prvním odkazování naOut
vlastnost, jako vConsole.Out.WriteLine("hello, world");
vytvoří se podklad
TextWriter
pro výstupní zařízení. Pokud však aplikace nebude odkazovat na vlastnostiIn
aError
vlastnosti, nebudou se pro tato zařízení vytvářet žádné objekty.end example
15.7.4 Automaticky implementované vlastnosti
Automaticky implementovaná vlastnost (nebo automatická vlastnost pro short) je ne abstraktní, non-extern, non-ref-valued vlastnost s středníkem pouze accessor_bodys. Automatické vlastnosti musí mít přístupové objekty get a mohou mít volitelně nastavené příslušenství.
Pokud je vlastnost zadána jako automaticky implementovaná vlastnost, skryté backing pole je automaticky k dispozici pro vlastnost a přistupující objekty jsou implementovány pro čtení a zápis do daného záložního pole. Skryté záložní pole je nepřístupné, může být přečteno a zapsáno pouze prostřednictvím automaticky implementovaných přístupových objektů vlastností, a to i v rámci obsahujícího typu. Pokud vlastnost auto-property nemá žádné nastavení příslušenství, je považováno za záložní pole readonly
(§15.5.3). Stejně jako readonly
pole může být v těle konstruktoru nadřazené třídy přiřazena také automatická vlastnost jen pro čtení. Takové přiřazení přiřadí přímo backingové pole vlastnosti jen pro čtení.
Automatická vlastnost může mít volitelně property_initializer, která se použije přímo na zadní pole jako variable_initializer (§17.7).
Příklad:
public class Point { public int X { get; set; } // Automatically implemented public int Y { get; set; } // Automatically implemented }
je ekvivalentní následující deklaraci:
public class Point { private int x; private int y; public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } }
end example
Příklad: V následujícím příkladu
public class ReadOnlyPoint { public int X { get; } public int Y { get; } public ReadOnlyPoint(int x, int y) { X = x; Y = y; } }
je ekvivalentní následující deklaraci:
public class ReadOnlyPoint { private readonly int __x; private readonly int __y; public int X { get { return __x; } } public int Y { get { return __y; } } public ReadOnlyPoint(int x, int y) { __x = x; __y = y; } }
Přiřazení k poli jen pro čtení jsou platná, protože se vyskytují v rámci konstruktoru.
end example
I když je backingové pole skryté, toto pole může obsahovat atributy cílené na pole přímo na něj prostřednictvím automaticky implementovaného majetku property_declaration (§15.7.1).
Příklad: Následující kód
[Serializable] public class Foo { [field: NonSerialized] public string MySecret { get; set; } }
výsledkem je použití atributu
NonSerialized
cíleného na pole kompilátoru generovaného backingem, jako by kód byl napsán takto:[Serializable] public class Foo { [NonSerialized] private string _mySecretBackingField; public string MySecret { get { return _mySecretBackingField; } set { _mySecretBackingField = value; } } }
end example
15.7.5 Přístupnost
Pokud má příslušenství accessor_modifier, určí se doména přístupnosti (§7.5.3) přístupového objektu pomocí deklarované přístupnosti accessor_modifier. Pokud příslušenství nemá accessor_modifier, je doména přístupnosti přístupového objektu určena z deklarované přístupnosti vlastnosti nebo indexeru.
Přítomnost accessor_modifier nikdy neovlivní vyhledávání členů (§12.5) nebo řešení přetížení (§12.6.4). Modifikátory vlastnosti nebo indexeru vždy určují, ke které vlastnosti nebo indexeru je vázána bez ohledu na kontext přístupu.
Jakmile je vybrána konkrétní nehodnocená vlastnost nebo indexer bez hodnoty ref, použijí se domény přístupnosti příslušných přístupových objektů k určení, jestli je toto použití platné:
- Je-li použití jako hodnota (§12.2.2), musí být přístupový objekt get k dispozici a přístupný.
- Pokud je použití jako cíl jednoduchého přiřazení (§12.21.2), musí existovat a být přístupný.
- Je-li použití jako cíl složeného přiřazení (§12.21.4) nebo jako cíl
++
operátorů (--
§12.8.16, §12.9.6), musí existovat a být přístupný.
Příklad: V následujícím příkladu je vlastnost skryta vlastností
A.Text
B.Text
, a to i v kontextech, kde je volána pouze sada přístup. Naproti tomu vlastnostB.Count
není 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 } }
end example
Jakmile je vybrána konkrétní vlastnost ref-valued nebo indexer ref-valued – ať už je použití jako hodnota, cíl jednoduchého přiřazení nebo cíle složeného přiřazení – doména přístupnosti příslušného přístupového objektu get slouží k určení, jestli je toto použití platné.
Přístup, který se používá k implementaci rozhraní, nesmí mít accessor_modifier. Pokud se k implementaci rozhraní používá pouze jeden přístupový objekt, může být druhý přístup deklarován pomocí accessor_modifier:
Příklad:
public interface I { string Prop { get; } } public class C : I { public string Prop { get => "April"; // Must not have a modifier here internal set {...} // Ok, because I.Prop has no set accessor } }
end example
15.7.6 Virtuální, zapečetěné, přepsání a abstraktní přístupové objekty
Poznámka: Tato klauzule se vztahuje na obě vlastnosti (§15.7) a indexery (§15.9). Klauzule se zapisuje z hlediska vlastností, při čtení indexerů nahradí indexer/indexery vlastnost/vlastnosti a prohlédnou seznam rozdílů mezi vlastnostmi a indexery zadanými v §15.9.2. koncová poznámka
Deklarace virtuální vlastnosti určuje, že přístupové objekty vlastnosti jsou virtuální. Modifikátor virtual
se vztahuje na všechny nesoukromého přístupového objektu vlastnosti. Pokud má přístup k virtuální vlastnosti private
accessor_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 abstract
override
, určuje, že vlastnost je abstraktní a přepisuje základní vlastnost. Přístupové objekty takové vlastnosti jsou také abstraktní.
Abstraktní deklarace vlastností jsou povoleny pouze v abstraktních třídách (§15.2.2.2).2). Přístupové objekty zděděné virtuální vlastnosti lze přepsat v odvozené třídě zahrnutím deklarace vlastnosti, která určuje direktivu override
. Označuje se jako deklarace vlastnosti přepsání. Deklarace přepsání vlastnosti deklaruje novou vlastnost. Místo toho se jednoduše specializuje na implementace přístupových objektů existující virtuální vlastnosti.
Deklarace přepsání a přepsaná základní vlastnost musí mít stejnou deklarovanou přístupnost. Jinými slovy, deklarace přepsání nezmění přístupnost základní vlastnosti. Pokud je však přepsaná základní vlastnost chráněna interně a je deklarována v jiném sestavení než sestavení obsahující deklaraci přepsání, bude deklarována deklarace přepsání přístupnosti chráněna. Pokud zděděná vlastnost má pouze jeden přístupový objekt (tj. pokud je zděděná vlastnost jen pro čtení nebo jen pro zápis), přepisovaná vlastnost musí obsahovat pouze tento přístupový objekt. Pokud zděděná vlastnost zahrnuje oba přístupové objekty (tj. pokud je zděděná vlastnost jen pro čtení i zápis), může přepsání zahrnovat buď jeden přístupový objekt, nebo oba přístupové objekty. Mezi typem přepsání a zděděnou vlastností musí existovat převod identity.
Přepsání deklarace vlastnosti může zahrnovat sealed
modifikátor. Použití tohoto modifikátoru zabraňuje odvozené třídě v dalším přepsání vlastnosti. Příslušenství zapečetěné vlastnosti jsou také zapečetěné.
S výjimkou rozdílů v deklaraci a vyvolání syntaxe, virtuální, zapečetěné, přepsání a abstraktní přístupové objekty se chovají přesně jako virtuální, zapečetěné, přepsání a abstraktní metody. Konkrétně platí, že pravidla popsaná v §15.6.4, §15.6.5, §15.6.6 a §15.6.7 se vztahují, jako by byly metody příslušenství odpovídajícího formuláře:
- Get Accessor odpovídá metodě bez parametrů s návratovou hodnotou typu vlastnosti a stejnými modifikátory jako obsahující vlastnost.
- Objekt set odpovídá metodě s jedním parametrem hodnoty typu vlastnosti, návratovým typem void a stejnými modifikátory jako obsahující vlastnost.
Příklad: V následujícím kódu
abstract class A { int y; public virtual int X { get => 0; } public virtual int Y { get => y; set => y = value; } public abstract int Z { get; set; } }
X
je virtuální vlastnost jen pro čtení,Y
je virtuální vlastností pro čtení i zápis aZ
je 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
,Y
aZ
jsou přepsány deklarace vlastností. Každá deklarace vlastnosti přesně odpovídá modifikátorům přístupnosti, typu a názvu odpovídající zděděné vlastnosti. Get accessor ofX
a set accessor of use theY
base keyword to access the zděděné přístupové objekty. Deklarace přepsání jak abstraktních přístupovýchZ
objektů, tak neexistují žádné nevyřízenéabstract
členyB
funkce aB
je povoleno být ne-abstraktní třídou.end example
Pokud je vlastnost deklarována jako přepsání, musí být všechny přepsané přístupové objekty přístupné přepisovacímu kódu. Kromě toho deklarovaná přístupnost samotné vlastnosti nebo indexeru a přístupových objektů musí odpovídat vlastnosti a příslušenství přepsaného člena a přístupových objektů.
Příklad:
public class B { public virtual int P { get {...} protected set {...} } } public class D: B { public override int P { get {...} // Must not have a modifier here protected set {...} // Must specify protected here } }
end example
15.8 Události
15.8.1 Obecné
Událost je člen, který umožňuje objektu nebo třídě poskytovat oznámení. Klienti mohou připojit spustitelný kód pro události zadáním obslužných rutin událostí.
Události se deklarují pomocí event_declarations:
event_declaration
: attributes? event_modifier* 'event' type variable_declarators ';'
| attributes? event_modifier* 'event' type member_name
'{' event_accessor_declarations '}'
;
event_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
event_accessor_declarations
: add_accessor_declaration remove_accessor_declaration
| remove_accessor_declaration add_accessor_declaration
;
add_accessor_declaration
: attributes? 'add' block
;
remove_accessor_declaration
: attributes? 'remove' block
;
unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
Event_declaration může obsahovat sadu atributů (§22) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new
(§15.3.5), static
(§15.6.3, §15.8.4), virtual
(§15.6.4, §15.8.5), override
(§15.6.5, §15.8.5), sealed
(§15.6.6), abstract
(§15.6.7, §15.8.5) a extern
(§15.6.8) modifikátory.
Deklarace událostí podléhají stejným pravidlům jako deklarace metody (§15.6) s ohledem na platné kombinace modifikátorů.
Typ prohlášení o události je delegate_type (§8.2.8) a že delegate_type musí být alespoň tak přístupné jako samotná událost (§7.5.5).
Deklarace události může obsahovat event_accessor_declarations. Pokud tomu tak není, pro neexterní a neabstraktní události je kompilátor poskytne automaticky (§15.8.2); pro události extern
jsou přístupové metody poskytovány externě.
Deklarace události, která vynechá event_accessor_declarations definuje jednu nebo více událostí – jednu pro každou z variable_declarators. Atributy a modifikátory se vztahují na všechny členy deklarované takovým event_declaration.
Jedná se o chybu v době kompilace, kdy event_declaration zahrnout abstract
modifikátor i event_accessor_declarations.
Pokud deklarace události obsahuje extern
modifikátor, říká se, že událost je externí událost. Vzhledem k tomu, že deklarace externí události neposkytuje žádnou skutečnou implementaci, jedná se o chybu, která zahrnuje extern
modifikátor i event_accessor_declarations.
Jedná se o chybu v době kompilace pro variable_declarator deklarace události s nebo abstract
modifikátorem external
.
Událost lze použít jako levý operand +=
operátorů a -=
operátory. Tyto operátory se používají k připojení obslužných rutin událostí nebo k odebrání obslužných rutin událostí z události a modifikátory přístupu ovládacího prvku události kontexty, ve kterých jsou tyto operace povoleny.
Jediné operace, které jsou povoleny u události kódem, který je mimo typ, ve kterém je tato událost deklarována, jsou +=
a -=
. I když takový kód může přidávat a odebírat obslužné rutiny pro událost, nemůže přímo získat ani upravit podkladový seznam obslužných rutin událostí.
Při operaci formuláře x += y
nebo , pokud x –= y
je výsledkem operace typ x
(void
) (na rozdíl od typu za přiřazením s hodnotou x
za přiřazením, stejně jako u jiných x
+=
operátorů definovaných v jiných typech -=
než událostí). To brání externímu kódu v nepřímém zkoumání podkladového delegáta události.
Příklad: Následující příklad ukazuje, jak jsou obslužné rutiny událostí připojeny k instancím
Button
třídy:public delegate void EventHandler(object sender, EventArgs e); public class Button : Control { public event EventHandler Click; } public class LoginDialog : Form { Button okButton; Button cancelButton; public LoginDialog() { okButton = new Button(...); okButton.Click += new EventHandler(OkButtonClick); cancelButton = new Button(...); cancelButton.Click += new EventHandler(CancelButtonClick); } void OkButtonClick(object sender, EventArgs e) { // Handle okButton.Click event } void CancelButtonClick(object sender, EventArgs e) { // Handle cancelButton.Click event } }
LoginDialog
Zde konstruktor instance vytvoří dvěButton
instance a připojí obslužné rutiny událostí k událostemClick
.end example
15.8.2 Události podobné polím
V rámci programu textu třídy nebo struktury, která obsahuje deklaraci události, lze určité události použít jako pole. Aby byla použita tímto způsobem, nesmí být událost abstraktní ani externá a nesmí explicitně obsahovat event_accessor_declarations. Takovou událost lze použít v libovolném kontextu, který povoluje pole. Pole obsahuje delegáta (§20), který odkazuje na seznam obslužných rutin událostí, které byly do události přidány. Pokud nebyly přidány žádné obslužné rutiny událostí, pole obsahuje null
.
Příklad: V následujícím kódu
public delegate void EventHandler(object sender, EventArgs e); public class Button : Control { public event EventHandler Click; protected void OnClick(EventArgs e) { EventHandler handler = Click; if (handler != null) { handler(this, e); } } public void Reset() => Click = null; }
Click
slouží jako pole v rámciButton
třídy. Jak ukazuje příklad, pole lze prozkoumat, upravit a použít ve výrazech vyvolání delegáta. MetodaOnClick
veButton
třídě vyvoláClick
událost. Pojem vyvolání události je přesně ekvivalentem vyvolání delegáta reprezentované událostí , a proto neexistují žádné speciální jazykové konstrukce pro vyvolání událostí. Všimněte si, že vyvolání delegáta předchází kontrola, která zajišťuje, že delegát nemá hodnotu null a že kontrola je provedena v místní kopii, aby se zajistilo zabezpečení vlákna.Mimo deklaraci
Button
třídyClick
lze č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
Click
vyvolání události aClick –= new EventHandler(...);
která odebere delegáta ze seznamu
Click
vyvolání události.end example
Při kompilaci události podobné poli kompilátor automaticky vytvoří úložiště pro uložení delegáta a vytvoří přístupové metody k události, které přidávají nebo odebírají obslužné rutiny událostí do pole delegáta. Operace přidávání a odebírání jsou bezpečné pro nit a mohou být provedeny (ale nejsou nutné) při držení zámku (§13.13) na objektu obsahujícím instanci nebo System.Type
objektu (§12.8.18) pro statickou událost.
Poznámka: Deklarace události instance formuláře:
class X { public event D Ev; }
se zkompilují na něco, co odpovídá:
class X { private D __Ev; // field to hold the delegate public event D Ev { add { /* Add the delegate in a thread safe way */ } remove { /* Remove the delegate in a thread safe way */ } } }
V rámci třídy
X
se odkazy naEv
levou stranu+=
a–=
operátory způsobí vyvolání doplňků pro přidání a odebrání. Všechny ostatní odkazy jsouEv
kompilovány tak, aby odkazovaly na skryté pole__Ev
(§12.8.7). Název "__Ev
" je libovolný. Skryté pole může mít jakýkoli název nebo vůbec žádný název.koncová poznámka
15.8.3 Příslušenství událostí
Poznámka: Deklarace událostí obvykle vynechávají event_accessor_declarations, jak je uvedeno v příkladu
Button
výše. Mohou být například zahrnuté, pokud náklady na úložiště jednoho pole na událost nejsou přijatelné. V takových případech může třída zahrnovat event_accessor_declarations a použít privátní mechanismus pro ukládání seznamu obslužných rutin událostí. koncová poznámka
Event_accessor_declarations události určují spustitelné příkazy přidružené k přidávání a odebírání obslužných rutin událostí.
Deklarace příslušenství se skládají z add_accessor_declaration a remove_accessor_declaration. Každá deklarace přístupového objektu se skládá z přidání nebo odebrání tokenu následovaného blokem. Blok přidružený k add_accessor_declaration určuje příkazy, které se mají provést při přidání obslužné rutiny události, a blok přidružený k remove_accessor_declaration určuje příkazy, které se mají provést při odebrání obslužné rutiny události.
Každý add_accessor_declaration a remove_accessor_declaration odpovídá metodě s jedním parametrem hodnoty typu události a návratovým typem void
. Implicitní parametr přístupového objektu událostí má název value
. Pokud se událost použije v přiřazení události, použije se odpovídající přístup k události. Konkrétně platí, že pokud je +=
operátor přiřazení, použije se přístupný objekt pro přidání a pokud je –=
operátor přiřazení, použije se odebraný přístup. V obou případech se pravý operand operátoru přiřazení použije jako argument pro přístup události. Blok add_accessor_declaration nebo remove_accessor_declaration odpovídá pravidlům pro metody popsané v §15.6.9.void
Zejména return
příkazy v tomto bloku nejsou povoleny k určení výrazu.
Vzhledem k tomu, že příslušenství událostí implicitně má pojmenovaný value
parametr , jedná se o chybu v době kompilace pro místní proměnnou nebo konstantu deklarovanou v přístupovém objektu události, aby měl tento název.
Příklad: V následujícím kódu
class Control : Component { // Unique keys for events static readonly object mouseDownEventKey = new object(); static readonly object mouseUpEventKey = new object(); // Return event handler associated with key protected Delegate GetEventHandler(object key) {...} // Add event handler associated with key protected void AddEventHandler(object key, Delegate handler) {...} // Remove event handler associated with key protected void RemoveEventHandler(object key, Delegate handler) {...} // MouseDown event public event MouseEventHandler MouseDown { add { AddEventHandler(mouseDownEventKey, value); } remove { RemoveEventHandler(mouseDownEventKey, value); } } // MouseUp event public event MouseEventHandler MouseUp { add { AddEventHandler(mouseUpEventKey, value); } remove { RemoveEventHandler(mouseUpEventKey, value); } } // Invoke the MouseUp event protected void OnMouseUp(MouseEventArgs args) { MouseEventHandler handler; handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey); if (handler != null) { handler(this, args); } } }
třída
Control
implementuje interní mechanismus úložiště pro události. MetodaAddEventHandler
přidruží hodnotu delegáta ke klíči,GetEventHandler
metoda vrátí delegáta, který je aktuálně přidružený ke klíči, aRemoveEventHandler
metoda odebere delegáta jako obslužnou rutinu události pro zadanou událost. Předpokládá se, že základní mechanismus úložiště je navržen tak, že neexistuje žádné náklady na přidružení hodnoty delegáta s hodnotou null ke klíči, a proto neošetřené události spotřebovávají žádné úložiště.end example
15.8.4 Statické události a události instance
Když deklarace události obsahuje static
modifikátor, říká se, že událost je statickou událostí. Pokud není k dispozici žádný static
modifikátor, říká se, že událost je událost instance.
Statická událost není přidružená ke konkrétní instanci a jedná se o chybu v době kompilace odkazující na this
přístupové objekty statické události.
Událost instance je přidružena k dané instanci třídy a tato instance je přístupná jako this
(§12.8.14) v přístupových objektech této události.
Rozdíly mezi statickými členy a členy instance jsou popsány dále v §15.3.8.
15.8.5 Virtuální, zapečetěné, přepsání a abstraktní přístupové objekty
Deklarace virtuální události určuje, že přístupové objekty této události jsou virtuální.
virtual
Modifikátor se vztahuje na oba přístupové objekty události.
Abstraktní deklarace události určuje, že přístupové objekty události jsou virtuální, ale neposkytuje skutečnou implementaci přístupových objektů. Místo toho jsou pro přístupové objekty vyžadovány jiné než abstraktní odvozené třídy, aby poskytovaly vlastní implementaci přepsáním události. Vzhledem k tomu, že příslušenství pro abstraktní deklaraci události neposkytuje žádnou skutečnou implementaci, neposkytuje event_accessor_declarations.
Deklarace události, která obsahuje jak modifikátory abstract
override
, určuje, že událost je abstraktní a přepíše základní událost. Přístupové objekty takové události jsou také abstraktní.
Abstraktní deklarace událostí jsou povoleny pouze v abstraktních třídách (§15.2.2.2.2).
Přístupové objekty zděděné virtuální události lze přepsat v odvozené třídě zahrnutím deklarace události, která určuje override
modifikátor. To se označuje jako deklarace události přepsání. Deklarace události přepsání nehlásí novou událost. Místo toho se jednoduše specializuje na implementace přístupových objektů existující virtuální události.
Deklarace události přepsání určuje přesně stejné modifikátory přístupnosti a název jako událost přepsání, musí existovat převod identity mezi typem přepsání a přepsánou událostí, a v rámci deklarace musí být zadány doplňky a doplňky pro odebrání.
Deklarace události přepsání sealed
může obsahovat modifikátor. Použití modifikátoru this
brání odvozené třídě v dalším přepsání události. Příslušenství zapečetěné události jsou také zapečetěné.
Jedná se o chybu v době kompilace pro přepsání deklarace události, která zahrnuje new
modifikátor.
S výjimkou rozdílů v deklaraci a vyvolání syntaxe, virtuální, zapečetěné, přepsání a abstraktní přístupové objekty se chovají přesně jako virtuální, zapečetěné, přepsání a abstraktní metody. Konkrétně platí, že pravidla popsaná v §15.6.4, §15.6.5, §15.6.6 a §15.6.7 platí, jako by byly metody příslušenství odpovídajícího formuláře. Každý přístup odpovídá metodě s jedním parametrem hodnoty typu události, návratovým void
typem a stejnými modifikátory jako obsahující událost.
15.9 Indexery
15.9.1 Obecné
Indexer je člen, který umožňuje indexování objektu stejným způsobem jako pole. Indexery se deklarují pomocí indexer_declarations:
indexer_declaration
: attributes? indexer_modifier* indexer_declarator indexer_body
| attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
;
indexer_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
indexer_declarator
: type 'this' '[' parameter_list ']'
| type interface_type '.' 'this' '[' parameter_list ']'
;
indexer_body
: '{' accessor_declarations '}'
| '=>' expression ';'
;
ref_indexer_body
: '{' ref_get_accessor_declaration '}'
| '=>' 'ref' variable_reference ';'
;
unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
Existují dva druhy indexer_declaration:
- První deklaruje indexer bez ref-value. Jeho hodnota má typ typu. Tento druh indexeru může být čitelný nebo zapisovatelný.
- Druhá deklaruje indexer ref-valued. Jeho hodnota je variable_reference (§9,5 Tento druh indexeru je jen čitelný.
Indexer_declaration může obsahovat sadu atributů (§22) a některý z povolených druhů deklarované přístupnosti (§15.3.6), new
(§15.3.5), (§15.3.5), (virtual
).) 6.4), override
(§15.6.5), sealed
(§15.6.6), abstract
(§15.6.7) a extern
(§15.6.8) modifikátory.
Deklarace indexeru podléhají stejným pravidlům jako deklarace metody (§15.6) s ohledem na platné kombinace modifikátorů, přičemž jedinou výjimkou je, že static
modifikátor není u deklarace indexeru povolen.
Typ deklarace indexeru určuje typ prvku indexeru zavedeného deklarací.
Poznámka: Vzhledem k tomu, že indexery jsou navrženy tak, aby se používaly v kontextech podobných elementům pole, používá se s indexerem také typ prvku termínu definovaný pro pole. koncová poznámka
Pokud indexer není explicitní implementace člena rozhraní, typ je následovaný klíčovým slovem this
. Pro explicitní implementaci člena rozhraní je typ následovaný interface_type, ".
" a klíčové slovo this
. Na rozdíl od ostatních členů nemají indexery uživatelsky definované názvy.
Parameter_list určuje parametry indexeru. Seznam parametrů indexeru odpovídá metodě (§15.6.2), s tím rozdílem, že musí být zadán alespoň jeden parametr a že this
modifiká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
in
jako ,out
neboref
argument, pokud indexer není ref-valued, a proto vrátí odkaz (§9.7). koncová poznámka
Parameter_list indexeru definuje podpis (§7.6) indexeru. Podpis indexeru se konkrétně skládá z počtu a typů jeho parametrů. Typ prvku a názvy parametrů nejsou součástí podpisu indexeru.
Podpis indexeru se liší od podpisů všech ostatních indexerů deklarovaných ve stejné třídě.
Když deklarace indexeru extern
obsahuje modifikátor, indexer se označuje jako externí indexer. Vzhledem k tomu, že deklarace externího indexeru neposkytuje žádnou skutečnou implementaci, musí být každý z accessor_bodyv jeho accessor_declarations středníkem.
Příklad: Následující příklad deklaruje
BitArray
třídu, která implementuje indexer pro přístup k jednotlivým bitům v bitovém poli.class BitArray { int[] bits; int length; public BitArray(int length) { if (length < 0) { throw new ArgumentException(); } bits = new int[((length - 1) >> 5) + 1]; this.length = length; } public int Length => length; public bool this[int index] { get { if (index < 0 || index >= length) { throw new IndexOutOfRangeException(); } return (bits[index >> 5] & 1 << index) != 0; } set { if (index < 0 || index >= length) { throw new IndexOutOfRangeException(); } if (value) { bits[index >> 5] |= 1 << index; } else { bits[index >> 5] &= ~(1 << index); } } } }
Instance
BitArray
třídy spotřebovává podstatně méně paměti než odpovídajícíbool[]
(protože každá hodnota bývalého objektu zabírá pouze jeden bit místo tohobyte
druhého), ale umožňuje stejné operace jakobool[]
.Následující
CountPrimes
třída používáBitArray
a klasický algoritmus "síta" k výpočtu počtu primes mezi 2 a daným maximem:class CountPrimes { static int Count(int max) { BitArray flags = new BitArray(max + 1); int count = 0; for (int i = 2; i <= max; i++) { if (!flags[i]) { for (int j = i * 2; j <= max; j += i) { flags[j] = true; } count++; } } return count; } static void Main(string[] args) { int max = int.Parse(args[0]); int count = Count(max); Console.WriteLine($"Found {count} primes between 2 and {max}"); } }
Všimněte si, že syntaxe pro přístup k prvkům objektu
BitArray
je přesně stejná jako 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; } } }
end example
15.9.2 Indexer a rozdíly vlastností
Indexery a vlastnosti jsou v konceptu velmi podobné, ale liší se následujícími způsoby:
- Vlastnost je identifikována svým názvem, zatímco indexer je identifikován svým podpisem.
- Nemovitost je přístupná prostřednictvím simple_name (§12.8.4) nebo member_access (§12.8.7), zatímco k prvku indexeru se přistupuje prostřednictvím element_access (§12.8.12.3).
- Vlastnost může být statickým členem, zatímco indexer je vždy členem instance.
- Přístupové objekty get vlastnosti odpovídají metodě bez parametrů, zatímco přístupové objekty get indexeru odpovídají metodě se stejným seznamem parametrů jako indexer.
- Objekt set vlastnosti odpovídá metodě s jedním parametrem s názvem
value
, zatímco objekt set přístupového objektu indexeru odpovídá metodě se stejným seznamem parametrů jako indexer a další parametr s ná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
, kdeP
je název vlastnosti. V deklaraci přepsání indexeru je zděděný indexer přístupný pomocí syntaxebase[E]
, kdeE
je čárkami oddělený seznam výrazů. - Neexistuje žádný koncept "automaticky implementovaného indexeru". Jedná se o chybu, která nemá abstraktní externí indexer s středníkem accessor_bodys.
Kromě těchto rozdílů se všechna pravidla definovaná v §15.7.3, §15.7.5 a §15.7.6 vztahují i na přístupové objekty indexeru.
Tato výměna vlastností/vlastností indexerem/indexery při čtení §15.7.3, §15.7.5 a §15.7.6 platí i pro definované termíny. Konkrétně se vlastnost pro čtení a zápis stává indexerem pro čtení, jen pro čtení se stane indexerem jen pro čtení a vlastnost jen pro zápis se stane indexerem jen pro zápis.
15.10 – operátory
15.10.1 Obecné
Operátor je člen, který definuje význam operátoru výrazu, který lze použít na instance třídy. Operátory jsou deklarovány pomocí operator_declarations:
operator_declaration
: attributes? operator_modifier+ operator_declarator operator_body
;
operator_modifier
: 'public'
| 'static'
| 'extern'
| unsafe_modifier // unsafe code support
;
operator_declarator
: unary_operator_declarator
| binary_operator_declarator
| conversion_operator_declarator
;
unary_operator_declarator
: type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
;
logical_negation_operator
: '!'
;
overloadable_unary_operator
: '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
;
binary_operator_declarator
: type 'operator' overloadable_binary_operator
'(' fixed_parameter ',' fixed_parameter ')'
;
overloadable_binary_operator
: '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' | '<<'
| right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
;
conversion_operator_declarator
: 'implicit' 'operator' type '(' fixed_parameter ')'
| 'explicit' 'operator' type '(' fixed_parameter ')'
;
operator_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
Poznámka: Operátory logické předpony (§12.9.4) a přípony null-odpouštějící operátory (§12.8.9), které jsou reprezentovány stejným lexikálním tokenem (!
), jsou odlišné. Druhý operátor není přetížitelným operátorem.
koncová poznámka
Existují tři kategorie přetížitelných operátorů: unární operátory (§15.10.2), binární operátory (§15.10.3) a konverzní operátory (§15.10.4).
Operator_body je buď středníkem, blokovým tělem (§15.6.1), nebo výrazem (§15.6.1). Tělo bloku se skládá z bloku, který určuje příkazy, které se mají provést při vyvolání operátoru. Blok odpovídá pravidlům pro metody vrácení hodnoty popsané v §15.6.11. Tělo výrazu se skládá z výrazu následovaného =>
výrazem a středníkem a označuje jeden výraz, který se má provést při vyvolání operátoru.
Pro extern
operátory se operator_body skládá jednoduše ze středníku. U všech ostatních operátorů je operator_body buď blokovým tělem, nebo tělem výrazu.
Následující pravidla platí pro všechny deklarace operátorů:
- Prohlášení operátoru
public
static
zahrnuje jak modifikátor, tak i modifikátor. - Parametry operátoru nesmí mít jiné modifikátory než
in
. - Podpis operátora (§15.10.2, §15.10.3, §15.10.4) se liší od podpisů všech ostatních operátorů deklarovaných ve stejné třídě.
- Všechny typy uvedené v prohlášení operátora musí být alespoň tak přístupné jako samotný provozovatel (§7.5.5).
- Jedná se o chybu, která se u stejného modifikátoru zobrazí vícekrát v deklaraci operátoru.
Každá kategorie operátoru ukládá další omezení, jak je popsáno v následujících dílčích náklážích.
Stejně jako ostatní členy jsou operátory deklarované v základní třídě zděděné odvozenými třídami. Vzhledem k tomu, že deklarace operátorů vždy vyžadují třídu nebo strukturu, ve které je operátor deklarován pro účast v podpisu operátoru, není možné, aby operátor deklarovaný v odvozené třídě skryl operátor deklarovaný v základní třídě.
new
Modifikátor se tedy nikdy nevyžaduje, a proto není nikdy povolen v deklaraci operátoru.
Další informace o unárních a binárních operátorech naleznete v §12.4.
Další informace o převodních operátorech naleznete v §10.5.
15.10.2 Unární operátory
Následující pravidla se vztahují na deklarace unárního operátoru, kde T
označuje typ instance třídy nebo struktury, která obsahuje deklaraci operátoru:
- Unární
+
, (-
!
pouze logická negace) nebo~
operátor použije jeden parametr typuT
neboT?
může vrátit libovolný typ. - Unární
++
nebo--
operátor přijme jeden parametr typuT
neboT?
vrátí stejný typ nebo typ odvozený z něj. - Unární
true
nebofalse
operátor použije jeden parametr typuT
neboT?
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
a false
operátory jsou dále popsány v §12.24.
Příklad: Následující příklad ukazuje implementaci a následné použití operátoru++ pro celočíselnou třídu vektoru:
public class IntVector { public IntVector(int length) {...} public int Length { get { ... } } // Read-only property public int this[int index] { get { ... } set { ... } } // Read-write indexer public static IntVector operator++(IntVector iv) { IntVector temp = new IntVector(iv.Length); for (int i = 0; i < iv.Length; i++) { temp[i] = iv[i] + 1; } return temp; } } class Test { static void Main() { IntVector iv1 = new IntVector(4); // Vector of 4 x 0 IntVector iv2; iv2 = iv1++; // iv2 contains 4 x 0, iv1 contains 4 x 1 iv2 = ++iv1; // iv2 contains 4 x 2, iv1 contains 4 x 2 } }
Všimněte si, jak metoda operátoru vrátí hodnotu vytvořenou přidáním 1 do operandu, stejně jako operátory přírůstku a dekrementace přípony (§12.8.16) a operátory inkrementace a dekrementace předpony (§12.9.6). Na rozdíl od jazyka C++ by tato metoda neměla upravovat hodnotu svého operandu přímo, protože by porušila standardní sémantiku operátoru přírůstku přípony (§12.8.16).
end example
15.10.3 Binární operátory
Následující pravidla platí pro deklarace binárního operátoru, kde T
označuje typ instance třídy nebo struktury, která obsahuje deklaraci operátoru:
- Binární operátor bez posunu musí mít dva parametry, alespoň jeden z nich musí mít typ
T
neboT?
, a může vrátit jakýkoli typ. - Binární
<<
nebo>>
operátor (§12.11) má dva parametry, z nichž první musí mít typT
neboT?
a druhý z nichž má typint
neboint?
a může vrátit libovolný typ.
Podpis binárního operátoru se skládá z tokenu operátoru (+
, -
, *
, /
%
&
|
^
<<
>>
==
!=
, >
, , <
nebo >=
<=
) a typů dvou parametrů. Návratový typ a názvy parametrů nejsou součástí podpisu binárního operátoru.
Některé binární operátory vyžadují deklaraci typu pár. Pro každou deklaraci některého operátoru páru musí existovat odpovídající deklarace druhého operátoru dvojice. Dvě deklarace operátorů se shodují, pokud mezi jejich návratovými typy a odpovídajícími typy parametrů existují převody identit. Následující operátory vyžadují deklaraci podle páru:
- operátor
==
a operátor!=
- operátor
>
a operátor<
- operátor
>=
a operátor<=
15.10.4 Převodní operátory
Deklarace operátoru převodu zavádí převod definovaný uživatelem (§10.5), který rozšiřuje předem definované implicitní a explicitní převody.
Deklarace operátoru převodu implicit
, která obsahuje klíčové slovo, zavádí uživatelem definovaný implicitní převod. Implicitní převody můžou nastat v různých situacích, včetně volání členů funkce, výrazů přetypování a přiřazení. Toto je popsáno dále v §10.2.
Deklarace operátoru převodu explicit
, která obsahuje klíčové slovo, zavádí uživatelem definovaný explicitní převod. Explicitní převody mohou nastat ve výrazech přetypování a jsou popsány dále v §10.3.
Operátor převodu se převede ze zdrojového typu označeného typem parametru operátoru převodu na cílový typ označený návratovým typem operátoru převodu.
Pro daný typ zdroje a cílový typ S
T
, 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 S
T
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
S
doT
nebo zT
doS
.
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
T
int
astring
, 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>
.end example
Z druhého pravidla vyplývá, že operátor převodu převede buď na třídu nebo z typu struktury, ve které je operátor deklarován.
Příklad: Typ třídy nebo struktury
C
je možné definovat převod zC
int
do a zint
doC
, ale ne zint
dobool
. end example
Není možné přímo předdefinovat předdefinovaný převod. Operátory převodu tedy nemohou převést z nebo na object
, protože implicitní a explicitní převody již existují mezi object
a všemi ostatními typy. Stejně tak zdroj ani cílové typy převodu nemohou být základním typem druhého, protože převod by již existoval. Je však možné deklarovat operátory u obecných typů, které pro konkrétní argumenty typu určují převody, které již existují jako předdefinované převody.
Příklad:
struct Convertible<T> { public static implicit operator Convertible<T>(T value) {...} public static explicit operator T(Convertible<T> value) {...} }
pokud je typ
object
zadán jako typ argument 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).end example
V případech, kdy existuje předdefinovaný převod mezi dvěma typy, budou všechny uživatelem definované převody mezi těmito typy ignorovány. Konkrétně:
- Pokud předdefinovaný implicitní převod (§10.2) existuje z typu
S
na typT
, budou ignorovány všechny uživatelem definované převody (implicitní nebo explicitní).S
T
- Pokud předdefinovaný explicitní převod (§10.3) existuje z typu
S
na typT
, budou ignorovány všechny explicitní převodyS
T
definované uživatelem. Mimoto:- Pokud se jedná
S
oT
typ rozhraní, budou se ignorovat implicitní převodyS
T
definované uživatelem. - V opačném případě se budou i nadále zvažovat
S
implicitní převodyT
definované uživatelem.
- Pokud se jedná
Pro všechny typy, ale object
operá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
object
však předdefinované převody skryjí uživatelem definované převody ve všech případech, ale jednu:void F(object o, Convertible<object> n) { o = n; // Pre-defined boxing conversion o = (object)n; // Pre-defined boxing conversion n = o; // User-defined implicit conversion n = (Convertible<object>)o; // Pre-defined unboxing conversion }
end example
Uživatelem definované převody nelze převést z nebo na interface_types. Konkrétně toto omezení zajišťuje, že při převodu na interface_type nedojde k žádným uživatelem definovaným transformacím a že převod na interface_type bude úspěšný pouze v případě object
, že převáděná transformace skutečně implementuje zadanou interface_type.
Podpis operátoru převodu se skládá ze zdrojového typu a cílového typu. (Toto je jediná forma člena, pro který se návratový typ účastní podpisu.) Implicitní nebo explicitní klasifikace operátoru převodu není součástí podpisu operátora. Třída nebo struktura proto nemůže deklarovat implicitní i explicitní převodní operátor se stejnými zdrojovými a cílovými typy.
Poznámka: Obecně platí, že implicitní převody definované uživatelem by měly být navrženy tak, aby nikdy nevyvolávat výjimky a nikdy neztratily informace. Pokud převod definovaný uživatelem může vést k výjimkám (například kvůli tomu, že je zdrojový argument mimo rozsah) nebo ztrátu informací (například zahození bitů s vysokým pořadím), měl by být tento převod definován jako explicitní převod. koncová poznámka
Příklad: V následujícím kódu
public struct Digit { byte value; public Digit(byte value) { if (value < 0 || value > 9) { throw new ArgumentException(); } this.value = value; } public static implicit operator byte(Digit d) => d.value; public static explicit operator Digit(byte b) => new Digit(b); }
převod z
Digit
nabyte
je implicitní, protože nikdy nevyvolá výjimky nebo ztratí informace, ale převod zbyte
naDigit
je explicitní, protožeDigit
může představovat pouze podmnožinu možných hodnotbyte
.end example
15.11 Konstruktory instancí
15.11.1 Obecné
Konstruktor instance je člen, který implementuje akce potřebné k inicializaci instance třídy. Konstruktory instancí jsou deklarovány pomocí constructor_declarations:
constructor_declaration
: attributes? constructor_modifier* constructor_declarator constructor_body
;
constructor_modifier
: 'public'
| 'protected'
| 'internal'
| 'private'
| 'extern'
| unsafe_modifier // unsafe code support
;
constructor_declarator
: identifier '(' parameter_list? ')' constructor_initializer?
;
constructor_initializer
: ':' 'base' '(' argument_list? ')'
| ':' 'this' '(' argument_list? ')'
;
constructor_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
Constructor_declaration může obsahovat sadu atributů (§22), kterýkoli z povolených druhů deklarované přístupnosti (§15.3.6) a extern
modifikátor (§15.6.8). Deklarace konstruktoru není povolena k zahrnutí stejného modifikátoru vícekrát.
Identifikátor constructor_declarator pojmenuje třídu, ve které je deklarován konstruktor instance. Pokud je zadán jakýkoli jiný název, dojde k chybě v době kompilace.
Nepovinný parameter_list konstruktoru instance podléhá stejným pravidlům jako parameter_list metody (§15.6).
this
Modifikátor parametrů se vztahuje pouze na rozšiřující metody (§15.6.10), ani parametr v parameter_list konstruktoru this
nesmí obsahovat modifikátor. Seznam parametrů definuje podpis (§7.6) konstruktoru instance a řídí proces, při kterém řešení přetížení (§12.6.4) při vyvolání vybere konkrétní konstruktor instance.
Každý z typů odkazovaných v parameter_list konstruktoru instance musí být alespoň tak přístupný jako samotný konstruktor (§7.5.5).
Nepovinný constructor_initializer určuje jiný konstruktor instance, který se má vyvolat před spuštěním příkazů uvedených v constructor_body tohoto konstruktoru instance. Toto je popsáno dále v §15.11.2.
Pokud deklarace konstruktoru extern
obsahuje modifikátor, konstruktor se říká, že je externí konstruktor. Vzhledem k tomu, že deklarace externího konstruktoru neposkytuje žádnou skutečnou implementaci, jeho constructor_body se skládá z středníku. U všech ostatních konstruktorů se constructor_body skládá z obou
- blok, který určuje příkazy pro inicializaci nové instance třídy; nebo
- tělo výrazu, které se skládá z
=>
výrazu a středníku, a označuje jeden výraz pro inicializaci nové instance třídy.
Constructor_body, která je blokem nebo tělem výrazu, odpovídá přesně bloku metody instance návratovým typem void
(§15.6.11).
Konstruktory instancí nejsou zděděné. Třída tedy nemá žádné konstruktory instance jiné než ty, které jsou ve skutečnosti deklarovány ve třídě, s výjimkou, že pokud třída neobsahuje žádné deklarace konstruktoru instance, je automaticky zadán výchozí konstruktor instance (§15.11.5).
Konstruktory instancí jsou vyvolány object_creation_expressions (§12.8.17.2) a constructor_initializer s.
15.11.2 Inicializátory konstruktoru
Všechny konstruktory instance (s výjimkou těch pro třídu object
) implicitně zahrnují vyvolání jiného konstruktoru instance bezprostředně před constructor_body. Konstruktor k implicitnímu vyvolání je určen constructor_initializer:
- Inicializátor konstruktoru instance formuláře
base(
argument_list)
(kde argument_list je volitelný) způsobí vyvolání konstruktoru instance z přímé základní třídy. Tento konstruktor je vybrán pomocí argument_list a pravidel rozlišení přetížení §12.6.4. Sada konstruktorů instance kandidáta se skládá ze všech přístupných konstruktorů instance přímé základní třídy. Pokud je tato sada prázdná nebo pokud nelze identifikovat jeden nejlepší konstruktor instance, dojde k chybě v době kompilace. - Inicializátor konstruktoru instance formuláře
this(
argument_list)
(kde argument_list je nepovinný) vyvolá jiný konstruktor instance ze stejné třídy. Konstruktor je vybrán pomocí argument_list a pravidla rozlišení přetížení §12.6.4. Sada konstruktorů instance kandidáta se skládá ze všech konstruktorů instancí deklarovaných v samotné třídě. Pokud je výsledná sada použitelných konstruktorů instancí prázdná nebo pokud nelze identifikovat jeden nejlepší konstruktor instance, dojde k chybě v době kompilace. Pokud se deklarace konstruktoru instance vyvolá sama prostřednictvím řetězu jednoho nebo více inicializátorů konstruktoru, dojde k chybě v době kompilace.
Pokud konstruktor instance nemá žádný konstruktor inicializátor, konstruktor inicializátor formuláře base()
je implicitně poskytován.
Poznámka: Deklarace konstruktoru instance formuláře
C(...) {...}
je přesně ekvivalentní
C(...) : base() {...}
koncová poznámka
Rozsah parametrů zadaných parameter_list deklarace konstruktoru instance zahrnuje konstruktor inicializátoru této deklarace. Inicializátor konstruktoru je tedy povolen přístup k parametrům konstruktoru.
Příklad:
class A { public A(int x, int y) {} } class B: A { public B(int x, int y) : base(x + y, x - y) {} }
end example
Inicializátor konstruktoru instance nemá přístup k vytvářené instanci. Proto se jedná o chybu v době kompilace, která se na tuto chybu odkazuje ve výrazu argumentu inicializátoru konstruktoru, protože se jedná o chybu v době kompilace výrazu argumentu odkazovat na libovolný člen instance prostřednictvím simple_name.
15.11.3 Inicializátory proměnných instancí
Pokud konstruktor instance nemá žádný konstruktor inicializátor nebo má konstruktor inicializátor formuláře base(...)
, že konstruktor implicitně provádí inicializace určené variable_initializers polí instance deklarovaných ve své třídě. To odpovídá posloupnosti přiřazení, která jsou provedena okamžitě po vstupu do konstruktoru a před implicitním vyvoláním přímého konstruktoru základní třídy. Inicializátory proměnných se provádějí v textovém pořadí, ve kterém jsou uvedeny v deklaraci třídy (§15.5.6).
15.11.4 Provádění konstruktoru
Inicializátory proměnných se transformují na příkazy přiřazení a tyto příkazy přiřazení se provádějí před vyvoláním konstruktoru instance základní třídy. Toto řazení zajišťuje, že se všechna pole instance inicializují pomocí inicializátorů proměnných před spuštěním příkazů , které mají přístup k dané instanci.
Příklad: S ohledem na následující:
class A { public A() { PrintFields(); } public virtual void PrintFields() {} } class B: A { int x = 1; int y; public B() { y = -1; } public override void PrintFields() => Console.WriteLine($"x = {x}, y = {y}"); }
pokud se k vytvoření instance použije nový
B()
,B
vytvoří se následující výstup:x = 1, y = 0
Hodnota
x
je 1, protože inicializátor proměnné se spustí před vyvolání konstruktoru instance základní třídy. Hodnotay
je však 0 (výchozí hodnotaint
) protože přiřazeníy
není provedeno, dokud se konstruktor základní třídy nevrátí. Je užitečné si představit inicializátory proměnných instancí a inicializátory konstruktoru jako příkazy, které jsou automaticky vloženy před constructor_body. Pří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ářů (
base
athis
). Příklad odpovídá níže uvedenému kódu, kde každý komentář označuje automaticky vložený příkaz (syntaxe použitá pro automaticky vložené vyvolání konstruktoru není platná, ale slouží pouze k ilustraci mechanismu).class A { int x, y, count; public A() { x = 1; // Variable initializer y = -1; // Variable initializer object(); // Invoke object() constructor count = 0; } public A(int n) { x = 1; // Variable initializer y = -1; // Variable initializer object(); // Invoke object() constructor count = n; } } class B : A { double sqrt2; ArrayList items; int max; public B() : this(100) { B(100); // Invoke B(int) constructor items.Add("default"); } public B(int n) : base(n - 1) { sqrt2 = Math.Sqrt(2.0); // Variable initializer items = new ArrayList(100); // Variable initializer A(n - 1); // Invoke A(int) constructor max = n; } }
end example
15.11.5 Výchozí konstruktory
Pokud třída neobsahuje žádné deklarace konstruktoru instance, je automaticky poskytnut výchozí konstruktor instance. Tento výchozí konstruktor jednoduše vyvolá konstruktor přímé základní třídy, jako by měl konstruktor inicializátor formuláře base()
. Pokud je třída abstraktní, je deklarovaná přístupnost pro výchozí konstruktor chráněna. V opačném případě je deklarovaná přístupnost výchozího konstruktoru veřejná.
Poznámka: Výchozí konstruktor je tedy vždy ve formuláři.
protected C(): base() {}
nebo
public C(): base() {}
kde
C
je název třídy.koncová poznámka
Pokud řešení přetížení nemůže určit jedinečného nejlepšího kandidáta pro inicializátor konstruktoru základní třídy, dojde k chybě kompilace.
Příklad: V následujícím kódu
class Message { object sender; string text; }
Je k dispozici výchozí konstruktor, protože třída neobsahuje žádné deklarace konstruktoru instance. Proto je příklad přesně ekvivalentní
class Message { object sender; string text; public Message() : base() {} }
end example
15.12 Statické konstruktory
Statický konstruktor je člen, který implementuje akce potřebné k inicializaci uzavřené třídy. Statické konstruktory jsou deklarovány pomocí static_constructor_declarations:
static_constructor_declaration
: attributes? static_constructor_modifiers identifier '(' ')'
static_constructor_body
;
static_constructor_modifiers
: 'static'
| 'static' 'extern' unsafe_modifier?
| 'static' unsafe_modifier 'extern'?
| 'extern' 'static' unsafe_modifier?
| 'extern' unsafe_modifier 'static'
| unsafe_modifier 'static' 'extern'?
| unsafe_modifier 'extern' 'static'
;
static_constructor_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
Static_constructor_declaration může obsahovat sadu atributů (§22) a extern
modifikátor (§15.6.8).
Identifikátor static_constructor_declaration pojmenuje třídu, ve které je deklarován statický konstruktor. Pokud je zadán jakýkoli jiný název, dojde k chybě v době kompilace.
Pokud deklarace statického konstruktoru extern
obsahuje modifikátor, statický konstruktor se říká, že se jedná o externí statický konstruktor. Vzhledem k tomu, že deklarace externího statického konstruktoru neposkytuje žádnou skutečnou implementaci, jeho static_constructor_body se skládá z středníku. Pro všechny ostatní deklarace statického konstruktoru se static_constructor_body skládá z obou
- blok, který určuje příkazy, které se mají provést za účelem inicializace třídy; nebo
- tělo výrazu, které se skládá z
=>
výrazu a středníku, a označuje jeden výraz, který se má provést, aby se inicializovala třída.
Static_constructor_body, která je blokem nebo tělem výrazu, přesně odpovídá method_body statické metody návratovým typem void
(§15.6.11).
Statické konstruktory nejsou zděděné a nelze je volat přímo.
Statický konstruktor pro uzavřenou třídu se spustí najednou v dané doméně aplikace. Spuštění statického konstruktoru se aktivuje první z následujících událostí v rámci domény aplikace:
- Vytvoří se instance třídy.
- Na kterýkoli ze statických členů třídy se odkazuje.
Pokud třída obsahuje metodu Main
(§7.1), ve které spuštění začíná, statický konstruktor pro tuto třídu se provede před voláním Main
metody.
Chcete-li inicializovat nový uzavřený typ třídy, vytvoří se nejprve nová sada statických polí (§15.5.2) pro daný uzavřený typ. Každé statické pole je inicializováno na výchozí hodnotu (§15.5.5). Dále se pro tato statická pole provádějí inicializátory statických polí (§15.5.6.2). Nakonec se spustí statický konstruktor.
Příklad: Příklad
class Test { static void Main() { A.F(); B.F(); } } class A { static A() { Console.WriteLine("Init A"); } public static void F() { Console.WriteLine("A.F"); } } class B { static B() { Console.WriteLine("Init B"); } public static void F() { Console.WriteLine("B.F"); } }
musí vytvořit výstup:
Init A A.F Init B B.F
vzhledem k tomu, že spuštění statického
A
konstruktoru 'je aktivováno volánímA.F
a spuštěníB
statického konstruktoru 'je aktivováno volánímB.F
.end example
Je možné vytvořit cyklické závislosti, které umožňují sledovat statická pole s inicializátory proměnných ve výchozím stavu hodnoty.
Příklad: Příklad
class A { public static int X; static A() { X = B.Y + 1; } } class B { public static int Y = A.X + 1; static B() {} static void Main() { Console.WriteLine($"X = {A.X}, Y = {B.Y}"); } }
vytvoří výstup.
X = 1, Y = 2
Chcete-li spustit metodu
Main
, systém nejprve spustí inicializátor proB.Y
, před statický konstruktor třídyB
.Y
Inicializátor způsobíA
static
spuštění konstruktoruA.X
, protože na hodnotu se odkazuje. Statický konstruktorA
následně vypočítá hodnotuX
a tím načte výchozí hodnotuY
, která je nula.A.X
inicializuje se tedy na 1. Proces spuštěníA
inicializá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.end example
Vzhledem k tomu, že statický konstruktor se provádí přesně jednou pro každý uzavřený konstruovaný typ třídy, je vhodné vynutit kontroly za běhu u parametru typu, který nelze zkontrolovat v době kompilace prostřednictvím omezení (§15.2.5).
Příklad: Následující typ používá statický konstruktor k vynucení, že argument typu je výčt:
class Gen<T> where T : struct { static Gen() { if (!typeof(T).IsEnum) { throw new ArgumentException("T must be an enum"); } } }
end example
15.13 Finalizační metody
Poznámka: V dřívější verzi této specifikace se nyní označuje jako "finalizátor", který se nazývá "destruktor". Zkušenosti ukázaly, že termín "destruktor" způsobil nejasnost a často způsoboval nesprávné očekávání, zejména programátorům, kteří znají jazyk C++. V jazyce C++ je destruktor volána determinatem, zatímco v jazyce C# není finalizátor. Pokud chcete získat determinantní chování z jazyka C#, měli byste použít
Dispose
. koncová poznámka
Finalizátor je člen, který implementuje akce potřebné k dokončení instance třídy. Finalizátor je deklarován pomocí finalizer_declaration:
finalizer_declaration
: attributes? '~' identifier '(' ')' finalizer_body
| attributes? 'extern' unsafe_modifier? '~' identifier '(' ')'
finalizer_body
| attributes? unsafe_modifier 'extern'? '~' identifier '(' ')'
finalizer_body
;
finalizer_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).
Finalizer_declaration může obsahovat sadu atributů (§22).
Identifikátor finalizer_declarator pojmenuje třídu, ve které je finalizátor deklarován. Pokud je zadán jakýkoli jiný název, dojde k chybě v době kompilace.
Když deklarace finalizátoru extern
obsahuje modifikátor, finalizátor se říká, že se jedná o externí finalizátor. Vzhledem k tomu, že deklarace externí finalizátoru neposkytuje žádnou skutečnou implementaci, jeho finalizer_body se skládá z středníku. U všech ostatních finalizátorů se finalizer_body skládá z obou
- blok, který určuje příkazy, které se mají provést, aby bylo možné dokončit instanci třídy.
- nebo tělo výrazu, které se skládá z
=>
výrazu a středníku, a označuje jeden výraz, který se má provést, aby bylo možné dokončit instanci třídy.
Finalizer_body, která je blokemnebo tělem výrazu, přesně odpovídá method_body metody instance s návratovým typem void
(§15.6.11).
Finalizační metody nejsou zděděny. Třída tedy nemá žádné finalizační metody jiné než ty, které mohou být deklarovány v této třídě.
Poznámka: Vzhledem k tomu, že finalizátor musí mít žádné parametry, nemůže být přetížen, takže třída může mít maximálně jeden finalizátor. koncová poznámka
Finalizační metody jsou vyvolány automaticky a nelze je vyvolat explicitně. Instance se stane oprávněnou k dokončení, pokud už není možné, aby jakýkoli kód tuto instanci používal. K provedení finalizátoru instance může dojít kdykoli poté, co se instance stane způsobilým k dokončení (§7.9). Při finalizaci instance se finalizační metody v řetězu dědičnosti instance volají v pořadí od většiny odvozených po nejméně odvozené. Finalizační metodu lze spustit na libovolném vlákně. Další diskuzi o pravidlech, která řídí, kdy a jak se finalizátor provádí, najdete v §7.9.
Příklad: Výstup příkladu
class A { ~A() { Console.WriteLine("A's finalizer"); } } class B : A { ~B() { Console.WriteLine("B's finalizer"); } } class Test { static void Main() { B b = new B(); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
is
B's finalizer A's finalizer
vzhledem k tomu, že finalizační metody v řetězci dědičnosti jsou volány v pořadí, od většiny odvozených po nejméně odvozené.
end example
Finalizační metody jsou implementovány přepsáním virtuální metody Finalize
on System.Object
. Programy jazyka C# nemají povoleno přepsat tuto metodu nebo ji volat (nebo přepisovat) přímo.
Příklad: Například program
class A { override protected void Finalize() {} // Error public void F() { this.Finalize(); // Error } }
obsahuje dvě chyby.
end example
Kompilátor se chová, jako by tato metoda a její přepsání vůbec neexistovaly.
Příklad: Tímto programem:
class A { void Finalize() {} // Permitted }
je platná a zobrazená metoda skryje
System.Object
metoduFinalize
.end example
Diskuzi o chování při vyvolání výjimky z finalizátoru naleznete v §21.4.
15.14 Iterátory
15.14.1 Obecné
Člen funkce (§12.6) implementovaný pomocí bloku iterátoru (§13.3) se nazývá iterátor.
Blok iterátoru lze použít jako tělo člena funkce, pokud návratový typ odpovídajícího členu funkce je jedním z rozhraní enumerátoru (§15.14.2) nebo jednoho z výčtových rozhraní (§15.14.3). Může k němu dojít jako method_body, operator_body nebo accessor_body, zatímco události, konstruktory instancí, statické konstruktory a finalizátory se neimplementují jako iterátory.
Pokud je člen funkce implementovaný pomocí bloku iterátoru, jedná se o chybu v době kompilace pro seznam parametrů člena funkce k určení libovolného in
parametru , out
, nebo ref
parametrů nebo parametru typu ref struct
.
15.14.2 Rozhraní enumerátoru
Rozhraní enumerátoru jsou ne generické rozhraní System.Collections.IEnumerator
a všechny instance obecného rozhraní System.Collections.Generic.IEnumerator<T>
. Pro účely stručnosti se v tomto podkclause a jeho na stejné úrovni tyto rozhraní odkazují jako IEnumerator
a IEnumerator<T>
v uvedeném pořadí.
15.14.3 Výčet rozhraní
Enumerable rozhraní jsou non-generic rozhraní System.Collections.IEnumerable
a všechny instance obecného rozhraní System.Collections.Generic.IEnumerable<T>
. Pro účely stručnosti se v tomto podkclause a jeho na stejné úrovni tyto rozhraní odkazují jako IEnumerable
a IEnumerable<T>
v uvedeném pořadí.
15.14.4 Typ výnosu
Iterátor vytvoří sekvenci hodnot, všechny stejného typu. Tento typ se nazývá typ výnosu iterátoru.
- Typ výnosu iterátoru, který vrací
IEnumerator
neboIEnumerable
jeobject
. - Typ výnosu iterátoru, který vrací
IEnumerator<T>
neboIEnumerable<T>
jeT
.
15.14.5 Enumerator – objekty
15.14.5.1 Obecné
Pokud je člen funkce vracející typ rozhraní enumerátoru implementován pomocí bloku iterátoru, vyvolání člena funkce okamžitě nespustí kód v bloku iterátoru. Místo toho se vytvoří a vrátí objekt enumerátoru. Tento objekt zapouzdřuje kód zadaný v bloku iterátoru a spuštění kódu v bloku iterátoru dojde při vyvolání metody objektu MoveNext
enumeratoru. Objekt enumerátoru má následující charakteristiky:
- Implementuje
IEnumerator
aIEnumerator<T>
, kdeT
je typ výnosu iterátoru. - Implementuje
System.IDisposable
. - Inicializuje se pomocí kopie hodnot argumentů (pokud existuje) a hodnoty instance předané členu funkce.
- Má čtyři potenciální stavy, před, spuštěním, pozastavením a po, a je zpočátku v představu.
Enumerator objekt je obvykle instance kompilátoru generované enumerator třídy, která zapouzdřuje kód v bloku iterátoru a implementuje rozhraní enumerátoru, ale jiné metody implementace jsou možné. Pokud kompilátor vygeneruje třídu enumerátoru, bude tato třída vnořena přímo nebo nepřímo do třídy obsahující člen funkce, bude mít privátní přístupnost a bude mít název vyhrazený pro použití kompilátoru (§6.4.3).
Objekt enumerátoru může implementovat více rozhraní, než je uvedeno výše.
Následující dílčí popisy popisují požadované chování MoveNext
, Current
a Dispose
členy IEnumerator
implementace rozhraní poskytované IEnumerator<T>
enumerátor objektu.
Objekty enumerátoru nepodporují metodu IEnumerator.Reset
. Vyvolání této metody způsobí System.NotSupportedException
vyvolání.
15.14.5.2 Metoda MoveNext
Metoda MoveNext
objektu enumerátoru zapouzdřuje kód bloku iterátoru. Vyvolání MoveNext
metody provede kód v bloku iterátoru a podle potřeby nastaví Current
vlastnost objektu enumerátoru. Přesná akce provedená MoveNext
pomocí závisí na stavu objektu enumerátoru při MoveNext
vyvolání:
- Pokud je stav objektu enumerátoru před, vyvolání
MoveNext
:- Změní stav na spuštěný.
- Inicializuje parametry (včetně
this
) bloku iterátoru na hodnoty argumentů a hodnotu instance uloženou při inicializaci objektu enumeratoru. - Spustí blok iterátoru od začátku, dokud se nepřeruší provádění (jak je popsáno níže).
- Pokud je spuštěn stav objektu enumerátoru, výsledek vyvolání
MoveNext
není zadán. - Pokud je stav objektu enumerátoru pozastaven, vyvolání MoveNext:
- Změní stav na spuštěný.
- Obnoví hodnoty všech místních proměnných a parametrů (včetně
this
) na hodnoty uložené při posledním pozastavení bloku iterátoru.Poznámka: Obsah všech objektů odkazovaných těmito proměnnými se mohl od předchozího volání
MoveNext
změnit . koncová poznámka - Obnoví provádění bloku iterátoru bezprostředně za příkazem výnosu, který způsobil pozastavení provádění a pokračuje, dokud se provádění nepřeruší (jak je popsáno níže).
- Pokud je stav objektu enumerátoru za, vyvolání
MoveNext
vrátí hodnotu false.
Když MoveNext
spustíte blok iterátoru, může být provádění přerušeno čtyřmi způsoby: yield return
příkazem yield break
, příkazem, narazí na konec bloku iterátoru a vyvoláním a rozšířením výjimky z bloku iterátoru.
-
yield return
Pokud je zjištěn výrok (§9.4.4.20):- Výraz zadaný v příkazu je vyhodnocen, implicitně převeden na typ výnosu a přiřazen k
Current
vlastnosti enumerator objektu. - Provádění těla iterátoru je pozastaveno. Hodnoty všech místních proměnných a parametrů (včetně
this
) jsou uloženy, stejně jako umístění tohotoyield return
příkazu.yield return
Pokud je příkaz v jednom nebo vícetry
blocích, přidružené bloky finally se v tuto chvíli nespustí. - Stav objektu enumerátoru se změní na pozastavený.
- Metoda
MoveNext
se vrátítrue
do svého volajícího, což znamená, že iterace úspěšně pokročila na další hodnotu.
- Výraz zadaný v příkazu je vyhodnocen, implicitně převeden na typ výnosu a přiřazen k
-
yield break
Pokud je zjištěn výrok (§9.4.4.20):-
yield break
Pokud je příkaz v jednom nebo vícetry
blocích, spustí se přidruženéfinally
bloky. - Stav objektu enumerátoru se změní na následující.
- Metoda
MoveNext
se vrátífalse
do svého volajícího, což znamená, že iterace je dokončena.
-
- Při výskytu konce textu iterátoru:
- Stav objektu enumerátoru se změní na následující.
- Metoda
MoveNext
se vrátífalse
do svého volajícího, což znamená, že iterace je dokončena.
- Při vyvolání a rozšíření výjimky z bloku iterátoru:
- Šíření výjimky provede příslušné
finally
bloky v těle iterátoru. - Stav objektu enumerátoru se změní na následující.
- Šíření výjimek pokračuje volajícím
MoveNext
metody.
- Šíření výjimky provede příslušné
15.14.5.3 Aktuální vlastnost
Vlastnost objektu Current
enumerátoru je ovlivněna yield return
příkazy v bloku iterátoru.
Pokud je objekt enumerátoru v pozastaveném stavu, hodnota je hodnota Current
nastavena předchozím voláním MoveNext
. Pokud je objekt enumerátoru v před, spuštěném nebo po stavu, není výsledek přístupu Current
zadán.
Pro iterátor s jiným typem výnosu než object
, výsledek přístupu Current
prostřednictvím implementace enumerator objektu IEnumerable
odpovídá přístupu prostřednictvím Current
implementace enumerátoru objektu IEnumerator<T>
a přetypování výsledku na object
.
15.14.5.4 Metoda Dispose
Metoda Dispose
slouží k vyčištění iterace přenesením objektu enumerátoru do po stavu.
- Pokud je stav objektu enumerátoru před tím, vyvolání
Dispose
změní stav na za. - Pokud je spuštěn stav objektu enumerátoru, výsledek vyvolání
Dispose
není zadán. - Pokud je stav objektu enumerátoru pozastaven, vyvolání
Dispose
:- Změní stav na spuštěný.
- Provede všechny bloky finally, jako by poslední spouštěný
yield return
příkaz bylyield break
příkaz. Pokud to způsobí vyvolání a šíření výjimky z těla iterátoru, stav objektu enumerátoru je nastaven na za a výjimka se rozšíří do volající metodyDispose
. - Změní stav na následující.
- Pokud je stav objektu enumerátoru po, vyvolání
Dispose
nemá žádný vliv.
15.14.6 Výčtové objekty
15.14.6.1 Obecné
Pokud je člen funkce vracející typ enumerovatelného rozhraní implementován pomocí bloku iterátoru, vyvolání člena funkce okamžitě nespustí kód v bloku iterátoru. Místo toho se vytvoří a vrátí výčtový objekt. Enumerable objekt GetEnumerator
metoda vrátí enumerator objekt, který zapouzdřuje kód zadaný v bloku iterátoru a spuštění kódu v bloku iterátoru dojde při vyvolání metody enumerator objektu MoveNext
. Výčtový objekt má následující charakteristiky:
- Implementuje
IEnumerable
aIEnumerable<T>
, kdeT
je typ výnosu iterátoru. - Inicializuje se pomocí kopie hodnot argumentů (pokud existuje) a hodnoty instance předané členu funkce.
Enumerable objekt je obvykle instance kompilátor-generated enumerable třídy, která zapouzdřuje kód v iterátor bloku a implementuje enumerable rozhraní, ale jiné metody implementace jsou možné. Pokud kompilátor vygeneruje výčtovou třídu, bude tato třída přímo nebo nepřímo vnořena do třídy obsahující člen funkce, bude mít privátní přístupnost a bude mít název vyhrazený pro použití kompilátoru (§6.4.3).
Výčtový objekt může implementovat více rozhraní, než je uvedeno výše.
Poznámka: Například enumerable objekt může také implementovat
IEnumerator
aIEnumerator<T>
umožnit, aby sloužil jako výčet i enumerátor. Taková implementace by obvykle vrátila vlastní instanci (pro uložení přidělení) z prvního volání .GetEnumerator
Následné vyvoláníGetEnumerator
, pokud existuje, vrátí novou instanci třídy, obvykle stejné třídy, takže volání různých instancí enumerátoru nebudou mít vliv na sebe navzájem. Nemůže vrátit stejnou instanci, i když předchozí výčet již vyčíslil za konec sekvence, protože všechna budoucí volání výčtu výčtu musí vyvolat výjimky. koncová poznámka
15.14.6.2 Metoda GetEnumerator
Enumerable objekt poskytuje implementaci GetEnumerator
metod IEnumerable
a IEnumerable<T>
rozhraní.
GetEnumerator
Dvě metody sdílejí společnou implementaci, která získává a vrací dostupný enumerátor objektu. Objekt enumerátoru se inicializuje s hodnotami argumentu a hodnotou instance uloženou při inicializaci výčtového objektu, ale jinak objekt enumerátoru funguje, jak je popsáno v §15.14.5.
15.15 Asynchronní funkce
15.15.1 Obecné
Metoda (§15.6) nebo anonymní funkce (§12.19) s modifikátorem se nazývá async
. Obecně platí, že termín async se používá k popisu jakéhokoli druhu funkce, která má async
modifikátor.
Jedná se o chybu v době kompilace pro seznam parametrů asynchronní funkce k určení libovolného in
parametru , out
, nebo ref
parametrů nebo jakéhokoli parametru ref struct
typu.
Return_type asynchronní metody musí být buď void
nebo typ úkolu. Pro asynchronní metodu, která vytváří výslednou hodnotu, musí být typ úkolu obecný. Pro asynchronní metodu, která nevygeneruje výslednou hodnotu, nesmí být typ úkolu obecný. Tyto typy jsou uvedeny v této specifikaci jako «TaskType»<T>
a «TaskType»
v uvedeném pořadí. Typ System.Threading.Tasks.Task
a typy standardní knihovny vytvořené z System.Threading.Tasks.Task<TResult>
jsou typy úkolů, stejně jako třída, struktura nebo typ rozhraní, který je přidružen k typu tvůrce úloh prostřednictvím atributu System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
. Tyto typy jsou uvedeny v této specifikaci jako «TaskBuilderType»<T>
a «TaskBuilderType»
. Typ úkolu může mít maximálně jeden parametr typu a nelze jej vnořit do obecného typu.
Asynchronní metoda vracející typ úkolu se říká, že vrací úkol.
Typy úkolů se mohou lišit v přesné definici, ale z pohledu jazyka je typ úkolu v jednom ze stavů neúplných, úspěšných nebo chybných. Chybný úkol zaznamenává příslušnou výjimku. A succeeded«TaskType»<T>
records a result of type T
. Typy úkolů jsou očekávané a úkoly mohou být operandy výrazů await (§12.9.8).
Příklad: Typ
MyTask<T>
úlohy je přidružen k 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() { ... } }
end example
Typ tvůrce úkolů je typ třídy nebo struktury, který odpovídá určitému typu úkolu (§15.15.2). Typ tvůrce úkolů musí přesně odpovídat deklarované přístupnosti odpovídajícího typu úkolu.
Poznámka: Pokud je typ úlohy deklarován
internal
, musí být odpovídající typ tvůrce deklarováninternal
a definován ve stejném sestavení. Pokud je typ úkolu vnořený do jiného typu, musí být typ buider úkolu také vnořený do stejného typu. koncová poznámka
Asynchronní funkce má schopnost pozastavit vyhodnocování pomocí výrazů await (§12.9.8) v těle. Vyhodnocení může být později obnoveno v okamžiku pozastavení výrazu await prostřednictvím delegáta obnovení. Delegát obnovení je typu System.Action
a při vyvolání se vyhodnocení asynchronní vyvolání funkce obnoví z výrazu await, kde skončil. Aktuální volající vyvolání asynchronní funkce je původní volající, pokud volání funkce nebylo nikdy pozastaveno nebo poslední volající delegáta obnovení jinak.
15.15.2 Model tvůrce typů úloh
Typ tvůrce úloh může mít maximálně jeden parametr typu a nelze jej vnořit do obecného typu. Typ tvůrce úkolů musí mít následující členy (pro typy tvůrce úloh, které nejsou obecné, SetResult
nemají žádné parametry) s deklarovanou public
přístupností:
class «TaskBuilderType»<T>
{
public static «TaskBuilderType»<T> Create();
public void Start<TStateMachine>(ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine;
public void SetStateMachine(IAsyncStateMachine stateMachine);
public void SetException(Exception exception);
public void SetResult(T result);
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine;
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine;
public «TaskType»<T> Task { get; }
}
Kompilátor vygeneruje kód, který používá "TaskBuilderType" k implementaci sémantiky pozastavení a obnovení vyhodnocení asynchronní funkce. Kompilátor použije "TaskBuilderType" takto:
-
«TaskBuilderType».Create()
je vyvolána k vytvoření instance «TaskBuilderType», pojmenovanébuilder
v tomto seznamu. -
builder.Start(ref stateMachine)
je vyvolána pro přidružení tvůrce k instanci stavového počítače vygenerovaného kompilátorem .stateMachine
- Tvůrce zavolá
stateMachine.MoveNext()
buď inStart()
, nebo potéStart()
, co se vrátí, aby přešel do stavového stroje.
- Tvůrce zavolá
- Po
Start()
vráceníasync
vyvolá metodabuilder.Task
pro úlohu vrátit z asynchronní metody. - Každé volání
stateMachine.MoveNext()
přejde na stavový počítač. - Pokud se stavový počítač úspěšně dokončí,
builder.SetResult()
je volána, s návratovou hodnotou metody, pokud existuje. - V opačném případě je vyvolána výjimka
e
ve stavovém počítačibuilder.SetException(e)
. - Pokud stavový počítač dosáhne výrazu
await expr
,expr.GetAwaiter()
vyvolá se. - Pokud awaiter implementuje
ICriticalNotifyCompletion
aIsCompleted
je false, stavový počítač vyvolábuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
.-
AwaitUnsafeOnCompleted()
by měla volatawaiter.UnsafeOnCompleted(action)
s tímto volánímAction
stateMachine.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ímAction
stateMachine.MoveNext()
, jakmile se operátor awaiter dokončí.
-
-
SetStateMachine(IAsyncStateMachine)
může být volána kompilátorem vygenerovanouIAsyncStateMachine
implementací k identifikaci instance tvůrce přidruženého k instanci stavového počítače, zejména v případech, kdy je stavový počítač implementován jako typ hodnoty.- Pokud tvůrce volá
stateMachine.SetStateMachine(stateMachine)
, bude volánístateMachine
builder.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.15.3 Vyhodnocení asynchronní funkce vracející úlohu
Vyvolání asynchronní funkce vracející úlohu způsobí vygenerování instance vráceného typu úlohy. Tomu se říká návratová úloha asynchronní funkce. Úkol je zpočátku v neúplném stavu.
Tělo asynchronní funkce se pak vyhodnocuje, dokud nebude pozastaven (dosažením výrazu await) nebo ukončen, v jakém okamžiku se ovládací prvek vrátí volajícímu spolu s vrácenou úlohou.
Když se tělo asynchronní funkce ukončí, návratový úkol se přesune z neúplného stavu:
- Pokud se tělo funkce ukončí v důsledku dosažení návratového příkazu nebo konce těla, zaznamená se do návratového úkolu jakákoli výsledná hodnota, která se vloží do úspěšného stavu.
- Pokud tělo funkce skončí z důvodu nezachycené
OperationCanceledException
, výjimka se zaznamená do návratové úlohy, která je vložena do zrušeného stavu. - Pokud se tělo funkce ukončí jako výsledek jakékoli jiné nezachycené výjimky (§13.10.6), zaznamená se výjimka do návratového úkolu, který je vložen do chybného stavu.
15.15.4 Vyhodnocení asynchronní funkce vracející void
Pokud je void
ná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í void
asynchronní funkce vracející asynchronní funkci, úspěšně se dokončí nebo způsobí vyvolání nezachycené výjimky.
To umožňuje kontextu sledovat, kolik void
asynchronních funkcí pod ním běží, a rozhodnout se, jak z nich vycházet výjimky.
ECMA C# draft specification