Freigeben über


22 Attribute

22.1 Allgemein

Ein Großteil der C#-Sprache ermöglicht es dem Programmierer, deklarative Informationen zu den im Programm definierten Entitäten anzugeben. Beispielsweise wird die Barrierefreiheit einer Methode in einer Klasse angegeben, indem sie mit den method_modifiers public, , protected, internalund private.

C# ermöglicht Es Programmierern, neue Arten von deklarativen Informationen zu erfinden, die als Attribute bezeichnet werden. Programmierer können dann Attribute an verschiedene Programmentitäten anfügen und Attributinformationen in einer Laufzeitumgebung abrufen.

Hinweis: Beispielsweise kann ein Framework ein HelpAttribute Attribut definieren, das auf bestimmten Programmelementen (z. B. Klassen und Methoden) platziert werden kann, um eine Zuordnung von diesen Programmelementen zur Dokumentation bereitzustellen. Endnote

Attribute werden durch die Deklaration von Attributklassen (§22.2) definiert, die Positions- und benannte Parameter (§22.2.3) aufweisen können. Attribute werden an Entitäten in einem C#-Programm mithilfe von Attributspezifikationen (§22.3) angefügt und können zur Laufzeit als Attributinstanzen (§22.4) abgerufen werden.

22.2 Attributklassen

22.2.1 Allgemein

Eine Klasse, die von der abstrakten Klasse System.Attributeabgeleitet wird , ob direkt oder indirekt, ist eine Attributklasse. Die Deklaration einer Attributklasse definiert eine neue Art von Attribut, die für Programmentitäten platziert werden kann. Standardmäßig werden Attributklassen mit einem Suffix von Attributebenannt. Die Verwendung eines Attributs kann entweder dieses Suffix enthalten oder weglassen.

Eine generische Klassendeklaration darf nicht als direkte oder indirekte Basisklasse verwendet werden System.Attribute .

Beispiel:

public class B : Attribute {}
public class C<T> : B {} // Error – generic cannot be an attribute

Endbeispiel

22.2.2 Attributverwendung

Das Attribut AttributeUsage (§22.5.2) wird verwendet, um zu beschreiben, wie eine Attributklasse verwendet werden kann.

AttributeUsage verfügt über einen Positionsparameter (§22.2.3), mit dem eine Attributklasse die Arten von Programmentitäten angeben kann, für die sie verwendet werden kann.

Beispiel: Im folgenden Beispiel wird eine Attributklasse definiert SimpleAttribute , die nur auf class_declarations und interface_declarationplatziert werden kann, und zeigt mehrere Verwendungsmöglichkeiten des Simple Attributs an.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute : Attribute
{ 
    ... 
}

[Simple] class Class1 {...}
[Simple] interface Interface1 {...}

Obwohl dieses Attribut mit dem Namen SimpleAttributedefiniert ist, kann das Attribute Suffix bei Verwendung dieses Attributs weggelassen werden, was zu dem Kurznamen Simpleführt. Daher entspricht das obige Beispiel semantisch folgendem

[SimpleAttribute] class Class1 {...}
[SimpleAttribute] interface Interface1 {...}

Endbeispiel

AttributeUsage hat einen benannten Parameter (§22.2.3), der aufgerufen AllowMultiplewird, der angibt, ob das Attribut für eine bestimmte Entität mehrmals angegeben werden kann. Wenn AllowMultiple für eine Attributklasse wahr ist, ist diese Attributklasse eine mehrverwendige Attributklasse und kann in einer Entität mehrmals angegeben werden. Wenn AllowMultiple für eine Attributklasse falsch oder nicht angegeben ist, handelt es sich bei dieser Attributklasse um eine attributbasierte Klasse, die höchstens einmal für eine Entität angegeben werden kann.

Beispiel: Im folgenden Beispiel wird eine attributübergreifende Klasse namens AuthorAttribute definiert und eine Klassendeklaration mit zwei Verwendungen des Author Attributs angezeigt:

[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 
{
    ...
}

Endbeispiel

AttributeUsage hat einen weiteren benannten Parameter (§22.2.3), der aufgerufen Inheritedwird, der angibt, ob das Attribut, wenn in einer Basisklasse angegeben, auch von Klassen geerbt wird, die von dieser Basisklasse abgeleitet sind. Wenn Inherited für eine Attributklasse "true" angegeben ist, wird dieses Attribut geerbt. Wenn Inherited für eine Attributklasse "false" lautet, wird dieses Attribut nicht geerbt. Wenn sie nicht angegeben ist, ist der Standardwert "true".

Eine Attributklasse X , der kein AttributeUsage Attribut zugeordnet ist, wie in

class X : Attribute { ... }

entspricht folgendem:

[AttributeUsage(
   AttributeTargets.All,
   AllowMultiple = false,
   Inherited = true)
]
class X : Attribute { ... }

22.2.3 Positions- und benannte Parameter

Attributklassen können Positionsparameter s und benannte Parameters aufweisen. Jeder öffentliche Instanzkonstruktor für eine Attributklasse definiert eine gültige Abfolge von Positionsparametern für diese Attributklasse. Jedes nicht statische öffentliche Lese-/Schreibfeld und jede Eigenschaft für eine Attributklasse definiert einen benannten Parameter für die Attributklasse. Damit eine Eigenschaft einen benannten Parameter definiert, muss diese Eigenschaft sowohl über einen öffentlichen Get-Accessor als auch über einen öffentlichen Zugriffszugriffsor verfügen.

Beispiel: Im folgenden Beispiel wird eine Attributklasse definiert HelpAttribute , die einen Positionsparameter urlund einen benannten Parameter Topicenthält. Obwohl sie nicht statisch und öffentlich ist, definiert die Eigenschaft Url keinen benannten Parameter, da sie nicht schreibgeschützt ist. Zwei Verwendungen dieses Attributs werden ebenfalls angezeigt:

[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
{
}

Endbeispiel

22.2.4 Attributparametertypen

Die Typen von Positions- und benannten Parametern für eine Attributklasse sind auf die Attributparametertypen beschränkt, die folgendes sind:

  • Einer der folgenden Typen: bool, , byte, char, double, float, , int, , sbytelong, short, string, , , uint, . ulongushort
  • Der object-Typ.
  • Der System.Type-Typ.
  • Enumerationstypen.
  • Eindimensionale Arrays der obigen Typen.
  • Ein Konstruktorargument oder ein öffentliches Feld, das nicht über einen dieser Typen verfügt, darf nicht als positionaler oder benannter Parameter in einer Attributspezifikation verwendet werden.

22.3 Attributspezifikation

Attributspezifikation ist die Anwendung eines zuvor definierten Attributs auf eine Programmentität. Ein Attribut ist ein Teil zusätzlicher deklarativer Informationen, die für eine Programmentität angegeben werden. Attribute können im globalen Bereich angegeben werden (um Attribute für die enthaltende Assembly oder das enthaltende Modul anzugeben) und für type_declaration s (§14.7), class_member_declarations (§15.3), interface_member_declarations (§3) 18.4), struct_member_declarations (§16.3), enum_member_declarations (§19.2), accessor_declarations (§15.7.3), event_accessor_ Deklarationen(§15.8), Elemente von parameter_list(§15.6.2) und Elemente von type_parameter_lists (§15.2.3).

Attribute werden in Attributabschnitten angegeben. Ein Attributabschnitt besteht aus einem Paar eckiger Klammern, die eine durch Trennzeichen getrennte Liste eines oder mehrerer Attribute umgeben. Die Reihenfolge, in der Attribute in einer solchen Liste angegeben werden, und die Reihenfolge, in der Abschnitte, die mit derselben Programmentität verknüpft sind, angeordnet sind, ist nicht signifikant. Beispielsweise sind die Attributspezifikationen [A][B], [B][A], , [A, B]und [B, A] sind gleichwertig.

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
    ;

Für die Produktion global_attribute_target und im folgenden Text muss der Bezeichner eine Schreibweise aufweisen, die assembly dem in §6.4.3 definierten Gleichheit entspricht.module Für die Produktion attribute_target und im folgenden Text muss der Bezeichner über eine Schreibweise verfügen, die nicht gleich assembly ist oder moduledie gleiche Definition der Gleichheit wie oben verwendet.

Ein Attribut besteht aus einer attribute_name und einer optionalen Liste von Positions- und benannten Argumenten. Die positionellen Argumente (sofern vorhanden) sind den benannten Argumenten vorangestellt. Ein Positionsargument besteht aus einem attribute_argument_expression; ein benanntes Argument besteht aus einem Namen, gefolgt von einem Gleichheitszeichen, gefolgt von einer attribute_argument_expression, die zusammen durch dieselben Regeln wie einfache Zuordnung eingeschränkt werden. Die Reihenfolge der benannten Argumente ist nicht signifikant.

Hinweis: Aus Gründen der Einfachheit ist ein nachfolgendes Komma in einem global_attribute_section und einer attribute_section zulässig, genauso wie in einem array_initializer (§17.7). Endnote

Die attribute_name identifiziert eine Attributklasse.

Wenn ein Attribut auf globaler Ebene platziert wird, ist eine global_attribute_target_specifier erforderlich. Wenn die global_attribute_target gleich:

  • assembly — das Ziel ist die enthaltende Assembly.
  • module — das Ziel ist das enthaltende Modul.

Es sind keine anderen Werte für global_attribute_target zulässig.

Die standardisierten attribute_target Namen sind event, , field, method, param, property, return, und typetypevar. Diese Zielnamen dürfen nur in den folgenden Kontexten verwendet werden:

  • event — ein Ereignis.
  • field — ein Feld. Ein feldähnliches Ereignis (d. h. ein Ereignis ohne Accessoren) (§15.8.2) und eine automatisch implementierte Eigenschaft (§15.7.4) kann auch ein Attribut mit diesem Ziel aufweisen.
  • method — Konstruktor, Finalizer, Methode, Operator, Property Get- und Set-Accessoren, Indexer get and set accessors, and event add and remove accessors. Ein feldähnliches Ereignis (d. h. ein Ereignis ohne Accessoren) kann auch ein Attribut mit diesem Ziel aufweisen.
  • param — einen Eigenschaftensatz-Accessor, einen Indexersatz-Accessor, Accessoren zum Hinzufügen und Entfernen von Ereignissen sowie einen Parameter in einem Konstruktor, einer Methode und einem Operator.
  • property — eine Eigenschaft und ein Indexer.
  • return — ein Delegat, eine Methode, ein Operator, ein Accessor für Eigenschaften und einen Indexer zum Accessor.
  • type — Stellvertretung, Klasse, Struktur, Enumeration und Schnittstelle.
  • typevar — einen Typparameter.

Bestimmte Kontexte ermöglichen die Spezifikation eines Attributs für mehrere Ziele. Ein Programm kann das Ziel explizit angeben, indem ein attribute_target_specifier eingeschlossen wird. Ohne eine attribute_target_specifier wird ein Standardwert angewendet, aber ein attribute_target_specifier kann verwendet werden, um den Standardwert zu bestätigen oder außer Kraft zu setzen. Die Kontexte werden wie folgt aufgelöst:

  • Bei einem Attribut für eine Delegatdeklaration ist das Standardziel der Delegat. Andernfalls ist die attribute_target gleich:
    • type — das Ziel ist die Stellvertretung.
    • return — das Ziel ist der Rückgabewert.
  • Bei einem Attribut für eine Methodendeklaration ist das Standardziel die Methode. Andernfalls ist die attribute_target gleich:
    • method — das Ziel ist die Methode
    • return — das Ziel ist der Rückgabewert.
  • Bei einem Attribut für eine Operatordeklaration ist das Standardziel der Operator. Andernfalls ist die attribute_target gleich:
    • method — das Ziel ist der Operator
    • return — das Ziel ist der Rückgabewert.
  • Für ein Attribut für eine Get-Accessor-Deklaration für eine Eigenschafts- oder Indexerdeklaration ist das Standardziel die zugeordnete Methode. Andernfalls ist die attribute_target gleich:
    • method — das Ziel ist die zugeordnete Methode.
    • return — das Ziel ist der Rückgabewert.
  • Für ein Attribut, das für einen Set-Accessor für eine Eigenschaft oder Indexerdeklaration angegeben ist, ist das Standardziel die zugeordnete Methode. Andernfalls ist die attribute_target gleich:
    • method — das Ziel ist die zugeordnete Methode.
    • param — das Ziel ist der einsame implizite Parameter.
  • Für ein Attribut für eine automatisch implementierte Eigenschaftsdeklaration ist das Standardziel die Eigenschaft. Andernfalls ist die attribute_target gleich:
    • field — das Ziel ist das vom Compiler generierte Sicherungsfeld für die Eigenschaft.
  • Für ein Attribut, das für eine Ereignisdeklaration angegeben ist, die event_accessor_declarations das Standardziel ausgelassen wird, ist die Ereignisdeklaration. Andernfalls ist die attribute_target gleich:
    • event — das Ziel ist die Ereignisdeklaration.
    • field — das Ziel ist das Feld.
    • method — die Ziele sind die Methoden
  • Bei einer Ereignisdeklaration, die nicht event_accessor_declarations das Standardziel weggelassen wird, ist die Methode.
    • method — das Ziel ist die zugeordnete Methode.
    • param — das Ziel ist der einsame Parameter.

In allen anderen Kontexten ist die Aufnahme eines attribute_target_specifier zulässig, aber unnötig.

Beispiel: Eine Klassendeklaration kann entweder den Bezeichner typeenthalten oder weglassen:

[type: Author("Brian Kernighan")]
class Class1 {}

[Author("Dennis Ritchie")]
class Class2 {}

Endbeispiel.

Eine Implementierung kann andere attribute_targetakzeptieren, deren Umsetzungszwecke definiert sind. Eine Implementierung, die eine solche attribute_target nicht erkennt, gibt eine Warnung aus und ignoriert die enthaltenden attribute_section.

Standardmäßig werden Attributklassen mit einem Suffix von Attributebenannt. Ein attribute_name kann dieses Suffix entweder einschließen oder weglassen. Insbesondere wird ein attribute_name wie folgt aufgelöst:

  • Wenn es sich bei dem am rechten Rand des attribute_name um einen Verbatimbezeichner (§6.4.3) handelt, wird die attribute_name als type_name (§7.8) aufgelöst. Wenn das Ergebnis kein typ abgeleiteter System.AttributeTyp ist, tritt ein Kompilierungszeitfehler auf.
  • Andernfalls .
    • Die attribute_name wird als type_name (§7.8) behoben, außer dass Fehler unterdrückt werden. Wenn diese Auflösung erfolgreich ist und zu einem Vom System.Attribute Typ abgeleiteten Typ führt, ist der Typ das Ergebnis dieses Schritts.
    • Die Zeichen Attribute werden am rechten Rand des attribute_name angefügt, und die resultierende Tokenzeichenfolge wird als type_name (§7.8) aufgelöst, mit Ausnahme von Fehlern. Wenn diese Auflösung erfolgreich ist und zu einem Vom System.Attribute Typ abgeleiteten Typ führt, ist der Typ das Ergebnis dieses Schritts.

Wenn genau einer der beiden obigen Schritte zu einem typ abgeleitet wird System.Attribute, ist dieser Typ das Ergebnis der attribute_name. Andernfalls tritt ein Kompilierungsfehler auf.

Beispiel: Wenn eine Attributklasse sowohl mit als auch ohne dieses Suffix gefunden wird, ist eine Mehrdeutigkeit vorhanden und führt zu einem Kompilierungszeitfehler. Wenn der attribute_name so geschrieben ist, dass es sich bei dem bezeichner mit der rechten Maustaste um einen Verbatimbezeichner (§6.4.3) handelt, wird nur ein Attribut ohne Suffix abgeglichen, sodass eine solche Mehrdeutigkeit aufgelöst werden kann. Das Beispiel

[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 {}

zeigt zwei Attributklassen namens Example und ExampleAttribute. Das Attribut [Example] ist mehrdeutig, da es auf entweder Example oder ExampleAttribute. Durch die Verwendung eines Verbatim-Bezeichners kann die genaue Absicht in seltenen Fällen angegeben werden. Das Attribut [ExampleAttribute] ist nicht mehrdeutig (auch wenn eine Attributklasse mit dem Namen ExampleAttributeAttribute!vorhanden wäre). Wenn die Deklaration für die Klasse Example entfernt wird, verweisen beide Attribute wie folgt auf die Attributklasse mit dem Namen ExampleAttribute:

[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 {}

Endbeispiel

Es handelt sich um einen Kompilierungsfehler, um eine einmal verwendete Attributklasse für dieselbe Entität zu verwenden.

Beispiel: Das Beispiel

[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 {}

führt zu einem Kompilierzeitfehler, da versucht wird, eine Attributklasse mit einmaliger Verwendung zu verwenden HelpString, mehr als einmal in der Deklaration von Class1.

Endbeispiel

Ein Ausdruck E ist ein attribute_argument_expression , wenn alle folgenden Anweisungen wahr sind:

  • Der Typ von E ist ein Attributparametertyp (§22.2.4).
  • Zur Kompilierungszeit kann der Wert E von zu einem der folgenden Aufgelöst werden:
    • Ein konstanter Wert.
    • Ein System.Type mit einem typeof_expression (§12.8.18) abgerufenes Objekt, das einen nicht generischen Typ, einen geschlossenen konstruierten Typ (§8.4.3) oder einen ungebundenen generischen Typ (§8.4.4) angibt, jedoch keinen offenen Typ (§8.4.3).
    • Ein eindimensionales Array von attribute_argument_expressions.

Beispiel:

[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;
}

Endbeispiel

Die Attribute eines typs, der in mehreren Teilen deklariert ist, werden durch die Kombination der Attribute der einzelnen Teile in einer nicht angegebenen Reihenfolge bestimmt. Wenn dasselbe Attribut auf mehreren Teilen platziert wird, entspricht es dem Angeben dieses Attributs mehrmals für den Typ.

Beispiel: Die beiden Teile:

[Attr1, Attr2("hello")]
partial class A {}

[Attr3, Attr2("goodbye")]
partial class A {}

entspricht der folgenden einzelnen Deklaration:

[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}

Endbeispiel

Attribute für Typparameter werden auf die gleiche Weise kombiniert.

22.4-Attributinstanzen

22.4.1 Allgemein

Eine Attributinstanz ist eine Instanz, die ein Attribut zur Laufzeit darstellt. Ein Attribut wird mit einer Attributklasse, Positionsargumenten und benannten Argumenten definiert. Eine Attributinstanz ist eine Instanz der Attributklasse, die mit den positionsbezogenen und benannten Argumenten initialisiert wird.

Das Abrufen einer Attributinstanz umfasst sowohl die Kompilierungszeit als auch die Laufzeitverarbeitung, wie in den folgenden Unterclauses beschrieben.

22.4.2 Kompilierung eines Attributs

Die Kompilierung eines Attributs mit Attributklasse T, positional_argument_list P, named_argument_listN und in einer Programmentität E angegeben wird, wird über die folgenden Schritte in eine Assembly A kompiliert:

  • Befolgen Sie die Kompilierungszeitverarbeitungsschritte zum Kompilieren einer object_creation_expression des Formulars neu T(P). Diese Schritte führen entweder zu einem Kompilierungsfehler oder bestimmen einen Instanzkonstruktor C , der T zur Laufzeit aufgerufen werden kann.
  • Wenn C keine öffentliche Barrierefreiheit vorhanden ist, tritt ein Kompilierungszeitfehler auf.
  • Für jede named_argument Arg in N:
    • Lassen Sie uns Name der Bezeichner des named_argument Argsein.
    • Namemuss ein nicht statisches öffentliches Feld oder eine eigenschaft mit Lese-/Schreibzugriff identifizieren.T Wenn T kein solches Feld oder eine solche Eigenschaft vorhanden ist, tritt ein Kompilierungszeitfehler auf.
  • Wenn eines der Werte innerhalb positional_argument_listP oder eines der Werte innerhalb named_argument_listN vom Typ System.String ist und der Wert nicht wohlgeformt ist, wie durch den Unicode-Standard definiert, wird durch die Implementierung definiert, ob der kompilierte Wert dem abgerufenen Laufzeitwert entspricht (§22.4.3).

    Hinweis: Als Beispiel ist eine Zeichenfolge, die eine hohe UTF-16-Codeeinheit enthält, die nicht unmittelbar auf eine niedrige Ersatzcodeeinheit folgt, nicht wohlgeformt ist. Endnote

  • Speichern Sie die folgenden Informationen (für die Laufzeitinstanziierung des Attributs) in der Assemblyausgabe des Compilers als Ergebnis der Kompilierung des Programms, das das Attribut enthält: die AttributklasseT, der Instanzkonstruktor C fürT, die positional_argument_listP, die named_argument_listN und die zugeordnete ProgrammentitätE, wobei die Werte zur Kompilierungszeit vollständig aufgelöst wurden.

22.4.3 Laufzeitabruf einer Attributinstanz

Mit den in §22.4.2 definierten Begriffen kann die Attributinstanz, die durch T, C, Pund , und Nzugeordnet E ist, zur Laufzeit aus der Assembly A abgerufen werden, indem Sie die folgenden Schritte ausführen:

  • Befolgen Sie die Laufzeitverarbeitungsschritte zum Ausführen einer object_creation_expression des Formulars new T(P)mithilfe des Instanzkonstruktors C und der Werte, die zur Kompilierungszeit ermittelt wurden. Diese Schritte führen entweder zu einer Ausnahme oder zu einer Instanz O von T.
  • Für jede named_argument Arg in Nder Reihenfolge:
    • Lassen Sie uns Name der Bezeichner des named_argument Argsein. Wenn Name ein nicht statisches öffentliches Lese-/Schreibfeld oder eine Eigenschaft Onicht identifiziert wird, wird eine Ausnahme ausgelöst.
    • Lassen Sie uns Value das Ergebnis der Auswertung der attribute_argument_expression von Arg.
    • Wenn Name ein Feld angegeben Owird, legen Sie dieses Feld auf Value.
    • Andernfalls identifiziert Name eine Eigenschaft für O. Legen Sie diese Eigenschaft auf Value fest.
    • Das Ergebnis ist Oeine Instanz der Attributklasse T , die mit dem positional_argument_list P und dem named_argument_listN initialisiert wurde.

Hinweis: Das Format zum Speichern T, , C, N P( und Zuordnen mit E) in A und dem Mechanismus zum Angeben E und Abrufen Tvon , C, von PN A (und damit, wie eine Attributinstanz zur Laufzeit abgerufen wird) liegt über den Bereich dieser Spezifikation hinaus. Endnote

Beispiel: In einer Implementierung der CLI können die Attributinstanzen in der Assembly, die Help durch Kompilieren des Beispielprogramms in §22.2.3 erstellt wurden, mit dem folgenden Programm abgerufen werden:

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}");
            }
        }
    }
}

Endbeispiel

22.5 Reservierte Attribute

22.5.1 Allgemein

Eine Reihe von Attributen wirkt sich auf die Sprache auf irgendeine Weise aus. Zu diesen Attributen zählen Folgende:

  • System.AttributeUsageAttribute (§22.5.2), das verwendet wird, um die Verwendungsmöglichkeiten einer Attributklasse zu beschreiben.
  • System.Diagnostics.ConditionalAttribute (§22.5.3) ist eine mehrverwendige Attributklasse, die zum Definieren bedingter Methoden und bedingter Attributklassen verwendet wird. Dieses Attribut gibt eine Bedingung an, indem ein Symbol für die bedingte Kompilierung getestet wird.
  • System.ObsoleteAttribute (§22.5.4), das verwendet wird, um ein Element als veraltet zu kennzeichnen.
  • System.Runtime.CompilerServices.AsyncMethodBuilderAttribute (§22.5.5), das zum Einrichten eines Aufgaben-Generators für eine asynchrone Methode verwendet wird.
  • System.Runtime.CompilerServices.CallerLineNumberAttribute (§22.5.6.2), System.Runtime.CompilerServices.CallerFilePathAttribute (§22.5.6.3) und System.Runtime.CompilerServices.CallerMemberNameAttribute (§22.5.6.4), die verwendet werden, um Informationen zum aufrufenden Kontext an optionale Parameter zu liefern.

Die Attribute der statischen Nullanalyse (§22.5.7) können die Richtigkeit von Warnungen verbessern, die für Nullfähigkeiten und Nullzustände (§8.9.5) generiert wurden.

Eine Ausführungsumgebung kann zusätzliche implementierungsdefinierte Attribute bereitstellen, die sich auf die Ausführung eines C#-Programms auswirken.

22.5.2 Das Attribut "AttributeUsage"

Das Attribut AttributeUsage wird verwendet, um die Art und Weise zu beschreiben, in der die Attributklasse verwendet werden kann.

Eine klasse, die mit dem AttributeUsage Attribut versehen ist, muss entweder direkt oder indirekt von System.Attributeabgeleitet werden. Andernfalls tritt ein Kompilierungsfehler auf.

Hinweis: Ein Beispiel für die Verwendung dieses Attributs finden Sie unter §22.2.2. Endnote

22.5.3 Das Attribut "Bedingt"

22.5.3.1 Allgemein

Das Attribut Conditional ermöglicht die Definition von bedingten Methoden und bedingten Attributklassen.

22.5.3.2 Bedingte Methoden

Eine mit dem Conditional Attribut versehene Methode ist eine bedingte Methode. Jede bedingte Methode ist somit den in ihren Conditional Attributen deklarierten Symbolen für die bedingte Kompilierung zugeordnet.

Beispiel:

class Eg
{
    [Conditional("ALPHA")]
    [Conditional("BETA")]
    public static void M()
    {
        // ...
    }
}

Eg.M deklariert als bedingte Methode, die den beiden Symbolen ALPHA für die bedingte Kompilierung zugeordnet ist.BETA

Endbeispiel

Ein Aufruf einer bedingten Methode wird eingeschlossen, wenn mindestens eins der zugehörigen Symbole für die bedingte Kompilierung am Aufrufpunkt definiert ist, andernfalls wird der Aufruf weggelassen.

Eine bedingte Methode unterliegt den folgenden Einschränkungen:

  • Die bedingte Methode muss eine Methode in einem class_declaration oder struct_declaration sein. Ein Kompilierungszeitfehler tritt auf, wenn das Conditional Attribut für eine Methode in einer Schnittstellendeklaration angegeben wird.
  • Die bedingte Methode hat einen Rückgabetyp von void.
  • Die bedingte Methode darf nicht mit dem override Modifizierer gekennzeichnet werden. Eine bedingte Methode kann jedoch mit dem virtual Modifizierer markiert werden. Außerkraftsetzungen einer solchen Methode sind implizit bedingt und dürfen nicht explizit mit einem Conditional Attribut gekennzeichnet werden.
  • Die bedingte Methode darf keine Implementierung einer Schnittstellenmethode sein. Andernfalls tritt ein Kompilierungsfehler auf.
  • Die Parameter der bedingten Methode dürfen keine Ausgabeparameter sein.

Darüber hinaus tritt ein Kompilierungszeitfehler auf, wenn eine Stellvertretung aus einer bedingten Methode erstellt wird.

Beispiel: Das Beispiel

#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 deklariert als bedingte Methode. Class2Die Test Methode ruft diese Methode auf. Da das Symbol DEBUG für die bedingte Kompilierung definiert ist, wenn Class2.Test es aufgerufen wird, wird es aufgerufen M. Wenn das Symbol DEBUG nicht definiert wurde, würde das Class2.Test Symbol nicht aufgerufen Class1.M.

Endbeispiel

Es ist wichtig zu verstehen, dass der Ein- oder Ausschluss eines Aufrufs einer bedingten Methode durch die Symbole für die bedingte Kompilierung an der Stelle des Aufrufs gesteuert wird.

Beispiel: Im folgenden Code

// 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
    }
}

die Klassen Class2 und Class3 jede enthalten Aufrufe der bedingten Methode Class1.F, die abhängig davon, ob definiert ist oder nicht DEBUG . Da dieses Symbol im Kontext von Class2 , aber nicht Class3definiert ist, wird der Aufruf F Class2 eingeschlossen, während der Aufruf von F "In Class3 " weggelassen wird.

Endbeispiel

Die Verwendung bedingter Methoden in einer Vererbungskette kann verwirrend sein. Aufrufe an eine bedingte Methode über basedas Formular base.Munterliegen den normalen Regeln für den Aufruf der bedingten Methode.

Beispiel: Im folgenden Code

// 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 enthält einen Aufruf der M definierten Basisklasse. Dieser Aufruf wird nicht angegeben, da die Basismethode auf der Grundlage des Vorhandenseins des Symbols DEBUG, das nicht definiert ist, bedingt ist. Daher schreibt die Methode nur in die Konsole "Class2.M executed". Eine sorgfältige Verwendung von pp_declarationkann solche Probleme beseitigen.

Endbeispiel

22.5.3.3 Bedingte Attributklassen

Eine Attributklasse (§22.2), die mit einem oder Conditional mehreren Attributen versehen ist, ist eine bedingte Attributklasse. Eine bedingte Attributklasse ist somit den in den Conditional Attributen deklarierten Symbolen für die bedingte Kompilierung zugeordnet.

Beispiel:

[Conditional("ALPHA")]
[Conditional("BETA")]
public class TestAttribute : Attribute {}

TestAttribute deklariert als bedingte Attributklasse, die den Symbolen ALPHA für die bedingte Kompilierung zugeordnet ist.BETA

Endbeispiel

Attributspezifikationen (§22.3) eines bedingten Attributs werden eingeschlossen, wenn mindestens ein zugehöriges Symbol für die bedingte Kompilierung an der Spezifikationspunkt definiert ist, andernfalls wird die Attributspezifikation weggelassen.

Es ist wichtig zu beachten, dass die Ein- oder Ausschluss einer Attributspezifikation einer bedingten Attributklasse durch die Symbole für die bedingte Kompilierung an der Stelle der Spezifikation gesteuert wird.

Beispiel: Im Beispiel

// 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 {}

die Klassen Class1 und Class2 sind jeweils mit Attributen Testversehen, die abhängig davon sind, ob sie definiert sind oder nicht DEBUG . Da dieses Symbol im Kontext von Class1 , aber nicht Class2definiert ist, wird die Spezifikation des Test-Attributs Class1 eingeschlossen, während die Spezifikation des Test Attributs Class2 ausgelassen wird.

Endbeispiel

22.5.4 Das veraltete Attribut

Das Attribut Obsolete wird verwendet, um Typen und Member von Typen zu markieren, die nicht mehr verwendet werden sollen.

Wenn ein Programm einen Typ oder member verwendet, der mit dem Obsolete Attribut versehen ist, gibt der Compiler eine Warnung oder einen Fehler aus. Insbesondere gibt der Compiler eine Warnung aus, wenn kein Fehlerparameter angegeben wird oder wenn der Fehlerparameter angegeben wird und den Wert falseaufweist. Der Compiler gibt einen Fehler aus, wenn der Fehlerparameter angegeben ist und den Wert truehat.

Beispiel: Im folgenden Code

[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();
    }
}

die Klasse A mit dem Obsolete Attribut versehen ist. Jede Verwendung von A in Main führt zu einer Warnung, die die angegebene Meldung enthält: "Diese Klasse ist veraltet; verwenden Sie stattdessen " B .

Endbeispiel

22.5.5 Das AsyncMethodBuilder-Attribut

Dieses Attribut wird in §15.15.1 beschrieben.

22.5.6 Attribute für Anruferinformationen

22.5.6.1 Allgemein

Für Zwecke wie Protokollierung und Berichterstellung ist es manchmal nützlich, dass ein Funktionselement bestimmte Kompilierungszeitinformationen zum aufrufenden Code abruft. Die Caller-Info-Attribute bieten eine Möglichkeit, solche Informationen transparent zu übergeben.

Wenn ein optionaler Parameter mit einem der Aufrufer-Info-Attribute kommentiert wird, führt das Weglassen des entsprechenden Arguments in einem Aufruf nicht unbedingt dazu, dass der Standardwert des Parameters ersetzt wird. Wenn stattdessen die angegebenen Informationen zum aufrufenden Kontext verfügbar sind, werden diese Informationen als Argumentwert übergeben.

Beispiel:

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);
}

Ein Aufruf Log() ohne Argumente würde die Zeilennummer und den Dateipfad des Aufrufs sowie den Namen des Elements, in dem der Aufruf aufgetreten ist, drucken.

Endbeispiel

Caller-Info-Attribute können an einer beliebigen Stelle in optionalen Parametern auftreten, einschließlich in Stellvertretungsdeklarationen. Die spezifischen Aufrufer-Info-Attribute haben jedoch Einschränkungen für die Typen der Parameter, die sie attributieren können, sodass immer eine implizite Konvertierung von einem ersetzten Wert in den Parametertyp vorhanden ist.

Es ist ein Fehler, dass dasselbe Caller-Info-Attribut für einen Parameter der definierenden und implementierenden Komponente einer partiellen Methodendeklaration vorhanden ist. Nur Aufrufer-Info-Attribute im definierenden Teil werden angewendet, während Aufrufer-Info-Attribute, die nur im implementierenden Teil auftreten, ignoriert werden.

Anruferinformationen wirken sich nicht auf die Überlastungsauflösung aus. Da die attributierten optionalen Parameter weiterhin aus dem Quellcode des Aufrufers weggelassen werden, ignoriert die Überladungsauflösung diese Parameter auf die gleiche Weise wie andere ausgelassene optionale Parameter (§12.6.4).

Aufruferinformationen werden nur ersetzt, wenn eine Funktion explizit im Quellcode aufgerufen wird. Implizite Aufrufe wie implizite übergeordnete Konstruktoraufrufe verfügen nicht über einen Quellspeicherort und ersetzen keine Aufruferinformationen. Darüber hinaus ersetzen dynamisch gebundene Aufrufe keine Anruferinformationen. Wenn in solchen Fällen ein Attributparameter für caller-info ausgelassen wird, wird stattdessen der angegebene Standardwert des Parameters verwendet.

Eine Ausnahme sind Abfrageausdrücke. Diese werden als syntaktische Erweiterungen betrachtet, und wenn die Aufrufe erweitert werden, um optionale Parameter mit Caller-Info-Attributen auszulassen, werden Anruferinformationen ersetzt. Der verwendete Speicherort ist der Speicherort der Abfrageklausel, aus der der Aufruf generiert wurde.

Wenn mehr als ein Aufrufer-Info-Attribut für einen bestimmten Parameter angegeben wird, werden sie in der folgenden Reihenfolge erkannt: CallerLineNumber, , CallerFilePathCallerMemberName. Betrachten Sie die folgende Parameterdeklaration:

[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...

CallerLineNumber hat Vorrang, und die anderen beiden Attribute werden ignoriert. Wenn CallerLineNumber sie weggelassen würden, CallerFilePath würde Vorrang haben und CallerMemberName ignoriert werden. Die lexikalische Sortierung dieser Attribute ist irrelevant.

22.5.6.2 Das CallerLineNumber-Attribut

Das Attribut System.Runtime.CompilerServices.CallerLineNumberAttribute ist für optionale Parameter zulässig, wenn eine implizite Standardkonvertierung (§10.4.2) vom Konstantenwert int.MaxValue in den Typ des Parameters vorhanden ist. Dadurch wird sichergestellt, dass alle nicht negativen Zeilennummern bis zu diesem Wert ohne Fehler übergeben werden können.

Wenn ein Funktionsaufruf von einem Speicherort im Quellcode einen optionalen Parameter mit dem CallerLineNumberAttributeAuslassungszeichen auslassen soll, wird ein numerisches Literal, das die Zeilennummer dieser Position darstellt, als Argument für den Aufruf anstelle des Standardwerts verwendet.

Wenn sich der Aufruf über mehrere Zeilen erstreckt, ist die ausgewählte Zeile implementierungsabhängig.

Die Zeilennummer kann von #line Richtlinien betroffen sein (§6.5.8).

22.5.6.3 Das CallerFilePath-Attribut

Das Attribut System.Runtime.CompilerServices.CallerFilePathAttribute ist für optionale Parameter zulässig, wenn es eine implizite Standardkonvertierung (§10.4.2) vom string Typ des Parameters gibt.

Wenn ein Funktionsaufruf von einem Speicherort im Quellcode einen optionalen Parameter mit dem CallerFilePathAttributeParameter ausgelassen wird, wird ein Zeichenfolgenliteral, das den Dateipfad dieses Speicherorts darstellt, als Argument für den Aufruf anstelle des Standardwerts verwendet.

Das Format des Dateipfads ist implementierungsabhängig.

Der Dateipfad kann von #line Richtlinien betroffen sein (§6.5.8).

22.5.6.4 Das CallerMemberName-Attribut

Das Attribut System.Runtime.CompilerServices.CallerMemberNameAttribute ist für optionale Parameter zulässig, wenn es eine implizite Standardkonvertierung (§10.4.2) vom string Typ des Parameters gibt.

Wenn ein Funktionsaufruf von einer Stelle innerhalb des Textkörpers eines Funktionselements oder innerhalb eines Attributs, das auf das Funktionselement selbst oder den Rückgabetyp angewendet wird, werden Parameter oder Typparameter im Quellcode keinen optionalen Parameter mit dem CallerMemberNameAttribute, und dann wird ein Zeichenfolgenliteral, das den Namen dieses Elements darstellt, als Argument für den Aufruf anstelle des Standardwerts verwendet.

Bei Aufrufen, die innerhalb generischer Methoden auftreten, wird nur der Methodenname selbst verwendet, ohne die Typparameterliste.

Bei Aufrufen, die innerhalb expliziter Schnittstellenmememerimplementierungen auftreten, wird nur der Methodenname selbst verwendet, ohne die vorherige Schnittstellenqualifizierung.

Für Aufrufe, die innerhalb von Eigenschaften- oder Ereignisaccessoren auftreten, ist der verwendete Membername der Eigenschaft oder des Ereignisses selbst.

Für Aufrufe, die innerhalb von Indexer-Accessoren auftreten, wird der verwendete Membername von einem IndexerNameAttribute (§22.6) für das Indexermemmemm, sofern vorhanden, oder auf andere Weise der Standardname Item angegeben.

Bei Aufrufen, die innerhalb von Feld- oder Ereignisinitialisierern auftreten, ist der verwendete Membername der Name des Felds oder Ereignisses, das initialisiert wird.

Für Aufrufe, die in Deklarationen von Instanzkonstruktoren, statischen Konstruktoren, Finalizern und Operatoren auftreten, ist der verwendete Membername implementierungsabhängig.

22.5.7 Codeanalyseattribute

22.5.7.1 Allgemein

Die Attribute in diesem Abschnitt werden verwendet, um zusätzliche Informationen zur Unterstützung eines Compilers bereitzustellen, der nullability und null-state diagnostics (§8.9.5) bereitstellt. Für die Ausführung einer Nullzustandsdiagnose ist kein Compiler erforderlich. Das Vorhandensein oder Fehlen dieser Attribute wirkt sich weder auf die Sprache noch auf das Verhalten eines Programms aus. Ein Compiler, der keine Nullzustandsdiagnose bereitstellt, muss das Vorhandensein dieser Attribute lesen und ignorieren. Ein Compiler, der die Nullzustandsdiagnose bereitstellt, verwendet die in diesem Abschnitt definierte Bedeutung für jedes dieser Attribute, mit denen er seine Diagnose informiert.

Die Codeanalyseattribute werden im Namespace System.Diagnostics.CodeAnalysisdeklariert.

Attribut Bedeutung
AllowNull (§22.5.7.2) Ein Non-Nullable-Argument darf NULL sein.
DisallowNull (§22.5.7.3) Ein Nullable-Argument darf nie NULL sein.
MaybeNull (§22.5.7.6) Ein Non-Nullable-Rückgabewert darf NULL sein.
NotNull (§22.5.7.8) Ein Nullable-Rückgabetyp ist niemals NULL.
MaybeNullWhen (§22.5.7.7) Ein Non-Nullable-Argument darf NULL sein, wenn die Methode den angegebenen bool-Wert zurückgibt.
NotNullWhen (§22.5.7.10) Ein nullables Argument ist nicht NULL, wenn die Methode den angegebenen bool Wert zurückgibt.
NotNullIfNotNull (§22.5.7.9) Ein Rückgabewert ist nicht NULL, wenn das Argument für den angegebenen Parameter nicht null ist.
DoesNotReturn (§22.5.7.4) Diese Methode gibt nie zurück.
DoesNotReturnIf (§22.5.7.5) Die Methode gibt niemals ein Ergebnis zurück, wenn der zugeordnete Parameter bool einen angegebenen Wert aufweist.

Die folgenden Abschnitte in §22.5.7.1 sind bedingt normativ.

22.5.7.2 Das AllowNull-Attribut

Gibt an, dass ein Nullwert als Eingabe zulässig ist, auch wenn der entsprechende Typ ihn nicht zulässt.

Beispiel: Betrachten Sie die folgende Lese-/Schreibeigenschaft, die nie zurückgegeben wird null , da sie über einen angemessenen Standardwert verfügt. Ein Benutzer kann dem Set-Accessor jedoch NULL zugeben, um die Eigenschaft auf diesen Standardwert festzulegen.

#nullable enable
public class X
{
    [AllowNull]
    public string ScreenName
    {
        get => _screenName;
        set => _screenName = value ?? GenerateRandomScreenName();
    }
    private string _screenName = GenerateRandomScreenName();
    private static string GenerateRandomScreenName() => ...;
}

Bei der folgenden Verwendung des Set-Accessors dieser Eigenschaft

var v = new X();
v.ScreenName = null;   // may warn without attribute AllowNull

ohne das Attribut generiert der Compiler möglicherweise eine Warnung, da die nicht nullable-typed-Eigenschaft auf einen Nullwert festgelegt wird. Das Vorhandensein des Attributs unterdrückt diese Warnung. Endbeispiel

22.5.7.3 Das Attribut "DisallowNull"

Gibt an, dass ein Nullwert nicht als Eingabe zulässig ist, auch wenn der entsprechende Typ ihn zulässt.

Beispiel: Betrachten Sie die folgende Eigenschaft, in der Null der Standardwert ist, aber Clients können sie nur auf einen Wert ohne Null festlegen.

#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;
}

Der Get-Accessor kann den Standardwert nullzurückgeben, sodass der Compiler möglicherweise warnen kann, dass er vor dem Zugriff überprüft werden muss. Darüber hinaus warnt es Aufrufer, dass Aufrufer, auch wenn sie null sein könnten, nicht explizit auf Null festlegen sollten. Endbeispiel

22.5.7.4 Das Attribut DoesNotReturn

Gibt an, dass eine angegebene Methode nie zurückgegeben wird.

Beispiel: Betrachten Sie Folgendes:

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;
}

Das Vorhandensein des Attributs hilft dem Compiler auf verschiedene Weise. Zuerst kann der Compiler eine Warnung ausgeben, wenn es einen Pfad gibt, in dem die Methode beendet werden kann, ohne eine Ausnahme zu auslösen. Zweitens kann der Compiler Warnungen mit Nullwerte in jedem Code nach einem Aufruf dieser Methode unterdrücken, bis eine entsprechende Catch-Klausel gefunden wird. Drittens wirkt sich der nicht erreichbare Code nicht auf null-Zustände aus.

Das Attribut ändert die Reichweite nicht (§13.2) oder eine eindeutige Zuordnungsanalyse (§9.4) basierend auf dem Vorhandensein dieses Attributs. Es wird nur verwendet, um Warnungen zur Nullierbarkeit zu beeinträchtigen. Endbeispiel

22.5.7.5 Das Attribut DoesNotReturnIf

Gibt an, dass eine angegebene Methode niemals zurückgegeben wird, wenn der zugeordnete bool Parameter den angegebenen Wert aufweist.

Beispiel: Betrachten Sie Folgendes:

#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!;
}

Endbeispiel

22.5.7.6 Das MaybeNull-Attribut

Gibt an, dass ein nicht nullwertbarer Rückgabewert null sein kann.

Beispiel: Betrachten Sie die folgende generische Methode:

#nullable enable
public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }

Die Idee dieses Codes ist, dass, wenn T er durch stringersetzt wird, T? zu einer nullablen Anmerkung wird. Dieser Code ist jedoch nicht zulässig, da T er nicht auf einen Verweistyp beschränkt ist. Das Hinzufügen dieses Attributs löst jedoch das Problem:

#nullable enable
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }

Das Attribut informiert Aufrufer, dass der Vertrag einen nicht nullfähigen Typ impliziert, der Rückgabewert kann jedoch tatsächlich sein null. Endbeispiel

22.5.7.7 Das MaybeNullWhen-Attribut

Gibt an, dass ein nicht nullables Argument sein null kann, wenn die Methode den angegebenen bool Wert zurückgibt. Dies ähnelt dem MaybeNull Attribut (§22.5.7.6), enthält jedoch einen Parameter für den angegebenen Rückgabewert.

22.5.7.8 Das Attribut "NotNull"

Gibt an, dass ein nullwertebarer Wert niemals sein null wird, wenn die Methode zurückgegeben wird (und nicht ausgelöst).

Beispiel: Betrachten Sie Folgendes:

#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);
}

Wenn NULL-Verweistypen aktiviert sind, kompiliert die Methode ThrowWhenNull ohne Warnungen. Wenn diese Methode zurückgegeben wird, ist das value Argument garantiert nicht null. Es ist jedoch akzeptabel, einen Nullverweis aufzurufen ThrowWhenNull . Endbeispiel

22.5.7.9 Das Attribut "NotNullIfNotNull"

Gibt an, dass ein Rückgabewert nicht null ist, wenn das Argument für den angegebenen Parameter nicht nullangegeben ist.

Beispiel: Der NULL-Zustand eines Rückgabewerts kann vom Nullstatus eines oder mehrerer Argumente abhängen. Um die Analyse des Compilers zu unterstützen, wenn eine Methode immer einen Wert ungleich NULL zurückgibt, wenn bestimmte Argumente nicht null das NotNullIfNotNull Attribut sind, kann verwendet werden. Sehen Sie sich die folgende Methode an:

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

Wenn das url Argument nicht nullangegeben ist, null wird es nicht zurückgegeben. Wenn nullwerte Verweise aktiviert sind, funktioniert diese Signatur ordnungsgemäß, vorausgesetzt, die API akzeptiert niemals ein NULL-Argument. Wenn das Argument jedoch null sein könnte, kann der Rückgabewert auch NULL sein. Um diesen Vertrag richtig auszudrücken, kommentieren Sie diese Methode wie folgt:

#nullable enable
[return: NotNullIfNotNull("url")]
string? GetTopLevelDomainFromFullUrl(string? url) { ... }

Endbeispiel

22.5.7.10 Das Attribut "NotNullWhen"

Gibt an, dass ein nullables Argument nicht sein null wird, wenn die Methode den angegebenen bool Wert zurückgibt.

Beispiel: Die Bibliotheksmethode String.IsNullOrEmpty(String) gibt zurück true , wenn das Argument oder eine leere Zeichenfolge ist null . Dies ist eine Form der Nullüberprüfung: Aufrufer müssen das Argument nicht null überprüfen, wenn die Methode zurückgegeben wird false. Um eine Methode wie diese Nullwerte zu berücksichtigen, stellen Sie den Parametertyp als nullablen Verweistyp fest, und fügen Sie das Attribut "NotNullWhen" hinzu:

#nullable enable
bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }

Endbeispiel

22.6 Attribute für die Interoperabilität

Für die Interoperabilität mit anderen Sprachen kann ein Indexer mithilfe von indizierten Eigenschaften implementiert werden. Wenn kein IndexerName Attribut für einen Indexer vorhanden ist, wird der Name Item standardmäßig verwendet. Mit dem IndexerName Attribut kann ein Entwickler diese Standardeinstellung außer Kraft setzen und einen anderen Namen angeben.

Beispiel: Standardmäßig lautet Itemder Name eines Indexers . Dies kann wie folgt überschrieben werden:

[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
    get { ... }
    set { ... }
}

Nun lautet TheItemder Name des Indexers .

Endbeispiel