22 atrybuty
22.1 Ogólne
Większość języka C# umożliwia programistom określanie deklaratywnych informacji o jednostkach zdefiniowanych w programie. Na przykład dostępność metody w klasie jest określana przez dekorowanie jej za pomocą method_modifiers public
, protected
, internal
i private
.
Język C# umożliwia programistom tworzenie nowych rodzajów informacji deklaratywnych nazywanych atrybutami. Programiści mogą następnie dołączać atrybuty do różnych jednostek programu i pobierać informacje o atrybutach w środowisku uruchomieniowym.
Uwaga: na przykład struktura może zdefiniować
HelpAttribute
atrybut, który można umieścić w niektórych elementach programu (takich jak klasy i metody), aby zapewnić mapowanie z tych elementów programu do ich dokumentacji. notatka końcowa
Atrybuty są definiowane za pomocą deklaracji klas atrybutów (§22.2), które mogą mieć parametry pozycyjne i nazwane (§22.2.3). Atrybuty są dołączane do jednostek w programie języka C# przy użyciu specyfikacji atrybutów (§22.3) i można je pobrać w czasie wykonywania jako wystąpienia atrybutów (§22.4).
22.2 Klasy atrybutów
22.2.1 Ogólne
Klasa, która pochodzi z klasy System.Attribute
abstrakcyjnej , niezależnie od tego, czy bezpośrednio, czy pośrednio, jest klasą atrybutów. Deklaracja klasy atrybutów definiuje nowy rodzaj atrybutu, który można umieścić w jednostkach programu. Zgodnie z konwencją klasy atrybutów mają sufiks Attribute
. Użycie atrybutu może zawierać lub pomijać ten sufiks.
Deklaracja klasy ogólnej nie jest używana System.Attribute
jako bezpośrednia lub pośrednia klasa bazowa.
Przykład:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attribute
przykład końcowy
22.2.2 Użycie atrybutu
AttributeUsage
Atrybut (§22.5.2) służy do opisania sposobu użycia klasy atrybutów.
AttributeUsage
ma parametr pozycyjny (§22.2.3), który umożliwia klasie atrybutów określenie rodzajów jednostek programu, na których można go użyć.
Przykład: W poniższym przykładzie zdefiniowano klasę atrybutów o nazwie
SimpleAttribute
, która może zostać umieszczona tylko na class_declarations i interface_declarationi pokazuje kilka zastosowań atrybutuSimple
.[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}
Mimo że ten atrybut jest zdefiniowany z nazwą
SimpleAttribute
, gdy ten atrybut jest używany,Attribute
sufiks może zostać pominięty, co spowoduje krótką nazwęSimple
. W związku z tym powyższy przykład jest semantycznie równoważny z następującymi[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}
przykład końcowy
AttributeUsage
ma nazwany parametr (§22.2.3), o nazwie AllowMultiple
, który wskazuje, czy atrybut można określić więcej niż raz dla danej jednostki. Jeśli AllowMultiple
w przypadku klasy atrybutów ma wartość true, klasa atrybutów jest klasą atrybutów wielokrotnego użycia i można określić więcej niż raz w jednostce. Jeśli AllowMultiple
w przypadku klasy atrybutów jest fałsz lub jest nieokreślony, ta klasa atrybutów jest klasą atrybutów z jednym użyciem i może być określona co najwyżej raz w jednostce.
Przykład: W poniższym przykładzie zdefiniowano klasę atrybutów wielokrotnego użycia o nazwie
AuthorAttribute
i pokazano deklarację klasy z dwoma zastosowaniami atrybutuAuthor
:[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 { ... }
przykład końcowy
AttributeUsage
ma inny nazwany parametr (§22.2.3), o nazwie Inherited
, który wskazuje, czy atrybut, po określeniu w klasie bazowej, jest również dziedziczony przez klasy, które pochodzą z tej klasy bazowej. Jeśli Inherited
dla klasy atrybutu ma wartość true, ten atrybut jest dziedziczony. Jeśli Inherited
dla klasy atrybutu ma wartość false, ten atrybut nie jest dziedziczony. Jeśli nie zostanie określony, jego wartość domyślna to true.
Klasa X
atrybutu nie ma dołączonego do niego atrybutu AttributeUsage
, jak w
class X : Attribute { ... }
jest równoważny z następującymi elementami:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
22.2.3 Parametry pozycyjne i nazwane
Klasy atrybutów mogą mieć parametrypozycyjne i nazwane s. Każdy konstruktor wystąpienia publicznego dla klasy atrybutów definiuje prawidłową sekwencję parametrów pozycyjnych dla tej klasy atrybutów. Każde niestatyczne publiczne pole read-write i właściwość dla klasy atrybutów definiuje nazwany parametr dla klasy atrybutu. Dla właściwości do zdefiniowania nazwanego parametru właściwość ta musi mieć zarówno publiczny dostęp dostępu, jak i publiczny zestaw dostępu.
Przykład: Poniższy przykład definiuje klasę atrybutów o nazwie
HelpAttribute
, która ma jeden parametr pozycyjny,url
i jeden nazwany parametr,Topic
. Chociaż nie jest statyczna i publiczna, właściwośćUrl
nie definiuje nazwanego parametru, ponieważ nie jest to odczyt-zapis. Pokazano również dwa zastosowania tego atrybutu:[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 { }
przykład końcowy
22.2.4 Typy parametrów atrybutów
Typy parametrów pozycyjnych i nazwanych dla klasy atrybutów są ograniczone do typów parametrów atrybutów, które są następujące:
- Jeden z następujących typów:
bool
,char
double
float
short
long
int
byte
string
uint
sbyte
, , .ushort
ulong
- Typ
object
. - Typ
System.Type
. - Typy wyliczenia.
- Tablice jednowymiarowe powyższych typów.
- Argument konstruktora lub pole publiczne, które nie ma jednego z tych typów, nie może być używane jako parametr pozycyjny lub nazwany w specyfikacji atrybutu.
22.3 Specyfikacja atrybutu
Specyfikacja atrybutu to zastosowanie wcześniej zdefiniowanego atrybutu do jednostki programu. Atrybut jest elementem dodatkowych informacji deklaratywnych określonych dla jednostki programu. Atrybuty można określić w zakresie globalnym (aby określić atrybuty w zestawie zawierającym lub module) i dla 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_declaration s (§15.7.3), event_accessor_ deklaracji s (§15.8), elementów parameter_lists (§15.6.2) i elementów type_parameter_lists (§15.2.3).
Atrybuty są określane w sekcjach atrybutów. Sekcja atrybutu składa się z pary nawiasów kwadratowych, które otaczają rozdzielaną przecinkami listę co najmniej jednego atrybutu. Kolejność, w której atrybuty są określone na takiej liście, a kolejność, w której sekcje dołączone do tej samej jednostki programu są rozmieszczone, nie jest istotne. Na przykład specyfikacje atrybutów [A][B]
, [B][A]
, [A, B]
i [B, A]
są równoważne.
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
;
W przypadku global_attribute_target produkcji, a w poniższym tekście identyfikator ma pisownię równą assembly
lub module
, gdzie równość jest określona w §6.4.3. W przypadku attribute_target produkcji, a w poniższym tekście identyfikator zawiera pisownię, która nie jest równa assembly
lub module
, używając tej samej definicji równości co powyżej.
Atrybut składa się z attribute_name i opcjonalnej listy argumentów pozycyjnych i nazwanych. Argumenty pozycyjne (jeśli istnieją) poprzedzają nazwane argumenty. Argument pozycyjny składa się z attribute_argument_expression; nazwany argument składa się z nazwy, a następnie znaku równości, a następnie attribute_argument_expression, które razem są ograniczone przez te same reguły co proste przypisanie. Kolejność nazwanych argumentów nie jest znacząca.
Uwaga: Dla wygody przecinek końcowy jest dozwolony w global_attribute_section i attribute_section, tak jak jeden jest dozwolony w array_initializer (§17.7). notatka końcowa
Attribute_name identyfikuje klasę atrybutów.
Gdy atrybut zostanie umieszczony na poziomie globalnym, wymagany jest global_attribute_target_specifier . Gdy global_attribute_target jest równa:
assembly
— element docelowy jest zestawem zawierającymmodule
— element docelowy jest modułem zawierającym
Żadne inne wartości global_attribute_target nie są dozwolone.
Standardowe nazwy attribute_target to event
, , field
, param
method
, , property
return
, type
i typevar
. Te nazwy docelowe są używane tylko w następujących kontekstach:
event
— zdarzenie.field
— pole. Zdarzenie podobne do pola (tj. jedno bez metod dostępu) (§15.8.2) i automatycznie zaimplementowana właściwość (§15.7.4) może również mieć atrybut z tym celem.method
— konstruktor, finalizator, metoda, operator, właściwości get i set metody dostępu, indeksator get i set metod dostępu oraz dodawanie i usuwanie metod dostępu. Zdarzenie podobne do pola (tj. jedno bez metod dostępu) może również mieć atrybut z tym elementem docelowym.param
— metoda dostępu zestawu właściwości, akcesor zestawu indeksatora, dodawanie i usuwanie metod dostępu oraz parametr w konstruktorze, metodzie i operatorze.property
— właściwość i indeksator.return
— delegat, metoda, operator, właściwość get accessor i indeksator uzyskać metodę dostępu.type
— delegat, klasa, struktura, wyliczenie i interfejs.typevar
— parametr typu.
Niektóre konteksty zezwalają na specyfikację atrybutu na więcej niż jednym obiekcie docelowym. Program może jawnie określić cel, dołączając attribute_target_specifier. Bez attribute_target_specifier jest stosowany domyślny, ale attribute_target_specifier można użyć do potwierdzenia lub zastąpienia wartości domyślnej. Konteksty są rozpoznawane w następujący sposób:
- W przypadku atrybutu w deklaracji delegata domyślnym elementem docelowym jest delegat. W przeciwnym razie, gdy attribute_target jest równa:
type
— elementem docelowym jest delegatreturn
— element docelowy jest wartością zwracaną
- W przypadku atrybutu w deklaracji metody domyślny element docelowy to metoda. W przeciwnym razie, gdy attribute_target jest równa:
method
— elementem docelowym jest metodareturn
— element docelowy jest wartością zwracaną
- Dla atrybutu w deklaracji operatora domyślnym elementem docelowym jest operator. W przeciwnym razie, gdy attribute_target jest równa:
method
— element docelowy jest operatoremreturn
— element docelowy jest wartością zwracaną
- W przypadku atrybutu w deklaracji get metod dostępu dla właściwości lub deklaracji indeksatora domyślnym obiektem docelowym jest skojarzona metoda. W przeciwnym razie, gdy attribute_target jest równa:
method
— elementem docelowym jest skojarzona metodareturn
— element docelowy jest wartością zwracaną
- W przypadku atrybutu określonego w ustawie metody dostępu dla właściwości lub deklaracji indeksatora domyślnym celem jest skojarzona metoda. W przeciwnym razie, gdy attribute_target jest równa:
method
— elementem docelowym jest skojarzona metodaparam
— element docelowy to samotny niejawny parametr
- W przypadku atrybutu w automatycznie zaimplementowanym deklaracji właściwości domyślny element docelowy jest właściwością. W przeciwnym razie, gdy attribute_target jest równa:
field
— elementem docelowym jest pole kopii zapasowej generowane przez kompilator dla właściwości
- W przypadku atrybutu określonego w deklaracji zdarzenia pomija event_accessor_declarations domyślnym elementem docelowym jest deklaracja zdarzenia. W przeciwnym razie, gdy attribute_target jest równa:
event
— element docelowy jest deklaracją zdarzeniafield
— element docelowy jest polemmethod
— cele są metodami
- W przypadku deklaracji zdarzenia, która nie pomija event_accessor_declarations domyślnym celem jest metoda.
method
— elementem docelowym jest skojarzona metodaparam
— element docelowy jest samotnym parametrem
We wszystkich innych kontekstach włączenie attribute_target_specifier jest dozwolone, ale niepotrzebne.
Przykład: deklaracja klasy może zawierać lub pomijać specyfikator
type
:[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}
przykład końcowy.
Implementacja może akceptować inne attribute_target, których celem jest zdefiniowana implementacja. Wdrożenie, które nie uznaje takiego attribute_target , wydaje ostrzeżenie i ignoruje zawierające attribute_section.
Zgodnie z konwencją klasy atrybutów mają sufiks Attribute
. Attribute_name może zawierać lub pomijać ten sufiks. W szczególności attribute_name jest rozpoznawana w następujący sposób:
- Jeśli najbardziej odpowiedni identyfikator attribute_name jest identyfikatorem dosłownym (§6.4.3), attribute_name jest rozpoznawany jako type_name (§7.8). Jeśli wynik nie jest typem pochodzącym z
System.Attribute
klasy , wystąpi błąd czasu kompilacji. - Inaczej
- Attribute_name jest rozpoznawany jako type_name (§7.8), z wyjątkiem błędów są pomijane. Jeśli ta rozdzielczość zakończy się pomyślnie i wyniknie typu pochodzącego z
System.Attribute
tego typu, jest to wynik tego kroku. - Znaki
Attribute
są dołączane do najbardziej odpowiedniego identyfikatora w attribute_name , a wynikowy ciąg tokenów jest rozpoznawany jako type_name (§7.8), z wyjątkiem błędów są pomijane. Jeśli ta rozdzielczość zakończy się pomyślnie i wyniknie typu pochodzącego zSystem.Attribute
tego typu, jest to wynik tego kroku.
- Attribute_name jest rozpoznawany jako type_name (§7.8), z wyjątkiem błędów są pomijane. Jeśli ta rozdzielczość zakończy się pomyślnie i wyniknie typu pochodzącego z
Jeśli dokładnie jeden z dwóch powyższych kroków powoduje wystąpienie typu pochodzącego z System.Attribute
klasy , typ ten jest wynikiem attribute_name. W przeciwnym razie wystąpi błąd czasu kompilacji.
Przykład: jeśli klasa atrybutów zostanie znaleziona zarówno z sufiksem, jak i bez tego sufiksu, występuje niejednoznaczność i wyniki błędu czasu kompilacji. Jeśli attribute_name jest wpisana tak, że jego najbardziej odpowiedni identyfikator jest identyfikatorem dosłownym (§6.4.3), zostanie dopasowany tylko atrybut bez sufiksu, co umożliwi rozwiązanie takiej niejednoznaczności. Przykład
[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 {}
wyświetla dwie klasy atrybutów o nazwach
Example
iExampleAttribute
.[Example]
Atrybut jest niejednoznaczny, ponieważ może odwoływać się do elementuExample
lubExampleAttribute
. Użycie identyfikatora dosłownego umożliwia określenie dokładnej intencji w takich rzadkich przypadkach.[ExampleAttribute]
Atrybut nie jest niejednoznaczny (chociaż byłoby, gdyby istniała klasa atrybutów o nazwieExampleAttributeAttribute
!). Jeśli deklaracja klasyExample
zostanie usunięta, oba atrybuty odwołują się do klasy atrybutów o nazwieExampleAttribute
, w następujący sposób:[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 {}
przykład końcowy
Jest to błąd czasu kompilacji, aby użyć klasy atrybutów pojedynczego użycia więcej niż raz w tej samej jednostce.
Przykład: przykład
[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 {}
powoduje błąd czasu kompilacji, ponieważ próbuje użyć
HelpString
klasy , która jest klasą atrybutów pojedynczego użycia, więcej niż raz w deklaracjiClass1
.przykład końcowy
Wyrażenie E
jest attribute_argument_expression , jeśli wszystkie następujące instrukcje są prawdziwe:
- Typ jest typem parametru atrybutu
E
(§22.2.4). - W czasie kompilacji wartość parametru
E
można rozpoznać na jedną z następujących wartości:
Przykład:
[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; }
przykład końcowy
Atrybuty typu zadeklarowanego w wielu częściach są określane przez połączenie w nieokreślonej kolejności atrybutów każdego z jego części. Jeśli ten sam atrybut jest umieszczany w wielu częściach, jest odpowiednikiem określenia tego atrybutu wiele razy w typie.
Przykład: dwie części:
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}
są równoważne następującej pojedynczej deklaracji:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}
przykład końcowy
Atrybuty parametrów typu łączą się w ten sam sposób.
22.4 Wystąpienia atrybutów
22.4.1 Ogólne
Wystąpienie atrybutu to wystąpienie reprezentujące atrybut w czasie wykonywania. Atrybut jest definiowany z klasą atrybutów, argumentami pozycyjnymi i nazwanymi argumentami. Wystąpienie atrybutu to wystąpienie klasy atrybutów zainicjowane za pomocą argumentów pozycyjnych i nazwanych.
Pobieranie wystąpienia atrybutu obejmuje zarówno przetwarzanie w czasie kompilacji, jak i w czasie wykonywania, zgodnie z opisem w poniższych podklasach.
22.4.2 Kompilacja atrybutu
Kompilacja atrybutu z klasą T
atrybutów , positional_argument_list P
, named_argument_listN
i określona w jednostce E
programu jest kompilowana w zestawie A
za pomocą następujących kroków:
- Wykonaj kroki przetwarzania w czasie kompilacji, aby skompilować object_creation_expression nowego
T(P)
formularza . Te kroki powodują błąd w czasie kompilacji lub określenie konstruktoraC
T
wystąpienia, który można wywołać w czasie wykonywania. - Jeśli
C
nie ma dostępu publicznego, wystąpi błąd czasu kompilacji. - Dla każdego named_argument
Arg
w plikuN
:- Zajmijmy
Name
się identyfikatorem named_argument .Arg
Name
identyfikuje niestatyczne pole publiczne odczytu i zapisu lub właściwość w obiekcieT
. JeśliT
nie ma takiego pola lub właściwości, wystąpi błąd czasu kompilacji.
- Zajmijmy
- Jeśli którakolwiek z wartości w positional_argument_list lub jednej z wartości w named_argument_list
N
P
jest typuSystem.String
, a wartość nie jest poprawnie sformułowana zgodnie ze standardem Unicode, jest definiowana przez implementację, czy skompilowana wartość jest równa wartości czasu wykonywania pobranej (§22.4.3).Uwaga: Na przykład ciąg, który zawiera wysoką zastępczą jednostkę kodu UTF-16, która nie jest natychmiast obserwowana przez niską jednostkę kodu zastępczego, nie jest dobrze sformułowana. notatka końcowa
- Zapisz następujące informacje (w przypadku wystąpienia atrybutu w czasie wykonywania) w danych wyjściowych zestawu przez kompilator w wyniku kompilowania programu zawierającego atrybut: klasy
T
atrybutu , konstruktoraC
wystąpienia wT
obiekcie , positional_argument_listP
, named_argument_listN
i skojarzonej jednostkiE
programu , z wartościami rozwiązanymi całkowicie w czasie kompilacji.
22.4.3 Pobieranie wystąpienia atrybutu w czasie wykonywania
Korzystając z terminów zdefiniowanych w §22.4.2, wystąpienie atrybutu reprezentowane przez T
, , C
P
i N
, E
można pobrać w czasie wykonywania z zestawuA
, wykonując następujące kroki:
- Wykonaj kroki przetwarzania w czasie wykonywania object_creation_expression formularza
new T(P)
przy użyciu konstruktoraC
wystąpienia i wartości określonych w czasie kompilacji. Te kroki powodują wyjątek lub tworzą wystąpienie klasyO
T
. - Dla każdego named_argument
Arg
wN
kolejności:- Zajmijmy
Name
się identyfikatorem named_argument .Arg
JeśliName
nie zidentyfikuje niestacjonanego publicznego pola odczytu i zapisu lub właściwości wO
obiekcie , zostanie zgłoszony wyjątek. - Niech
Value
wynik oceny attribute_argument_expression wartościArg
. - Jeśli
Name
zidentyfikuje pole w poleO
, ustaw to pole naValue
wartość . - W przeciwnym razie nazwa identyfikuje właściwość na .
O
Ustaw tę właściwość na Wartość. - Wynikiem jest
O
wystąpienie klasyT
atrybutów, która została zainicjowana przy użyciu positional_argument_listP
i named_argument_listN
.
- Zajmijmy
Uwaga: format przechowywania
T
, ,C
,P
N
(i kojarzenia go zE
) wA
oraz mechanizmE
określania i pobieraniaT
, ,P
C
N
, zA
(a tym samym sposobu uzyskiwania wystąpienia atrybutu w czasie wykonywania) wykracza poza zakres tej specyfikacji. notatka końcowa
Przykład: W implementacji interfejsu wiersza polecenia
Help
wystąpienia atrybutów w zestawie utworzonym przez skompilowanie przykładowego programu w §22.2.3 można pobrać za pomocą następującego 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}"); } } } }
przykład końcowy
22.5 Atrybuty zarezerwowane
22.5.1 Ogólne
Wiele atrybutów wpływa na język w jakiś sposób. Są to następujące elementy:
System.AttributeUsageAttribute
(§22.5.2), który służy do opisywania sposobów użycia klasy atrybutów.System.Diagnostics.ConditionalAttribute
(§22.5.3) to wieloużytkowa klasa atrybutów, która służy do definiowania metod warunkowych i klas atrybutów warunkowych. Ten atrybut wskazuje warunek, testując symbol kompilacji warunkowej.System.ObsoleteAttribute
(§22.5.4), który jest używany do oznaczania elementu członkowskiego jako przestarzały.System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
(§22.5.5), który służy do ustanowienia konstruktora zadań dla metody asynchronicznej.System.Runtime.CompilerServices.CallerLineNumberAttribute
(§22.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute
(§22.5.6.3) iSystem.Runtime.CompilerServices.CallerMemberNameAttribute
(§22.5.6.4), które służą do dostarczania informacji o kontekście wywołującym do parametrów opcjonalnych.
Atrybuty analizy statycznej dopuszczające wartość null (§22.5.7) mogą poprawić poprawność ostrzeżeń generowanych dla wartości null i stanów null (§8.9.5).
Środowisko wykonywania może udostępniać dodatkowe atrybuty zdefiniowane przez implementację, które wpływają na wykonywanie programu w języku C#.
22.5.2 AtrybutUsage atrybutu
AttributeUsage
Atrybut służy do opisywania sposobu, w jaki można użyć klasy atrybutów.
Klasa, która jest ozdobiona atrybutem AttributeUsage
, pochodzi z System.Attribute
klasy , bezpośrednio lub pośrednio. W przeciwnym razie wystąpi błąd czasu kompilacji.
Uwaga: aby uzyskać przykład użycia tego atrybutu, zobacz §22.2.2. notatka końcowa
22.5.3 Atrybut warunkowy
22.5.3.1 Ogólne
Conditional
Atrybut umożliwia definicję metod warunkowych i klas atrybutów warunkowych.
22.5.3.2 Metody warunkowe
Metoda ozdobiona atrybutem Conditional
jest metodą warunkową. Każda metoda warunkowa jest zatem skojarzona z symbolami kompilacji warunkowej zadeklarowanymi w jej Conditional
atrybutach.
Przykład:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }
Eg.M
deklaruje jako metodę warunkową skojarzona z dwoma symbolamiALPHA
kompilacji warunkowej iBETA
.przykład końcowy
Wywołanie metody warunkowej jest uwzględniane, jeśli co najmniej jeden ze skojarzonych symboli kompilacji warunkowej jest zdefiniowany w momencie wywołania, w przeciwnym razie wywołanie zostanie pominięte.
Metoda warunkowa podlega następującym ograniczeniom:
- Metoda warunkowa jest metodą w class_declaration lub struct_declaration. Błąd czasu kompilacji występuje, jeśli
Conditional
atrybut jest określony w metodzie w deklaracji interfejsu. - Metoda warunkowa ma zwracany typ
void
. - Metoda warunkowa nie jest oznaczona modyfikatorem
override
. Metodę warunkową można jednak oznaczyć modyfikatoremvirtual
. Zastąpienia takiej metody są niejawnie warunkowe i nie są jawnie oznaczone atrybutemConditional
. - Metoda warunkowa nie jest implementacją metody interfejsu. W przeciwnym razie wystąpi błąd czasu kompilacji.
- Parametry metody warunkowej nie są parametrami wyjściowymi.
Ponadto błąd czasu kompilacji występuje, jeśli delegat jest tworzony na podstawie metody warunkowej.
Przykład: przykład
#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(); } }
Class1.M
deklaruje jako metodę warunkową.Class2
Test
Metoda wywołuje tę metodę. Ponieważ zdefiniowany jest symbolDEBUG
kompilacji warunkowej, jeśliClass2.Test
jest wywoływany, wywołaM
element . Gdyby symbolDEBUG
nie został zdefiniowany,Class2.Test
nie wywołałby metodyClass1.M
.przykład końcowy
Ważne jest, aby zrozumieć, że włączenie lub wykluczenie wywołania metody warunkowej jest kontrolowane przez symbole kompilacji warunkowej w momencie wywołania.
Przykład: w poniższym kodzie
// 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 } }
klasy
Class2
iClass3
każdy zawiera wywołania metodyClass1.F
warunkowej , która jest warunkowa na podstawie tego, czyDEBUG
jest zdefiniowana. Ponieważ ten symbol jest zdefiniowany w kontekście ,Class2
ale nieClass3
, wywołanie doF
elementuClass2
jest uwzględniane, podczas gdy wywołanie metodyF
inClass3
jest pomijane.przykład końcowy
Użycie metod warunkowych w łańcuchu dziedziczenia może być mylące. Wywołania wykonywane do metody warunkowej za pośrednictwem base
formularza base.M
, podlegają normalnym regułom wywołania metody warunkowej.
Przykład: w poniższym kodzie
// 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
element zawiera wywołanie elementu zdefiniowanego w klasie bazowejM
. To wywołanie zostanie pominięte, ponieważ metoda podstawowa jest warunkowa na podstawie obecności symboluDEBUG
, który jest niezdefiniowany. W związku z tym metoda zapisuje tylko w konsoli "Class2.M executed
". Rozsądne wykorzystanie pp_declarations może wyeliminować takie problemy.przykład końcowy
22.5.3.3 Klasy atrybutów warunkowych
Klasa atrybutów (§22.2) ozdobiona co najmniej jednym Conditional
atrybutem jest klasą atrybutów warunkowych. Klasa atrybutów warunkowych jest zatem skojarzona z symbolami kompilacji warunkowej zadeklarowanymi w jej Conditional
atrybutach.
Przykład:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
TestAttribute
deklaruje jako klasę atrybutów warunkowych skojarzona z symbolami kompilacji warunkowychALPHA
iBETA
.przykład końcowy
Specyfikacje atrybutów (§22.3) atrybutu warunkowego są uwzględniane, jeśli co najmniej jeden ze skojarzonych symboli kompilacji warunkowej jest zdefiniowany w punkcie specyfikacji, w przeciwnym razie specyfikacja atrybutu zostanie pominięta.
Należy pamiętać, że włączenie lub wykluczenie specyfikacji atrybutu klasy atrybutu warunkowego jest kontrolowane przez symbole kompilacji warunkowej w punkcie specyfikacji.
Przykład: w przykładzie
// 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 {}
klasy
Class1
iClass2
są ozdobione atrybutemTest
, który jest warunkowy na podstawie tego, czyDEBUG
jest zdefiniowany. Ponieważ ten symbol jest zdefiniowany w kontekście elementuClass1
, ale nieClass2
, uwzględniana jest specyfikacja atrybutuClass1
Test, podczas gdy specyfikacja atrybutuTest
onClass2
zostanie pominięta.przykład końcowy
22.5.4 Przestarzały atrybut
Obsolete
Atrybut służy do oznaczania typów i elementów członkowskich typów, które nie powinny być już używane.
Jeśli program używa typu lub elementu członkowskiego ozdobionego atrybutem Obsolete
, kompilator wystawi ostrzeżenie lub błąd. W szczególności kompilator wydaje ostrzeżenie, jeśli nie podano parametru błędu lub jeśli parametr błędu jest podany i ma wartość false
. Kompilator wystawi błąd, jeśli określono parametr błędu i ma wartość true
.
Przykład: w poniższym kodzie
[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(); } }
klasa
A
jest ozdobiona atrybutemObsolete
. Każde użycieA
w programie powodujeMain
wyświetlenie ostrzeżenia zawierającego określony komunikat "Ta klasa jest przestarzała; zamiast tego użyj klasyB
".przykład końcowy
22.5.5 Atrybut AsyncMethodBuilder
Ten atrybut jest opisany w §15.15.1.
22.5.6 Atrybuty rozmówców
22.5.6.1 Ogólne
Do celów, takich jak rejestrowanie i raportowanie, czasami przydatne jest uzyskanie pewnych informacji w czasie kompilacji dotyczących kodu wywołującego. Atrybuty rozmówców zapewniają sposób przekazywania takich informacji w sposób niewidoczny.
Jeśli opcjonalny parametr jest adnotacją z jednym z atrybutów caller-info, pominięcie odpowiedniego argumentu w wywołaniu niekoniecznie powoduje zastąpienie domyślnej wartości parametru. Zamiast tego, jeśli są dostępne określone informacje o kontekście wywołującym, te informacje zostaną przekazane jako wartość argumentu.
Przykład:
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); }
Wywołanie polecenia
Log()
bez argumentów spowoduje wyświetlenie numeru wiersza i ścieżki pliku wywołania, a także nazwy elementu członkowskiego, w którym wystąpiło wywołanie.przykład końcowy
Atrybuty caller-info mogą występować w parametrach opcjonalnych w dowolnym miejscu, w tym w deklaracjach delegatów. Jednak określone atrybuty caller-info mają ograniczenia dotyczące typów parametrów, które mogą atrybuty, aby zawsze istniała niejawna konwersja z wartości zastępczej na typ parametru.
Jest to błąd, aby mieć ten sam atrybut caller-info dla parametru zarówno definiującego, jak i implementowania części części deklaracji metody częściowej. Stosowane są tylko atrybuty caller-info w części definiującej, natomiast atrybuty caller-info występujące tylko w części implementowania są ignorowane.
Informacje o obiekcie wywołującym nie mają wpływu na rozpoznawanie przeciążenia. Ponieważ przypisane parametry opcjonalne są nadal pomijane z kodu źródłowego obiektu wywołującego, rozwiązanie przeciążenia ignoruje te parametry w taki sam sposób, jak ignoruje inne pominięte parametry opcjonalne (§12.6.4).
Informacje wywołujące są zastępowane tylko wtedy, gdy funkcja jest jawnie wywoływana w kodzie źródłowym. Niejawne wywołania, takie jak niejawne wywołania konstruktora nadrzędnego, nie mają lokalizacji źródłowej i nie zastąpią informacji wywołujących. Ponadto wywołania, które są dynamicznie powiązane, nie zastąpią informacji wywołujących. Jeśli parametr atrybutu caller-info zostanie pominięty w takich przypadkach, zamiast tego zostanie użyta określona wartość domyślna parametru.
Jednym z wyjątków jest wyrażenie zapytania. Są one uważane za rozszerzenia składniowe, a jeśli wywołania rozszerzają się w celu pominięcia opcjonalnych parametrów z atrybutami caller-info, informacje wywołujące zostaną zastąpione. Używana lokalizacja to lokalizacja klauzuli zapytania, z której zostało wygenerowane wywołanie.
Jeśli dla danego parametru określono więcej niż jeden atrybut caller-info, są one rozpoznawane w następującej kolejności: CallerLineNumber
, , CallerFilePath
CallerMemberName
. Rozważ następującą deklarację parametru:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber
ma pierwszeństwo, a pozostałe dwa atrybuty są ignorowane. Gdyby CallerLineNumber
pominięto, CallerFilePath
pierwszeństwo miałoby pierwszeństwo i CallerMemberName
byłoby ignorowane. Porządkowanie leksykalne tych atrybutów jest nieistotne.
22.5.6.2 Atrybut CallerLineNumber
Atrybut System.Runtime.CompilerServices.CallerLineNumberAttribute
jest dozwolony dla parametrów opcjonalnych, gdy istnieje standardowa niejawna konwersja (§10.4.2) z wartości int.MaxValue
stałej na typ parametru. Gwarantuje to, że dowolna nieujemna liczba wierszy do tej wartości może zostać przekazana bez błędu.
Jeśli wywołanie funkcji z lokalizacji w kodzie źródłowym pomija opcjonalny parametr z CallerLineNumberAttribute
, to literał liczbowy reprezentujący numer wiersza tej lokalizacji jest używany jako argument wywołania zamiast domyślnej wartości parametru.
Jeśli wywołanie obejmuje wiele wierszy, wybrana linia jest zależna od implementacji.
Numer wiersza może mieć wpływ na #line
dyrektywy (§6.5.8).
22.5.6.3 Atrybut CallerFilePath
Atrybut System.Runtime.CompilerServices.CallerFilePathAttribute
jest dozwolony dla parametrów opcjonalnych, gdy istnieje standardowa niejawna konwersja (§10.4.2) z string
do typu parametru.
Jeśli wywołanie funkcji z lokalizacji w kodzie źródłowym pomija opcjonalny parametr z CallerFilePathAttribute
parametrem , literał ciągu reprezentujący ścieżkę pliku tej lokalizacji jest używany jako argument wywołania zamiast domyślnej wartości parametru.
Format ścieżki pliku jest zależny od implementacji.
Na ścieżkę pliku mogą mieć wpływ #line
dyrektywy (§6.5.8).
22.5.6.4 Atrybut CallerMemberName
Atrybut System.Runtime.CompilerServices.CallerMemberNameAttribute
jest dozwolony dla parametrów opcjonalnych, gdy istnieje standardowa niejawna konwersja (§10.4.2) z string
do typu parametru.
Jeśli wywołanie funkcji z lokalizacji w treści elementu członkowskiego funkcji lub w atrybucie zastosowanym do samego elementu członkowskiego funkcji lub jego zwracanym typem, parametrami lub parametrami typu w kodzie źródłowym pomija opcjonalny parametr z CallerMemberNameAttribute
parametrem , to literał ciągu reprezentujący nazwę tego elementu członkowskiego jest używany jako argument wywołania zamiast domyślnej wartości parametru.
W przypadku wywołań, które występują w metodach ogólnych, używana jest tylko sama nazwa metody bez listy parametrów typu.
W przypadku wywołań, które występują w jawnych implementacjach składowych interfejsu, używana jest tylko sama nazwa metody bez wcześniejszej kwalifikacji interfejsu.
W przypadku wywołań, które występują we właściwości lub akcesorach zdarzeń, używana nazwa elementu członkowskiego jest właściwością lub zdarzeniem.
W przypadku wywołań, które występują w akcesorach indeksatora, używana nazwa elementu członkowskiego jest dostarczana przez IndexerNameAttribute
element członkowski (§22.6) na składowej indeksatora, jeśli jest obecna, lub nazwa Item
domyślna w przeciwnym razie.
W przypadku wywołań, które występują w inicjatorach pól lub zdarzeń, używana nazwa elementu członkowskiego jest nazwą inicjowanego pola lub zdarzenia.
W przypadku wywołań, które występują w deklaracjach konstruktorów wystąpień, konstruktorów statycznych, finalizatorów i operatorów używana nazwa elementu członkowskiego jest zależna od implementacji.
22.5.7 Atrybuty analizy kodu
22.5.7.1 Ogólne
Atrybuty w tej sekcji służą do dostarczania dodatkowych informacji w celu obsługi kompilatora, który zapewnia diagnostykę dopuszczającą wartość null i stan null (§8.9.5). Kompilator nie jest wymagany do wykonania żadnej diagnostyki stanu null. Obecność lub brak tych atrybutów nie wpływa na język ani zachowanie programu. Kompilator, który nie zapewnia diagnostyki stanu null, odczytuje i ignoruje obecność tych atrybutów. Kompilator, który zapewnia diagnostykę stanu null, używa znaczenia zdefiniowanego w tej sekcji dla każdego z tych atrybutów, których używa do informowania o jego diagnostyce.
Atrybuty analizy kodu są deklarowane w przestrzeni nazw System.Diagnostics.CodeAnalysis
.
Atrybut | Znaczenie |
---|---|
AllowNull (§22.5.7.2) |
Argument bez wartości null może mieć wartość null. |
DisallowNull (§22.5.7.3) |
Argument dopuszczalny do wartości null nigdy nie powinien mieć wartości null. |
MaybeNull (§22.5.7.6) |
Wartość zwracana bez wartości null może mieć wartość null. |
NotNull (§22.5.7.8) |
Zwracana wartość dopuszczana do wartości null nigdy nie będzie równa null. |
MaybeNullWhen (§22.5.7.7) |
Argument bez wartości null może mieć wartość null, gdy metoda zwraca określoną bool wartość. |
NotNullWhen (§22.5.7.10) |
Argument dopuszczany do wartości null nie będzie mieć wartości null, gdy metoda zwraca określoną bool wartość. |
NotNullIfNotNull (§22.5.7.9) |
Wartość zwracana nie ma wartości null, jeśli argument dla określonego parametru nie ma wartości null. |
DoesNotReturn (§22.5.7.4) |
Ta metoda nigdy nie zwraca. |
DoesNotReturnIf (§22.5.7.5) |
Ta metoda nigdy nie zwraca wartości, jeśli skojarzony bool parametr ma określoną wartość. |
Poniższe sekcje w §22.5.7.1 są warunkowo normatywne.
22.5.7.2 Atrybut AllowNull
Określa, że wartość null jest dozwolona jako dane wejściowe, nawet jeśli odpowiedni typ nie zezwala na to.
Przykład: rozważ następującą właściwość odczytu/zapisu, która nigdy nie zwraca wartości
null
, ponieważ ma rozsądną wartość domyślną. Jednak użytkownik może nadać wartość null zestawowi dostępu, aby ustawić właściwość na tę wartość domyślną.#nullable enable public class X { [AllowNull] public string ScreenName { get => _screenName; set => _screenName = value ?? GenerateRandomScreenName(); } private string _screenName = GenerateRandomScreenName(); private static string GenerateRandomScreenName() => ...; }
Biorąc pod uwagę następujące użycie metody dostępu zestawu tej właściwości
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNull
bez atrybutu kompilator może wygenerować ostrzeżenie, ponieważ właściwość typu innego niż null wydaje się być ustawiona na wartość null. Obecność atrybutu pomija to ostrzeżenie. przykład końcowy
22.5.7.3 Atrybut DisallowNull
Określa, że wartość null jest niedozwolona jako dane wejściowe, nawet jeśli odpowiedni typ na to zezwala.
Przykład: Rozważmy następującą właściwość, w której wartość null jest wartością domyślną, ale klienci mogą ustawić ją tylko na wartość inną niż 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; }
Metodę pobierania można zwrócić wartość
null
domyślną , więc kompilator może ostrzegać, że musi zostać sprawdzony przed uzyskaniem dostępu. Ponadto ostrzega osoby wywołujące, że mimo że może to być wartość null, osoby wywołujące nie powinny jawnie ustawić go na wartość null. przykład końcowy
22.5.7.4 Atrybut DoesNotReturn
Określa, że dana metoda nigdy nie zwraca.
Przykład: Rozważ następujące kwestie:
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; }
Obecność atrybutu pomaga kompilatorowi na wiele sposobów. Najpierw kompilator może wydać ostrzeżenie, jeśli istnieje ścieżka, w której metoda może zakończyć działanie bez zgłaszania wyjątku. Po drugie kompilator może pominąć ostrzeżenia dopuszczające wartość null w dowolnym kodzie po wywołaniu tej metody do momentu znalezienia odpowiedniej klauzuli catch. Po trzecie, niemożliwy do osiągnięcia kod nie będzie mieć wpływu na żadne stany null.
Atrybut nie zmienia osiągalności (§13.2) ani określonej analizy przypisania (§9.4) na podstawie obecności tego atrybutu. Służy tylko do wywierania wpływu na ostrzeżenia o wartości null. przykład końcowy
22.5.7.5 Atrybut DoesNotReturnIf
Określa, że dana metoda nigdy nie zwraca, jeśli skojarzony bool
parametr ma określoną wartość.
Przykład: Rozważ następujące kwestie:
#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!; }
przykład końcowy
22.5.7.6 Atrybut MaybeNull
Określa, że wartość zwracana bez wartości null może mieć wartość null.
Przykład: Rozważ następującą metodę ogólną:
#nullable enable public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
Chodzi o to, że jeśli
T
zostanie zastąpiony przezstring
element ,T?
stanie się adnotacją dopuszczającą wartość null. Jednak ten kod nie jest legalny, ponieważT
nie jest ograniczony do typu odwołania. Jednak dodanie tego atrybutu rozwiązuje problem:#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
Atrybut informuje obiekty wywołujące, że kontrakt oznacza typ niezwiązany z wartością null, ale wartość zwracana może być
null
rzeczywiście . przykład końcowy
22.5.7.7 Atrybut MaybeNullWhen
Określa, że argument bez wartości null może być null
, gdy metoda zwraca określoną bool
wartość. Jest to podobne do atrybutu MaybeNull
(§22.5.7.6), ale zawiera parametr dla określonej wartości zwracanej.
22.5.7.8 Atrybut NotNull
Określa, że wartość dopuszczana do wartości null nigdy nie będzie, null
jeśli metoda zwróci wartość (zamiast zgłaszać).
Przykład: Rozważ następujące kwestie:
#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); }
Jeśli typy odwołań o wartości null są włączone, metoda
ThrowWhenNull
kompiluje się bez ostrzeżeń. Gdy ta metoda zwróci wartość ,value
argument nie będzienull
mieć wartości . Jednak dopuszczalne jest wywołanieThrowWhenNull
przy użyciu odwołania o wartości null. przykład końcowy
22.5.7.9 Atrybut NotNullIfNotNull
Określa, że wartość zwracana nie null
jest, jeśli argument dla określonego parametru nie null
jest .
Przykład: stan null wartości zwracanej może zależeć od stanu null co najmniej jednego argumentu. Aby ułatwić analizę kompilatora, gdy metoda zawsze zwraca wartość inną niż null, gdy niektóre argumenty nie
null
są atrybutemNotNullIfNotNull
, może być używany. Rozważmy następującą metodę:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
url
Jeśli argument nienull
null
jest , nie jest zwracany. Po włączeniu odwołań dopuszczających wartość null podpis działa poprawnie, pod warunkiem, że interfejs API nigdy nie akceptuje argumentu o wartości null. Jeśli jednak argument może mieć wartość null, zwracana wartość może być również równa null. Aby poprawnie wyrazić ten kontrakt, dodaj adnotację do tej metody w następujący sposób:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }
przykład końcowy
22.5.7.10 Atrybut NotNullWhen
Określa, że argument dopuszczający wartość null nie będzie, null
gdy metoda zwraca określoną bool
wartość.
Przykład: metoda
String.IsNullOrEmpty(String)
biblioteki zwracatrue
wartość , gdy argument jestnull
lub pusty ciąg. Jest to forma sprawdzania wartości null: Osoby wywołujące nie muszą sprawdzać argumentu o wartości null, jeśli metoda zwracafalse
wartość . Aby utworzyć metodę podobną do tej dopuszczanej do wartości null, ustaw parametr jako typ odwołania dopuszczalnego do wartości null i dodaj atrybut NotNullWhen:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }
przykład końcowy
22.6 Atrybuty dla współdziałania
W przypadku współdziałania z innymi językami indeksator może zostać zaimplementowany przy użyciu właściwości indeksowanych. Jeśli żaden atrybut nie IndexerName
istnieje dla indeksatora, nazwa Item
jest używana domyślnie. Atrybut IndexerName
umożliwia deweloperowi zastąpienie tej wartości domyślnej i określenie innej nazwy.
Przykład: domyślnie nazwa indeksatora to
Item
. Można to zastąpić w następujący sposób:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }
Teraz nazwa indeksatora to
TheItem
.przykład końcowy
ECMA C# draft specification