Udostępnij za pośrednictwem


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, internali 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.Attributeabstrakcyjnej , 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ń atrybutu Simple .

[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 atrybutu Author :

[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, urli 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, chardoublefloatshortlongintbytestringuintsbyte, , . ushortulong
  • 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ącym
  • module — element docelowy jest modułem zawierającym

Żadne inne wartości global_attribute_target nie są dozwolone.

Standardowe nazwy attribute_target to event, , field, parammethod, , propertyreturn, typei 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 delegat
    • return — 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 metoda
    • return — 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 operatorem
    • return — 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 metoda
    • return — 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 metoda
    • param — 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ą zdarzenia
    • field — element docelowy jest polem
    • method — cele są metodami
  • W przypadku deklaracji zdarzenia, która nie pomija event_accessor_declarations domyślnym celem jest metoda.
    • method — elementem docelowym jest skojarzona metoda
    • param — 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.Attributeklasy , 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 z System.Attribute tego typu, jest to wynik tego kroku.

Jeśli dokładnie jeden z dwóch powyższych kroków powoduje wystąpienie typu pochodzącego z System.Attributeklasy , 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 i ExampleAttribute. [Example] Atrybut jest niejednoznaczny, ponieważ może odwoływać się do elementu Example lub ExampleAttribute. 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 nazwie ExampleAttributeAttribute!). Jeśli deklaracja klasy Example zostanie usunięta, oba atrybuty odwołują się do klasy atrybutów o nazwie ExampleAttribute, 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ć HelpStringklasy , która jest klasą atrybutów pojedynczego użycia, więcej niż raz w deklaracji Class1.

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:
    • Stała wartość.
    • System.Type Obiekt uzyskany przy użyciu typeof_expression (§12.8.18) określający typ niegeneryczny, zamknięty typ skonstruowany (§8.4.3) lub niezwiązany typ ogólny (§8.4.4), ale nie typ otwarty (§8.4.3).
    • Jednowymiarowa tablica attribute_argument_expressions.

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ą Tatrybutó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 konstruktora C 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 pliku N:
    • Zajmijmy Name się identyfikatorem named_argument . Arg
    • Name identyfikuje niestatyczne pole publiczne odczytu i zapisu lub właściwość w obiekcie T. Jeśli T nie ma takiego pola lub właściwości, wystąpi błąd czasu kompilacji.
  • Jeśli którakolwiek z wartości w positional_argument_list lub jednej z wartości w named_argument_listNP 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 Tatrybutu , konstruktora C wystąpienia w Tobiekcie , positional_argument_list P , named_argument_listN i skojarzonej jednostki Eprogramu , 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, , CPi 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 konstruktora C wystąpienia i wartości określonych w czasie kompilacji. Te kroki powodują wyjątek lub tworzą wystąpienie klasy O T.
  • Dla każdego named_argument Arg w Nkolejności:
    • Zajmijmy Name się identyfikatorem named_argument . Arg Jeśli Name nie zidentyfikuje niestacjonanego publicznego pola odczytu i zapisu lub właściwości w Oobiekcie , zostanie zgłoszony wyjątek.
    • Niech Value wynik oceny attribute_argument_expression wartości Arg.
    • Jeśli Name zidentyfikuje pole w pole O, ustaw to pole na Valuewartość .
    • W przeciwnym razie nazwa identyfikuje właściwość na .O Ustaw tę właściwość na Wartość.
    • Wynikiem jest Owystąpienie klasy T atrybutów, która została zainicjowana przy użyciu positional_argument_list P i named_argument_listN.

Uwaga: format przechowywania T, , C, PN (i kojarzenia go z E) w A oraz mechanizm E określania i pobierania T, , PCN , z A (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) i System.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.Attributeklasy , 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 symbolami ALPHA kompilacji warunkowej i BETA.

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ć modyfikatorem virtual . Zastąpienia takiej metody są niejawnie warunkowe i nie są jawnie oznaczone atrybutem Conditional .
  • 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ą. Class2Test Metoda wywołuje tę metodę. Ponieważ zdefiniowany jest symbol DEBUG kompilacji warunkowej, jeśli Class2.Test jest wywoływany, wywoła Melement . Gdyby symbol DEBUG nie został zdefiniowany, Class2.Test nie wywołałby metody Class1.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 i Class3 każdy zawiera wywołania metody Class1.Fwarunkowej , która jest warunkowa na podstawie tego, czy DEBUG jest zdefiniowana. Ponieważ ten symbol jest zdefiniowany w kontekście , Class2 ale nie Class3, wywołanie do F elementu Class2 jest uwzględniane, podczas gdy wywołanie metody F in Class3 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 baseformularza 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 bazowej M . To wywołanie zostanie pominięte, ponieważ metoda podstawowa jest warunkowa na podstawie obecności symbolu DEBUG, 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 warunkowych ALPHA i BETA.

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 i Class2 są ozdobione atrybutem Test, który jest warunkowy na podstawie tego, czy DEBUG jest zdefiniowany. Ponieważ ten symbol jest zdefiniowany w kontekście elementu Class1 , ale nie Class2, uwzględniana jest specyfikacja atrybutu Class1 Test, podczas gdy specyfikacja atrybutu Test on Class2 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 atrybutem Obsolete . Każde użycie A w programie powoduje Main wyświetlenie ostrzeżenia zawierającego określony komunikat "Ta klasa jest przestarzała; zamiast tego użyj klasy B ".

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, , CallerFilePathCallerMemberName. 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 CallerFilePathAttributeparametrem , 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 CallerMemberNameAttributeparametrem , 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ść nulldomyś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 przez stringelement , 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ć nullrzeczywiś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ędzie nullmieć wartości . Jednak dopuszczalne jest wywołanie ThrowWhenNull 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 nulljest .

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ą atrybutem NotNullIfNotNull , może być używany. Rozważmy następującą metodę:

#nullable enable
string GetTopLevelDomainFromFullUrl(string url) { ... }

url Jeśli argument nie nullnull 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 zwraca true wartość , gdy argument jest null 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 zwraca falsewartość . 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