22 Atributy
22.1 Obecné
Velká část jazyka C# umožňuje programátorovi určit deklarativní informace o entitách definovaných v programu. Například přístupnost metody ve třídě je určena dekorací s method_modifiers public
, protected
, internal
a private
.
Jazyk C# umožňuje programátorům vymyslet nové druhy deklarativních informací označovaných jako atributy. Programátoři pak můžou připojit atributy k různým entitám programu a načíst informace o atributech v prostředí za běhu.
Poznámka: Například architektura může definovat
HelpAttribute
atribut, který lze umístit na určité prvky programu (například třídy a metody), aby bylo možné poskytnout mapování z těchto prvků programu do jejich dokumentace. koncová poznámka
Atributy jsou definovány prostřednictvím deklarace tříd atributů (§22.2), které mohou mít poziční a pojmenované parametry (§22.2.3). Atributy jsou připojeny k entitám v programu jazyka C# pomocí specifikací atributů (§22.3) a lze je načíst za běhu jako instance atributů (§22.4).
22.2 Třídy atributů
22.2.1 Obecné
Třída, která je odvozena z abstraktní třídy System.Attribute
, ať už přímo nebo nepřímo, je třída atributu. Deklarace třídy atributu definuje nový druh atributu, který lze umístit do programových entit. Podle konvence jsou třídy atributů pojmenovány příponou Attribute
. Použití atributu může obsahovat nebo vynechat tuto příponu.
Obecná deklarace třídy se nepoužije System.Attribute
jako přímá nebo nepřímá základní třída.
Příklad:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attribute
end example
22.2.2 Použití atributů
Atribut AttributeUsage
(§22.5.2) slouží k popisu způsobu použití třídy atributů.
AttributeUsage
má poziční parametr (§22.2.3), který umožňuje třídě atributů určit druhy entit programu, pro které lze použít.
Příklad: Následující příklad definuje třídu atributů pojmenovanou
SimpleAttribute
, která se dá umístit pouze na class_declarations a interface_declarations, a zobrazí několik použití atributuSimple
.[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}
I když je tento atribut definován s názvem
SimpleAttribute
, při použití tohoto atributuAttribute
může být přípona vynechána, což vede k krátkému názvuSimple
. Výše uvedený příklad je tedy sémanticky ekvivalentní následujícímu:[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}
end example
AttributeUsage
má pojmenovaný parametr (§22.2.3), který AllowMultiple
označuje, zda lze atribut zadat více než jednou pro danou entitu. Pokud AllowMultiple
je třída atributu pravdivá, pak tato třída atributu je třída atributu s více použitím a lze ji zadat více než jednou u entity. Pokud AllowMultiple
je třída atributu false nebo je nezadaná, pak je tato třída atributu třída atributu s jedním použitím a lze ji zadat maximálně na entitě.
Příklad: Následující příklad definuje třídu atributu s více použitím s názvem
AuthorAttribute
a zobrazuje deklaraci třídy se dvěma použitími atributuAuthor
:[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class AuthorAttribute : Attribute { public string Name { get; } public AuthorAttribute(string name) => Name = name; } [Author("Brian Kernighan"), Author("Dennis Ritchie")] class Class1 { ... }
end example
AttributeUsage
má jiný pojmenovaný parametr (§22.2.3), který Inherited
označuje, zda je atribut, pokud je zadán v základní třídě, zděděna také třídami odvozenými z této základní třídy. Pokud Inherited
je třída atributu true, pak je tento atribut zděděný. Pokud Inherited
je třída atributu false, tento atribut není zděděný. Pokud není zadána, její výchozí hodnota je true.
Třída X
atributu AttributeUsage
, která nemá k němu připojený atribut, jako v
class X : Attribute { ... }
odpovídá následujícímu:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
22.2.3 Poziční a pojmenované parametry
Třídy atributů mohou mít poziční parametrs a pojmenovaný parametrs. Každý konstruktor veřejné instance pro třídu atributu definuje platnou posloupnost pozičních parametrů pro danou třídu atributu. Každé nestatické veřejné pole pro čtení a zápis a vlastnost třídy atributu definuje pojmenovaný parametr pro třídu atributu. Aby vlastnost definovala pojmenovaný parametr, musí mít vlastnost veřejný přístup get i veřejný přístup k objektu set.
Příklad: Následující příklad definuje třídu atributu,
HelpAttribute
která má jeden poziční parametrurl
, a jeden pojmenovaný parametr,Topic
. I když není statický a veřejný, vlastnostUrl
nedefinuje pojmenovaný parametr, protože není pro čtení i zápis. Zobrazí se také dva použití tohoto atributu:[AttributeUsage(AttributeTargets.Class)] public class HelpAttribute : Attribute { public HelpAttribute(string url) // url is a positional parameter { ... } // Topic is a named parameter public string Topic { get; set; } public string Url { get; } } [Help("http://www.mycompany.com/xxx/Class1.htm")] class Class1 { } [Help("http://www.mycompany.com/xxx/Misc.htm", Topic ="Class2")] class Class2 { }
end example
22.2.4 Typy parametrů atributů
Typy pozičních a pojmenovaných parametrů pro třídu atributů jsou omezené na typy parametrů atributů, které jsou:
- Jeden z následujících typů:
bool
, ,byte
,char
double
,float
,int
,long
,sbyte
,short
,string
, ,uint
, ,ulong
.ushort
- Typ
object
. - Typ
System.Type
. - Typy výčtů.
- Jednorozměrná pole výše uvedených typů.
- Argument konstruktoru nebo veřejné pole, které nemá jeden z těchto typů, nesmí být použit jako poziční nebo pojmenovaný parametr ve specifikaci atributu.
Specifikace atributu 22.3
Specifikace atributu je použití dříve definovaného atributu pro entitu programu. Atribut je část dalších deklarativních informací určených pro entitu programu. Atributy lze zadat v globálním rozsahu (pro určení atributů pro obsahující sestavení nebo modul) a pro type_declaration s (§14.7), class_member_declarations (§15.3), interface_member_declarations (§§15.3) 18.4), struct_member_declarations (§16.3), enum_member_declarations (§19.2), accessor_declarations (§15.7.3), event_accessor_ prohlášení (§15.8), prvky parameter_lists (§15.6.2) a prvky type_parameter_lists (§15.2.3).
Atributy jsou zadané v oddílech atributů. Oddíl atributu se skládá z dvojice hranatých závorek, které obklopují čárkami oddělený seznam jednoho nebo více atributů. Pořadí, ve kterém jsou atributy zadány v tomto seznamu a pořadí, ve kterém jsou oddíly připojené ke stejné entitě programu uspořádané, není významné. Například specifikace [A][B]
atributů , [B][A]
, [A, B]
a [B, A]
jsou ekvivalentní.
global_attributes
: global_attribute_section+
;
global_attribute_section
: '[' global_attribute_target_specifier attribute_list ']'
| '[' global_attribute_target_specifier attribute_list ',' ']'
;
global_attribute_target_specifier
: global_attribute_target ':'
;
global_attribute_target
: identifier
;
attributes
: attribute_section+
;
attribute_section
: '[' attribute_target_specifier? attribute_list ']'
| '[' attribute_target_specifier? attribute_list ',' ']'
;
attribute_target_specifier
: attribute_target ':'
;
attribute_target
: identifier
| keyword
;
attribute_list
: attribute (',' attribute)*
;
attribute
: attribute_name attribute_arguments?
;
attribute_name
: type_name
;
attribute_arguments
: '(' ')'
| '(' positional_argument_list (',' named_argument_list)? ')'
| '(' named_argument_list ')'
;
positional_argument_list
: positional_argument (',' positional_argument)*
;
positional_argument
: argument_name? attribute_argument_expression
;
named_argument_list
: named_argument (',' named_argument)*
;
named_argument
: identifier '=' attribute_argument_expression
;
attribute_argument_expression
: non_assignment_expression
;
Pro výrobní global_attribute_target a v následujícím textu musí mít identifikátor pravopis stejný assembly
nebo module
, pokud je rovnost definována v §6.4.3. Pro výrobní attribute_target a v následujícím textu musí mít identifikátor pravopis, který není roven assembly
nebo module
, s použitím stejné definice rovnosti jako výše.
Atribut se skládá z attribute_name a volitelného seznamu pozičních a pojmenovaných argumentů. Poziční argumenty (pokud nějaké) předchází pojmenované argumenty. Poziční argument se skládá z attribute_argument_expression; pojmenovaný argument se skládá z názvu, za kterým následuje rovnítko následované attribute_argument_expression, které jsou společně omezeny stejnými pravidly jako jednoduché přiřazení. Pořadí pojmenovaných argumentů není významné.
Poznámka: Pro usnadnění práce je koncový čárka povolena v global_attribute_section a attribute_section, stejně jako je povolena v array_initializer (§17.7). koncová poznámka
Attribute_name identifikuje třídu atributů.
Pokud je atribut umístěn na globální úrovni, je vyžadován global_attribute_target_specifier . Pokud se global_attribute_target rovná:
-
assembly
— cílem je obsahující sestavení. -
module
— cílem je obsahující modul.
Nejsou povoleny žádné jiné hodnoty pro global_attribute_target .
Standardizované názvy attribute_target jsou event
, , field
method
param
, , property
, return
, , type
, a .typevar
Tyto cílové názvy se použijí pouze v následujících kontextech:
-
event
— událost. -
field
— pole. Událost podobná poli (tj. jedna bez příslušenství) (§15.8.2) a automaticky implementovaná vlastnost (§15.7.4) může mít také atribut s tímto cílem. -
method
— konstruktor, finalizátor, metoda, operátor, vlastnost get a set accessors, indexer get a set accessors, and event add and remove accessors. Událost podobná poli (tj. jedna bez přístupových objektů) může mít také atribut s tímto cílem. -
param
— přístupové objekty sady vlastností, přístupové objekty sady indexerů, přidání a odebrání přístupových objektů a parametr v konstruktoru, metodě a operátoru. -
property
— vlastnost a indexer. -
return
— delegát, metoda, operátor, vlastnost get accessor a indexer get accessor. -
type
— delegát, třída, struktura, výčet a rozhraní. -
typevar
— parametr typu.
Určité kontexty umožňují specifikaci atributu pro více než jeden cíl. Program může explicitně určit cíl zahrnutím attribute_target_specifier. Bez attribute_target_specifier se použije výchozí nastavení, ale attribute_target_specifier lze použít k potvrzení nebo přepsání výchozího nastavení. Kontexty jsou vyřešeny následujícím způsobem:
- Pro atribut deklarace delegáta je výchozím cílem delegát. V opačném případě, pokud je attribute_target rovna:
-
type
— cílem je delegát -
return
— cílem je návratová hodnota.
-
- Pro atribut deklarace metody výchozí cíl je metoda. V opačném případě, pokud je attribute_target rovna:
-
method
— cílem je metoda -
return
— cílem je návratová hodnota.
-
- Pro atribut deklarace operátoru je výchozím cílem operátor. V opačném případě, pokud je attribute_target rovna:
-
method
— cílem je operátor -
return
— cílem je návratová hodnota.
-
- Pro atribut pro get accessor deklarace pro vlastnost nebo indexer deklarace výchozí cíl je přidružená metoda. V opačném případě, pokud je attribute_target rovna:
-
method
— cílem je přidružená metoda. -
return
— cílem je návratová hodnota.
-
- Pro atribut zadaný u objektu set pro vlastnost nebo deklaraci indexeru je výchozí cíl přidružená metoda. V opačném případě, pokud je attribute_target rovna:
-
method
— cílem je přidružená metoda. -
param
— cílem je lone implicitní parametr.
-
- Pro atribut u automaticky implementované deklarace vlastnosti je výchozí cíl vlastnost. V opačném případě, pokud je attribute_target rovna:
-
field
— cílem je pole backingu vygenerované kompilátorem pro vlastnost.
-
- Pro atribut zadaný v deklaraci události, která vynechá event_accessor_declarations výchozí cíl je deklarace události. V opačném případě, pokud je attribute_target rovna:
-
event
— cílem je deklarace události. -
field
— cíl je pole -
method
— cíle jsou metody
-
- V případě deklarace události, která vynechá event_accessor_declarations výchozí cíl je metoda.
-
method
— cílem je přidružená metoda. -
param
— cílem je lone parametr.
-
Ve všech ostatních kontextech je zahrnutí attribute_target_specifier povoleno, ale nepotřebné.
Příklad: Deklarace třídy může obsahovat nebo vynechat specifikátor
type
:[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}
koncový příklad.
Implementace může přijmout jiné attribute_targets, jejichž účelem je definována implementace. Implementace, která takové attribute_target nerozpozná, vydá upozornění a ignoruje attribute_section obsahující.
Podle konvence jsou třídy atributů pojmenovány příponou Attribute
. Attribute_name může tuto příponu zahrnout nebo vynechat.
Konkrétně se attribute_name vyřeší následujícím způsobem:
- Pokud je identifikátor attribute_name nejvíce vpravo doslovný identifikátor (§6.4.3), je attribute_name vyřešen jako type_name (§7.8). Pokud výsledek není typem odvozeným z
System.Attribute
, dojde k chybě v době kompilace. - Jinak
- Attribute_name se vyřeší jako type_name (§7.8), s výjimkou jakýchkoli chyb. Pokud je toto řešení úspěšné a výsledkem je typ odvozený od
System.Attribute
tohoto typu, je výsledkem tohoto kroku. - Znaky
Attribute
jsou v attribute_name připojeny k identifikátoru nejvíce vpravo a výsledný řetězec tokenů je vyřešen jako type_name (§7.8) s výjimkou chyb, které jsou potlačeny. Pokud je toto řešení úspěšné a výsledkem je typ odvozený odSystem.Attribute
tohoto typu, je výsledkem tohoto kroku.
- Attribute_name se vyřeší jako type_name (§7.8), s výjimkou jakýchkoli chyb. Pokud je toto řešení úspěšné a výsledkem je typ odvozený od
Pokud přesně jeden ze dvou kroků výše vede k typu odvozeného z System.Attribute
, pak tento typ je výsledkem attribute_name. V opačném případě dojde k chybě v době kompilace.
Příklad: Pokud je třída atributu nalezena s i bez této přípony, je přítomna nejednoznačnost a výsledky chyb v době kompilace. Pokud je attribute_name napsaný tak, aby jeho identifikátor nejvíce vpravo byl doslovný identifikátor (§6.4.3), pak se shoduje pouze atribut bez přípony, takže je možné takovou nejednoznačnost vyřešit. Příklad
[AttributeUsage(AttributeTargets.All)] public class Example : Attribute {} [AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Error: ambiguity class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Refers to Example class Class3 {} [@ExampleAttribute] // Refers to ExampleAttribute class Class4 {}
zobrazuje dvě třídy atributů s názvem
Example
aExampleAttribute
. Atribut[Example]
je nejednoznačný, protože by mohl odkazovat na buďExample
neboExampleAttribute
. Použití doslovného identifikátoru umožňuje přesné určení záměru v takových vzácných případech. Atribut[ExampleAttribute]
není nejednoznačný (i když by se jednalo o třídu atributu s názvemExampleAttributeAttribute
!). Pokud je deklarace třídyExample
odebrána, oba atributy odkazují na třídu atributu pojmenovanouExampleAttribute
následujícím způsobem:[AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Refers to ExampleAttribute class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Error: no attribute named “Example” class Class3 {}
end example
Jedná se o chybu v době kompilace, kdy se ve stejné entitě používá třída atributu s jedním použitím více než jednou.
Příklad: Příklad
[AttributeUsage(AttributeTargets.Class)] public class HelpStringAttribute : Attribute { public HelpStringAttribute(string value) { Value = value; } public string Value { get; } } [HelpString("Description of Class1")] [HelpString("Another description of Class1")] // multiple uses not allowed public class Class1 {}
výsledkem chyby v době kompilace, protože se pokouší použít
HelpString
, což je třída atributu s jedním použitím, více než jednou v deklaraciClass1
.end example
Výraz E
je attribute_argument_expression , pokud jsou splněny všechny následující příkazy:
- Typ je typ parametru
E
atributu (§22.2.4). - V době kompilace lze hodnotu přeložit na jednu z následujících možností
E
:
Příklad:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field)] public class TestAttribute : Attribute { public int P1 { get; set; } public Type P2 { get; set; } public object P3 { get; set; } } [Test(P1 = 1234, P3 = new int[]{1, 3, 5}, P2 = typeof(float))] class MyClass {} class C<T> { [Test(P2 = typeof(T))] // Error – T not a closed type. int x1; [Test(P2 = typeof(C<T>))] // Error – C<;T>; not a closed type. int x2; [Test(P2 = typeof(C<int>))] // Ok int x3; [Test(P2 = typeof(C<>))] // Ok int x4; }
end example
Atributy typu deklarované ve více částech jsou určeny kombinováním atributů jednotlivých částí v nezadaném pořadí. Pokud je stejný atribut umístěn na více částí, je ekvivalentní k zadání daného atributu vícekrát u typu.
Příklad: Dvě části:
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}
jsou ekvivalentní následující jediné deklaraci:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}
end example
Atributy parametrů typu se stejným způsobem kombinují.
22.4 Instance atributů
22.4.1 Obecné
Instance atributu je instance, která představuje atribut za běhu. Atribut je definován pomocí třídy atributů, pozičních argumentů a pojmenovaných argumentů. Instance atributu je instance třídy atributu, která je inicializována pozičními a pojmenovanými argumenty.
Načtení instance atributu zahrnuje kompilaci i zpracování za běhu, jak je popsáno v následujících dílčích náclausech.
22.4.2 Kompilace atributu
Kompilace atributu s třídami T
atributů , P
, named_argument_listN
a určená pro entitu E
programu je zkompilována do sestavení A
pomocí následujících kroků:
- Při kompilaci object_creation_expression nového
T(P)
formuláře postupujte podle kroků pro zpracování doby kompilace. Tyto kroky buď způsobí chybu v době kompilace, nebo určí konstruktorC
instance, kterýT
lze vyvolat za běhu. - Pokud
C
nemá veřejnou přístupnost, dojde k chybě v době kompilace. - Pro každou named_argument
Arg
vN
:- Nechejte
Name
identifikátornamed_argument .Arg
-
Name
určí nestatické veřejné pole nebo vlastnostT
pro čtení i zápis . PokudT
takové pole nebo vlastnost neobsahuje, dojde k chybě v době kompilace.
- Nechejte
- Pokud je některá z hodnot v rámci positional_argument_list
P
nebo jedné z hodnot v rámci named_argument_listN
typuSystem.String
a hodnota není správně vytvořená podle standardu Unicode, je definována implementací, zda je zkompilovaná hodnota rovna načtené hodnotě za běhu (§22.4.3).Poznámka: Například řetězec, který obsahuje vysokou náhradní jednotku kódu UTF-16, která není okamžitě následovaná nízkou náhradní jednotkou kódu, není dobře formátovaná. koncová poznámka
- Do výstupu sestavení kompilátoru uložte následující informace (pro vytvoření instance atributu za běhu) v důsledku kompilace programu obsahujícího atribut: třída
T
atributu , konstruktorC
instance naT
, positional_argument_listP
, named_argument_listN
a přidružená programová entitaE
s hodnotami vyřešenými v době kompilace.
22.4.3 Načtení instance atributu za běhu
Pomocí termínů definovaných v §22.4.2 lze z sestavení T
načíst instanci atributu reprezentovanou parametrem C
, P
N
, a E
přidružené A
k ní za běhu pomocí následujících kroků:
- Postupujte podle kroků zpracování za běhu pro spuštění object_creation_expression formuláře
new T(P)
pomocí konstruktoruC
instance a hodnot určených v době kompilace. Tyto kroky buď způsobí výjimku, nebo vytvoří instanciO
T
. - Pro každou named_argument
Arg
vN
pořadí:- Nechejte
Name
identifikátornamed_argument .Arg
PokudName
neidentifikuje nestatické veřejné pole nebo vlastnostO
pro čtení i zápis , vyvolá se výjimka. - Pojďme
Value
být výsledkem vyhodnocení attribute_argument_expression .Arg
- Pokud
Name
identifikuje pole zapnutoO
, nastavte toto pole naValue
hodnotu . - V opačném případě Název identifikuje vlastnost
O
. Nastavte tuto vlastnost na Hodnotu. - Výsledkem je
O
instance třídyT
atributu, která byla inicializována sP
a named_argument_listN
.
- Nechejte
Poznámka: Formát pro ukládání
T
, ,C
, ,P
N
(a přidružováníE
) vA
a mechanismus k určeníE
a načteníT
,C
,P
N
zA
(a proto, jak je instance atributu získána za běhu) je nad rámec této specifikace. koncová poznámka
Příklad: V implementaci rozhraní příkazového řádku
Help
lze instance atributů v sestavení vytvořené kompilací ukázkového programu v §22.2.3 načíst pomocí následujícího programu:public sealed class InterrogateHelpUrls { public static void Main(string[] args) { Type helpType = typeof(HelpAttribute); string assemblyName = args[0]; foreach (Type t in Assembly.Load(assemblyName).GetTypes()) { Console.WriteLine($"Type : {t}"); var attributes = t.GetCustomAttributes(helpType, false); var helpers = (HelpAttribute[]) attributes; foreach (var helper in helpers) { Console.WriteLine($"\tUrl : {helper.Url}"); } } } }
end example
22.5 Rezervované atributy
22.5.1 Obecné
Určitý počet atributů má vliv na jazyk nějakým způsobem. K těmto atributům patří:
-
System.AttributeUsageAttribute
(§22.5.2), který se používá k popisu způsobů použití třídy atributu. -
System.Diagnostics.ConditionalAttribute
(§22.5.3) je třída atributů s více použitím, která slouží k definování podmíněných metod a tříd podmíněného atributu. Tento atribut označuje podmínku testováním symbolu podmíněné kompilace. -
System.ObsoleteAttribute
(§22.5.4), který slouží k označení člena jako zastaralého. -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
(§22.5.5), který slouží k vytvoření tvůrce úloh pro asynchronní metodu. -
System.Runtime.CompilerServices.CallerLineNumberAttribute
(§22.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute
(§22.5.6.3) aSystem.Runtime.CompilerServices.CallerMemberNameAttribute
(§22.5.6.4), které slouží k poskytování informací o volajícím kontextu volitelným parametrům.
Atributy statické analýzy s možnou hodnotou null (§22.5.7) mohou zlepšit správnost upozornění generovaných pro závazky null a stavy null (§8.9.5).
Spouštěcí prostředí může poskytovat další atributy definované implementací, které ovlivňují provádění programu jazyka C#.
22.5.2 AtributUsage
AttributeUsage
Atribut se používá k popisu způsobu, jakým lze třídu atributu použít.
Třída, která je zdobena AttributeUsage
atributem, musí být odvozena buď System.Attribute
přímo nebo nepřímo. V opačném případě dojde k chybě kompilace.
Poznámka: Příklad použití tohoto atributu naleznete v § 22.2.2. koncová poznámka
22.5.3 Podmíněný atribut
22.5.3.1 Obecné
Atribut Conditional
umožňuje definici podmíněných metod a tříd podmíněného atributu.
22.5.3.2 Podmíněné metody
Metoda zdobená atributem Conditional
je podmíněná metoda. Každá podmíněná metoda je tedy přidružena k symbolům podmíněné kompilace deklarovaným ve svých Conditional
atributech.
Příklad:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }
deklaruje
Eg.M
jako podmíněnou metodu přidruženou ke dvěma symbolůmALPHA
podmíněné kompilace aBETA
.end example
Volání podmíněné metody je zahrnuto, pokud je v okamžiku volání definován jeden nebo více jeho přidružených symbolů podmíněné kompilace, jinak je volání vynecháno.
Podmíněná metoda podléhá následujícím omezením:
- Podmíněná metoda je metoda v class_declaration nebo struct_declaration. K chybě v době kompilace dochází v případě, že
Conditional
je atribut zadán v metodě v deklaraci rozhraní. - Podmíněná metoda musí mít návratový
void
typ . - Podmíněná metoda nesmí být označena modifikátorem
override
. Podmíněnou metodu lze však označit modifikátoremvirtual
. Přepsání takové metody jsou implicitně podmíněná a nesmí být explicitně označena atributemConditional
. - Podmíněná metoda nesmí být implementací metody rozhraní. V opačném případě dojde k chybě kompilace.
- Parametry podmíněné metody nesmí být výstupními parametry.
Kromě toho dojde k chybě kompilace v případě, že je delegát vytvořen z podmíněné metody.
Příklad: Příklad
#define DEBUG using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void M() { Console.WriteLine("Executed Class1.M"); } } class Class2 { public static void Test() { Class1.M(); } }
deklaruje
Class1.M
jako podmíněnou metodu.Class2
Test
Metoda volá tuto metodu. Vzhledem k tomu, že je definován symbolDEBUG
podmíněné kompilace, pokudClass2.Test
je volána, bude volatM
. Pokud symbolDEBUG
nebyl definován, pakClass2.Test
by nebylo voláníClass1.M
.end example
Je důležité pochopit, že zahrnutí nebo vyloučení volání podmíněné metody je řízeno symboly podmíněné kompilace v okamžiku volání.
Příklad: V následujícím kódu
// File Class1.cs: using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void F() { Console.WriteLine("Executed Class1.F"); } } // File Class2.cs: #define DEBUG class Class2 { public static void G() { Class1.F(); // F is called } } // File Class3.cs: #undef DEBUG class Class3 { public static void H() { Class1.F(); // F is not called } }
třídy
Class2
aClass3
každá obsahují volání podmíněné metodyClass1.F
, která je podmíněná na základě toho, zda je definována nebo neníDEBUG
definována. Vzhledem k tomu, že tento symbol je definován v kontextuClass2
, ale neClass3
, je zahrnuta voláníF
Class2
, zatímco voláníF
doClass3
je vynecháno.end example
Použití podmíněných metod v řetězu dědičnosti může být matoucí. Volání podmíněné metody prostřednictvím base
formuláře base.M
podléhají normálním pravidlům volání podmíněné metody.
Příklad: V následujícím kódu
// File Class1.cs using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public virtual void M() => Console.WriteLine("Class1.M executed"); } // File Class2.cs class Class2 : Class1 { public override void M() { Console.WriteLine("Class2.M executed"); base.M(); // base.M is not called! } } // File Class3.cs #define DEBUG class Class3 { public static void Main() { Class2 c = new Class2(); c.M(); // M is called } }
Class2
obsahuje voláníM
definované v její základní třídě. Toto volání je vynecháno, protože základní metoda je podmíněná na základě přítomnosti symboluDEBUG
, který není definován. Proto metoda zapisuje pouze do konzoly "Class2.M executed
". Uvážlivé použití pp_declarations může tyto problémy odstranit.end example
22.5.3.3 Třídy podmíněného atributu
Třída atributu (§22.2) zdobená jedním nebo více Conditional
atributy je třída podmíněného atributu. Podmíněná třída atributu je tedy přidružena k symbolům podmíněné kompilace deklarovaným v jeho Conditional
atributech.
Příklad:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
deklaruje
TestAttribute
jako třídu podmíněného atributu asociované se symboly podmíněných kompilacíALPHA
aBETA
.end example
Specifikace atributů (§22.3) podmíněného atributu jsou zahrnuty, pokud je v místě specifikace definován jeden nebo více jeho přidružených symbolů podmíněné kompilace, jinak je specifikace atributu vynechána.
Je důležité si uvědomit, že zahrnutí nebo vyloučení specifikace atributu třídy podmíněného atributu je řízeno podmíněnými symboly kompilace v okamžiku specifikace.
Příklad: V příkladu
// File Test.cs: using System; using System.Diagnostics; [Conditional("DEBUG")] public class TestAttribute : Attribute {} // File Class1.cs: #define DEBUG [Test] // TestAttribute is specified class Class1 {} // File Class2.cs: #undef DEBUG [Test] // TestAttribute is not specified class Class2 {}
třídy
Class1
aClass2
jsou každý zdoben atributemTest
, který je podmíněný na základě toho, zda je definován nebo neníDEBUG
definován. Vzhledem k tomu, že tento symbol je definován v kontextuClass1
, ale neClass2
, specifikace test atributuClass1
je zahrnuta, zatímco specifikace atributuTest
onClass2
je vynechána.end example
22.5.4 Zastaralý atribut
Obsolete
Atribut se používá k označení typů a členů typů, které by se už neměly používat.
Pokud program používá typ nebo člen, který je zdoben atributem Obsolete
, kompilátor vydá upozornění nebo chybu. Konkrétně kompilátor vydá upozornění, pokud není zadaný žádný parametr chyby, nebo pokud je zadaný parametr chyby a má hodnotu false
. Kompilátor vydá chybu, pokud je zadán parametr chyby a má hodnotu true
.
Příklad: V následujícím kódu
[Obsolete("This class is obsolete; use class B instead")] class A { public void F() {} } class B { public void F() {} } class Test { static void Main() { A a = new A(); // Warning a.F(); } }
třída
A
je zdobena atributemObsolete
. Každé použitíA
vMain
důsledku toho způsobí upozornění, které obsahuje zadanou zprávu: "Tato třída je zastaralá; použít místo toho tříduB
".end example
22.5.5 Atribut AsyncMethodBuilder
Tento atribut je popsán v §15.15.1.
22.5.6 Atributy informace o volajícím
22.5.6.1 Obecné
Pro účely, jako je protokolování a generování sestav, je někdy užitečné, aby člen funkce získal určité informace o době kompilace o volajícím kódu. Atributy informací o volajícím poskytují způsob, jak tyto informace transparentně předat.
Pokud je volitelný parametr anotován jedním z atributů caller-info, vynechání odpovídajícího argumentu ve volání nemusí nutně způsobit nahrazení výchozí hodnoty parametru. Místo toho, pokud jsou k dispozici zadané informace o volajícím kontextu, předají se jako hodnota argumentu.
Příklad:
public void Log( [CallerLineNumber] int line = -1, [CallerFilePath] string path = null, [CallerMemberName] string name = null ) { Console.WriteLine((line < 0) ? "No line" : "Line "+ line); Console.WriteLine((path == null) ? "No file path" : path); Console.WriteLine((name == null) ? "No member name" : name); }
Volání
Log()
bez argumentů by vytisklo číslo řádku a cestu k souboru volání a také název člena, ve kterém došlo k volání.end example
Atributy informace o volajícím můžou nastat na volitelných parametrech kdekoli, včetně delegování deklarací. Konkrétní atributy volajícího-info však mají omezení na typy parametrů, které mohou atributovat, takže vždy bude existovat implicitní převod z nahrazené hodnoty na typ parametru.
Jedná se o chybu, která má stejný atribut caller-info pro parametr definování i implementaci části deklarace částečné metody. Použijí se pouze atributy informací o volajícím v definující části, zatímco atributy informací o volajícím, ke kterým dochází pouze v implementující části, se ignorují.
Informace o volajícím nemají vliv na rozlišení přetížení. Vzhledem k tomu, že atributy volitelné parametry jsou stále vynechány ze zdrojového kódu volajícího, rozlišení přetížení ignoruje tyto parametry stejným způsobem, jakým ignoruje jiné nepovinné parametry (§12.6.4).
Informace o volajícím se nahradí pouze v případech, kdy je funkce explicitně vyvolána ve zdrojovém kódu. Implicitní vyvolání, jako jsou implicitní volání nadřazeného konstruktoru, nemají zdrojové umístění a nenahrazují informace o volajícím. Volání dynamicky svázaná také nenahrazují informace o volajícím. Pokud je v takových případech vynechán parametr s atributem caller-info, použije se místo toho zadaná výchozí hodnota parametru.
Jednou výjimkou jsou výrazy dotazu. Jsou považovány za syntaktické rozšíření a pokud volání rozbalí, aby vynechaly volitelné parametry s atributy volajícího-informace, budou informace volajícího nahrazeny. Použité umístění je umístění klauzule dotazu, ze které bylo volání vygenerováno.
Pokud je pro daný parametr zadáno více než jeden atribut informace o volajícím, jsou rozpoznány v následujícím pořadí: CallerLineNumber
, , CallerFilePath
CallerMemberName
. Představte si následující deklaraci parametru:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber
má přednost a ostatní dva atributy jsou ignorovány. Pokud CallerLineNumber
by byl vynechán, CallerFilePath
měl by přednost a CallerMemberName
byl by ignorován. Lexikální řazení těchto atributů je irelevantní.
22.5.6.2 Atribut CallerLineNumber
Atribut System.Runtime.CompilerServices.CallerLineNumberAttribute
je povolen pro volitelné parametry, pokud existuje standardní implicitní převod (§10.4.2) z konstantní hodnoty int.MaxValue
na typ parametru. Tím zajistíte, že jakékoli nezáporné číslo řádku až do této hodnoty bude možné předat bez chyby.
Pokud funkce vyvolání z umístění ve zdrojovém kódu vynechá volitelný parametr s parametrem CallerLineNumberAttribute
, číselný literál představující číslo řádku tohoto umístění se použije jako argument pro vyvolání místo výchozí hodnoty parametru.
Pokud vyvolání zahrnuje více řádků, zvolená čára je závislá na implementaci.
Číslo řádku může být ovlivněno direktivou #line
(§6.5.8).
22.5.6.3 Atribut CallerFilePath
System.Runtime.CompilerServices.CallerFilePathAttribute
Atribut je povolen pro volitelné parametry, pokud existuje standardní implicitní převod (§10.4.2) od string
typu parametru.
Pokud funkce vyvolání z umístění ve zdrojovém kódu vynechá volitelný parametr s parametrem CallerFilePathAttribute
, pak řetězcový literál představující cestu k souboru umístění se použije jako argument pro vyvolání místo výchozí hodnoty parametru.
Formát cesty k souboru je závislý na implementaci.
Cesta k souboru může být ovlivněna direktivou #line
(§6.5.8).
22.5.6.4 Atribut CallerMemberName
System.Runtime.CompilerServices.CallerMemberNameAttribute
Atribut je povolen pro volitelné parametry, pokud existuje standardní implicitní převod (§10.4.2) od string
typu parametru.
Pokud vyvolání funkce z umístění v těle člena funkce nebo v atributu použitém pro samotný člen funkce nebo jeho návratový typ, parametry nebo parametry typu ve zdrojovém kódu vynechá volitelný parametr s CallerMemberNameAttribute
řetězcový literál představující název tohoto členu se použije jako argument pro vyvolání místo výchozí hodnoty parametru.
Pro vyvolání, ke kterým dochází v rámci obecných metod, se použije pouze samotný název metody bez seznamu parametrů typu.
Pro vyvolání, ke kterým dochází v rámci explicitních implementací členů rozhraní, se použije pouze samotný název metody bez předchozí kvalifikace rozhraní.
Pro vyvolání, ke kterým dochází v rámci vlastnosti nebo přístupových objektů událostí, je použitý název členu vlastnost nebo samotná událost.
Pro vyvolání, ke kterým dochází v rámci přístupových objektů indexeru, je použitý název člena zadaný (IndexerNameAttribute
§22.6) na členu indexeru, pokud existuje, nebo výchozí název Item
jinak.
Pro vyvolání, ke kterým dochází v rámci inicializátorů polí nebo událostí, je použitý název členu název pole nebo události, která se inicializuje.
Pro vyvolání, ke kterým dochází v rámci deklarací konstruktorů instancí, statických konstruktorů, finalizátorů a operátorů, je použitý název členu závislý na implementaci.
22.5.7 Atributy analýzy kódu
22.5.7.1 Obecné
Atributy v této části slouží k poskytování dalších informací pro podporu kompilátoru, který poskytuje diagnostiku stavu null a stavu null (§8.9.5). Kompilátor není nutný k provedení diagnostiky stavu null. Přítomnost nebo absence těchto atributů neovlivňuje jazyk ani chování programu. Kompilátor, který neposkytuje diagnostiku stavu null, musí číst a ignorovat přítomnost těchto atributů. Kompilátor, který poskytuje diagnostiku stavu null, použije význam definovaný v této části pro kterýkoli z těchto atributů, které používá k informování své diagnostiky.
Atributy analýzy kódu jsou deklarovány v oboru názvů System.Diagnostics.CodeAnalysis
.
Atribut | Význam |
---|---|
AllowNull (§22.5.7.2) |
Argument, který není null, může mít hodnotu null. |
DisallowNull (§22.5.7.3) |
Argument s možnou hodnotou null by nikdy neměl být null. |
MaybeNull (§22.5.7.6) |
Návratová hodnota, která není null, může mít hodnotu null. |
NotNull (§22.5.7.8) |
Návratová hodnota s možnou hodnotou null nikdy nebude null. |
MaybeNullWhen (§22.5.7.7) |
Pokud metoda vrátí zadanou bool hodnotu, může být argument nenulový. |
NotNullWhen (§22.5.7.10) |
Argument s možnou hodnotou null nebude null, pokud metoda vrátí zadanou bool hodnotu. |
NotNullIfNotNull (§22.5.7.9) |
Návratová hodnota není null, pokud argument pro zadaný parametr nemá hodnotu null. |
DoesNotReturn (§22.5.7.4) |
Tato metoda nikdy nevrátí. |
DoesNotReturnIf (§22.5.7.5) |
Tato metoda nikdy nevrátí, pokud přidružený bool parametr má zadanou hodnotu. |
Následující části § 22.5.7.1 jsou podmíněně normativní.
22.5.7.2 Atribut AllowNull
Určuje, že hodnota null je povolena jako vstup, i když odpovídající typ tuto hodnotu zakáže.
Příklad: Vezměte v úvahu následující vlastnost pro čtení a zápis, která se nikdy nevrátí
null
, protože má rozumnou výchozí hodnotu. Uživatel však může dát hodnotu null objektu set, který nastaví vlastnost na tuto výchozí hodnotu.#nullable enable public class X { [AllowNull] public string ScreenName { get => _screenName; set => _screenName = value ?? GenerateRandomScreenName(); } private string _screenName = GenerateRandomScreenName(); private static string GenerateRandomScreenName() => ...; }
Vzhledem k následujícímu použití přístupového objektu sady této vlastnosti
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNull
Bez atributu může kompilátor vygenerovat upozornění, protože vlastnost s ne-nullovými typy se zdá být nastavena na hodnotu null. Přítomnost atributu potlačí toto upozornění. end example
22.5.7.3 Atribut DisallowNull
Určuje, že hodnota null je zakázána jako vstup, i když to odpovídající typ umožňuje.
Příklad: Vezměte v úvahu následující vlastnost, ve které je null výchozí hodnota, ale klienti ji mohou nastavit pouze na hodnotu, která není null.
#nullable enable public class X { [DisallowNull] public string? ReviewComment { get => _comment; set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null"); } private string? _comment = default; }
Přístupový objekt get by mohl vrátit výchozí hodnotu
null
, takže kompilátor může upozornit, že musí být zkontrolován před přístupem. Kromě toho varuje volající, že i když může mít hodnotu null, volající by ji neměl explicitně nastavit na hodnotu null. end example
22.5.7.4 Atribut DoesNotReturn
Určuje, že daná metoda nikdy nevrátí.
Příklad: Zvažte následující:
public class X { [DoesNotReturn] private void FailFast() => throw new InvalidOperationException(); public void SetState(object? containedField) { if ((!isInitialized) || (containedField == null)) { FailFast(); } // null check not needed. _field = containedField; } private bool isInitialized = false; private object _field; }
Přítomnost atributu pomáhá kompilátoru mnoha způsoby. Nejprve může kompilátor vydat upozornění, pokud existuje cesta, kde metoda může ukončit bez vyvolání výjimky. Za druhé může kompilátor potlačit upozornění s možnou hodnotou null v jakémkoli kódu po volání této metody, dokud se nenajde příslušná klauzule catch. Za třetí, nedostupný kód nebude mít vliv na žádné stavy null.
Atribut nemění dosažitelnost (§13.2) ani určitou analýzu přiřazení (§9.4) na základě přítomnosti tohoto atributu. Používá se jenom k ovlivnění upozornění s nulovou dostupností. end example
22.5.7.5 Atribut DoesNotReturnIf
Určuje, že daná metoda nikdy nevrátí, pokud přidružený bool
parametr má zadanou hodnotu.
Příklad: Zvažte následující:
#nullable enable public class X { private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName) { if (!isNull) { throw new ArgumentException(argumentName, $"argument {argumentName} can't be null"); } } public void SetFieldState(object containedField) { ThrowIfNull(containedField == null, nameof(containedField)); // unreachable code when "isInitialized" is false: _field = containedField; } private bool isInitialized = false; private object _field = default!; }
end example
22.5.7.6 Atribut MaybeNull
Určuje, že návratová hodnota, která není null, může mít hodnotu null.
Příklad: Zvažte následující obecnou metodu:
#nullable enable public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
Myšlenka tohoto kódu je, že pokud
T
je nahrazenastring
,T?
stane se nullable anotace. Tento kód však není právní, protožeT
není omezen na odkazový typ. Přidáním tohoto atributu ale problém vyřešíte:#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
Atribut informuje volající, že kontrakt znamená non-nullable typ, ale návratová hodnota může být
null
skutečně . end example
22.5.7.7 Atribut MaybeNullWhen
Určuje, že nenulový argument může být null
, když metoda vrátí zadanou bool
hodnotu. Podobá se atributu MaybeNull
(§22.5.7.6), ale obsahuje parametr pro zadanou návratovou hodnotu.
22.5.7.8 Atribut NotNull
Určuje, že hodnota s možnou hodnotou null nikdy nebude null
, pokud metoda vrátí (místo vyvolání).
Příklad: Zvažte následující:
#nullable enable public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "") => _ = value ?? throw new ArgumentNullException(valueExpression); public static void LogMessage(string? message) { ThrowWhenNull(message, nameof(message)); Console.WriteLine(message.Length); }
Pokud jsou povoleny odkazové typy null, metoda
ThrowWhenNull
se zkompiluje bez upozornění. Pokud tato metoda vrátí,value
argument je zaručen, že nenínull
. Je však přijatelné volatThrowWhenNull
s nulovým odkazem. end example
22.5.7.9 Atribut NotNullIfNotNull
Určuje, že návratová hodnota není null
, pokud argument pro zadaný parametr není null
.
Příklad: Stav null návratové hodnoty může záviset na stavu null jednoho nebo více argumentů. Chcete-li pomoci s analýzou kompilátoru, můžete použít atribut
NotNullIfNotNull
, když metoda vždy vrátí nenulovou hodnotu, pokud určité argumenty nejsounull
. Zvažte následující metodu:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
url
Pokud argument nenínull
,null
nevrátí se. Pokud jsou povoleny odkazy s možnou hodnotou null, funguje tento podpis správně, pokud rozhraní API nikdy nepřijímá argument null. Pokud však argument může mít hodnotu null, může být vrácená hodnota také null. Chcete-li tento kontrakt správně vyjádřit, anotace této metody následujícím způsobem:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }
end example
22.5.7.10 Atribut NotNullWhen
Určuje, že argument s možnou hodnotou null nebude null
, když metoda vrátí zadanou bool
hodnotu.
Příklad: Metoda
String.IsNullOrEmpty(String)
knihovny vrátítrue
, pokud jenull
argument nebo prázdný řetězec. Jedná se o formu kontroly null: Volající nemusí argument null-check argument, pokud metoda vrátífalse
. Pokud chcete vytvořit metodu, jako je tato s možnou hodnotou null, nastavte typ parametru jako typ odkazu s možnou hodnotou null a přidejte atribut NotNullWhen:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }
end example
22.6 Atributy pro spolupráci
Pro spolupráci s jinými jazyky může být indexer implementován pomocí indexovaných vlastností. Pokud pro indexer neexistuje žádný IndexerName
atribut, použije se název Item
ve výchozím nastavení. Tento IndexerName
atribut umožňuje vývojáři přepsat toto výchozí nastavení a zadat jiný název.
Příklad: Ve výchozím nastavení je
Item
název indexeru . Můžete ho přepsat následujícím způsobem:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }
Teď je název
TheItem
indexeru .end example
ECMA C# draft specification