Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
23.1 Allgemein
Ein Großteil der C#-Sprache ermöglicht es dem Programmierer, deklarative Informationen über die im Programm definierten Entitäten anzugeben. Zum Beispiel wird die Zugänglichkeit einer Methode in einer Klasse durch die Auszeichnung mit den method_modifiers public, protected, internal und private angegeben.
C# ermöglicht Es Programmierern, neue Arten von deklarativen Informationen zu erfinden, die als Attributsbezeichnet werden. Entwickelnde können dann Attribute verschiedenen Entitäten des Programms zuordnen und die Attributinformationen in einer Umgebung zur Laufzeit abrufen.
Anmerkung: Ein Framework könnte beispielsweise ein
HelpAttributeAttribut definieren, das bestimmten Programmelementen (wie z. B. Klassen und Methoden) zugeordnet werden kann, um eine Zuordnung zwischen diesen Programmelementen und ihrer Dokumentation zu ermöglichen. Hinweisende
Attribute werden durch die Deklaration von Attributklassen (§23.2) definiert, die Positions- und benannte Parameter (§23.2.3) aufweisen können. Attribute werden an Entitäten in einem C#-Programm mithilfe von Attributspezifikationen (§23.3) angefügt und können zur Laufzeit als Attributinstanzen (§23.4) abgerufen werden.
23.2 Attributklassen
23.2.1 Allgemein
Eine Klasse, die direkt oder indirekt von der abstrakten Klasse System.Attribute abstammt, ist eine Attributklasse. Die Deklaration einer Attributklasse definiert eine neue Art von Attribut, das auf Entitäten des Programms platziert werden kann. Konventionell werden Attributklassen mit dem Suffix Attribute benannt. Die Verwendung eines Attributs kann dieses Suffix entweder einschließen oder weglassen.
Eine generische Klassendeklaration darf System.Attribute nicht als direkte oder indirekte Basisklasse verwenden.
Beispiel:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attributeEnde des Beispiels
23.2.2 Attributverwendung
Das Attribut AttributeUsage (§23.5.2) wird verwendet, um zu beschreiben, wie eine Attributklasse verwendet werden kann.
AttributeUsage verfügt über einen Positionsparameter (§23.2.3), mit dem eine Attributklasse die Arten von Programmentitäten angeben kann, für die sie verwendet werden kann.
Beispiel: Das folgende Beispiel definiert eine Attributklasse mit dem Namen
SimpleAttribute, die nur auf class_declarations und interface_declarations platziert werden kann, und zeigt mehrere Verwendungen des AttributsSimple.[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}Obwohl dieses Attribut mit dem Namen
SimpleAttributedefiniert ist, kann bei der Verwendung dieses Attributs das SuffixAttributeweggelassen werden, so dass sich der KurznameSimpleergibt. Somit ist das obige Beispiel semantisch äquivalent zu folgendem[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}Ende des Beispiels
AttributeUsage hat einen benannten Parameter (§23.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 true ist, dann ist diese Attributklasse eine Mehrzweck-Attributklasse und kann mehr als einmal für eine Entität angegeben werden. Wenn AllowMultiple für eine Attributklasse false ist oder nicht angegeben wird, dann ist diese Attributklasse eine Einzelverwendungs-Attributklasse und kann höchstens einmal für eine Entität angegeben werden.
Beispiel: Das folgende Beispiel definiert eine Attributklasse mit Mehrfachverwendung namens
AuthorAttributeund zeigt eine Klassendeklaration mit zwei Verwendungen des AttributsAuthor:[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 { ... }Ende des Beispiels
AttributeUsage hat einen weiteren benannten Parameter (§23.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 ist, dann wird dieses Attribut vererbt. Wenn Inherited für eine Attributklasse false ist, dann wird dieses Attribut nicht vererbt. Wenn es nicht spezifiziert ist, ist sein Standardwert true.
Eine Attributklasse X, der kein Attribut AttributeUsage zugeordnet ist, wie in
class X : Attribute { ... }
ist äquivalent zu:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
23.2.3 Positions- und benannte Parameter
Attributklassen können positionale Parametersowie benannte Parameters haben. Jeder öffentliche Instanz-Konstruktor für eine Attributklasse definiert eine gültige Sequenz von Positionsparametern für diese Attributklasse. Jedes nicht-statische öffentliche Schreib-Lese-Feld und jede Eigenschaft für eine Attributklasse definiert einen benannten Parameter für die Attributklasse. Damit eine Eigenschaft einen benannten Parameter definieren kann, muss diese Eigenschaft sowohl einen öffentlichen get-Accessor als auch einen öffentlichen set-Accessor haben.
Beispiel: Das folgende Beispiel definiert eine Attributklasse mit dem Namen
HelpAttribute, die einen Positionsparameter,url, und einen benannten Parameter,Topic, hat. Obwohl es nicht statisch und öffentlich ist, definiert die EigenschaftUrlkeinen benannten Parameter, da sie nicht schreibgeschützt ist. Es werden auch zwei Verwendungsmöglichkeiten für dieses Attribut gezeigt:[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 { }Ende des Beispiels
23.2.4 Attributparametertypen
Die Typen von Positions- und benannten Parametern für eine Attributklasse sind auf den Attributparametertyps beschränkt. Dies sind:
- Einer der folgenden Typen:
bool,byte,char,double,float,int,long,sbyte,short,string,uint,ulong,ushort. - Der
object-Typ. - Der
System.Type-Typ. - Enumerationstypen.
- Eindimensionale Arrays der oben genannten Typen.
- Ein Konstruktorargument oder ein öffentliches Feld, das nicht einem dieser Typen entspricht, darf nicht als Positions- oder benannter Parameter in einer Attributspezifikation verwendet werden.
23.3 Attributspezifikation
Die Anwendung eines zuvor definierten Attributs auf eine Programmentität wird als Attributspezifikation bezeichnet. Ein Attribut ist eine zusätzliche deklarative Information, die für eine Entität eines Programms angegeben wird. Attribute können im globalen Bereich angegeben werden (um Attribute für die enthaltende Assembly oder das enthaltende Modul anzugeben) und für type_declarations (§14.7), class_member_declarations (§15.3), interface_member_declarations (§19.19.3) 4), struct_member_declarations (§16.3), enum_member_declarations (§20.2), accessor_declarations (§15.7.3), event_accessor_declarations (§15.8)), Elemente von parameter_list(§15.6.2) und Elemente von type_parameter_list(§15.2.3).
Attribute werden in Attributabschnittenangegeben. Ein Attributabschnitt besteht aus einem Paar eckiger Klammern, die eine durch Komma getrennte Liste von einem oder mehreren Attributen umgeben. Die Reihenfolge, in der die Attribute in einer solchen Liste angegeben werden, und die Reihenfolge, in der die Abschnitte, die derselben Entität zugeordnet sind, angeordnet sind, ist unerheblich. Zum Beispiel sind die Attributangaben [A][B], [B][A], [A, B] und [B, A] gleichwertig.
global_attributes
: global_attribute_section+
;
global_attribute_section
: '[' 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_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 identifier die Schreibweise assembly oder module haben, wobei Gleichheit die in §6.4.3 definierte ist. Für die Produktion attribute_target und im folgenden Text muss identifier eine Schreibweise haben, die nicht gleich assembly oder module ist, wobei die gleiche Definition von Gleichheit wie oben verwendet wird.
Ein Attribut besteht aus einem attribute_name und einer optionalen Liste von positionellen 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 einem attribute_argument_expression, die zusammen den gleichen Regeln unterliegen wie die einfache Zuweisung. Die Reihenfolge der benannten Argumente ist nicht von Bedeutung.
Anmerkung: Der Einfachheit halber ist ein nachgestelltes Komma in einem global_attribute_section und einem attribute_section möglich, so wie es auch in einem array_initializer möglich ist (§17.7). Hinweisende
Der attribute_name identifiziert eine Attributklasse.
Wenn ein Attribut auf der globalen Ebene platziert wird, ist ein global_attribute_target_specifier erforderlich. Wenn global_attribute_target gleich ist mit:
-
assembly– ist das Ziel die enthaltende Assembly -
module– ist das Ziel das enthaltende Modul
Andere Werte für global_attribute_target sind nicht möglich.
Die standardisierten attribute_target Namen sind event, field, method, param, property, return, type, und typevar. Diese Zielnamen dürfen nur in den folgenden Kontexten verwendet werden:
-
event– ein Ereignis. -
field– ein Feld. Ein feldartiges Ereignis (d.h. eines ohne Accessoren) (§15.8.2) und eine automatisch implementierte Eigenschaft (§15.7.4) können ebenfalls ein Attribut mit diesem Ziel haben. -
method– ein Konstruktor, ein Finalisierer, eine Methode, ein Operator, ein Property-Accessor zum Abrufen und Festlegen, ein Indexer-Accessor zum Abrufen und Festlegen und ein Ereignis-Accessor zum Hinzufügen und Entfernen. Ein feldartiges Ereignis (d.h. eines ohne Accessoren) kann auch ein Attribut mit diesem Ziel haben. -
param– ein Property-Accessor zum Festlegen eines Eigenschaftssets, ein Accessor zum Setzen eines Indexers, Accessoren zum Hinzufügen und Entfernen von Ereignissen und ein Parameter in einem Konstruktor, einer Methode und einem Operator. -
property– eine Eigenschaft und ein Indexer. -
return– ein Delegat, eine Methode, ein Operator, ein Property-Accessor und ein Indexer-Accessor. -
type– ein Delegat, eine Klasse, ein Struct, eine Enum und eine Schnittstelle. -
typevar– ein Typ-Parameter.
Bestimmte Kontexte erlauben die Angabe eines Attributs für mehr als ein Ziel. Ein Programm kann das Ziel explizit angeben, indem es einen attribute_target_specifier einfügt. Ohne einen attribute_target_specifier wird eine Voreinstellung verwendet, aber ein attribute_target_specifier kann verwendet werden, um die Voreinstellung zu bestätigen oder zu überschreiben. Die Kontexte werden wie folgt aufgelöst:
- Für ein Attribut in einer Delegatdeklaration ist das Standardziel der Delegat. Andernfalls, wenn das attribute_target gleich ist mit:
-
type– ist das Ziel der Delegat -
return– ist das Ziel der Rückgabewert
-
- Für ein Attribut bei einer Methodendeklaration ist das Standardziel die Methode. Andernfalls, wenn das attribute_target gleich ist mit:
-
method– das Ziel ist die Methode -
return– ist das Ziel der Rückgabewert
-
- Für ein Attribut bei einer Operator-Deklaration ist das Standardziel der Operator. Andernfalls, wenn das attribute_target gleich ist mit:
-
method– das Ziel ist der Operator -
return– ist das Ziel der Rückgabewert
-
- Für ein Attribut bei einer Get-Accessor-Deklaration für eine Property- oder Indexer-Deklaration ist das Standardziel die zugehörige Methode. Andernfalls, wenn das attribute_target gleich ist mit:
-
method– das Ziel ist die zugehörige Methode -
return– ist das Ziel der Rückgabewert
-
- Für ein Attribut, das in einem Set-Accessor für eine Property- oder Indexer-Deklaration angegeben ist, ist das Standardziel die zugehörige Methode. Andernfalls, wenn das attribute_target gleich ist mit:
-
method– das Ziel ist die zugehörige Methode -
param– das Ziel ist der einzige implizite Parameter
-
- Für ein Attribut in einer automatisch implementierten Eigenschaftsdeklaration ist das Standardziel die Eigenschaft. Andernfalls, wenn das attribute_target gleich ist mit:
-
field– das Ziel ist das vom Compiler generierte Backing-Feld für die Eigenschaft
-
- Für ein Attribut, das in einer Ereignisdeklaration angegeben ist, die event_accessor_declarations auslässt, ist das Standardziel die Ereignisdeklaration. Andernfalls, wenn das attribute_target gleich ist mit:
-
event– das Ziel ist die Ereignis-Deklaration -
field– das Ziel ist das Feld -
method– die Ziele sind die Methoden
-
- Im Falle einer Ereignisdeklaration, die event_accessor_declarations nicht auslässt, ist das Standardziel die Methode.
-
method– das Ziel ist die zugehörige Methode -
param– das Ziel ist der einzige Parameter
-
In allen anderen Kontexten ist die Aufnahme eines attribute_target_specifier erlaubt, aber nicht notwendig.
Beispiel: eine Klassendeklaration kann den Spezifizierer entweder enthalten oder weglassen
type:[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}Ende des Beispiels.
Eine Implementierung kann andere attribute_targets akzeptieren, deren Zweck durch die Implementierung definiert wird. Eine Implementierung, die ein solches Attribut_Ziel nicht erkennt, gibt eine Warnung aus und ignoriert die enthaltende Attribut_section.
Konventionell werden Attributklassen mit dem Suffix Attribute benannt. Ein attribute_name kann dieses Suffix entweder enthalten oder weglassen. Konkret wird ein attribute_name wie folgt aufgelöst:
- Wenn der äußerste rechte Bezeichner des attribute_name ein wörtlicher Bezeichner ist (§6.4.3), dann wird der attribute_name als type_name (§7.8) aufgelöst. Wenn das Ergebnis kein von
System.Attributeabgeleiteter Typ ist, tritt ein Kompilierfehler auf. - Andernfalls,
- Der attribute_name wird als type_name (§7.8) behoben, mit der Ausnahme, dass alle Fehler unterdrückt werden. Wenn diese Auflösung erfolgreich ist und zu einem von
System.Attributeabgeleiteten Typ führt, ist der Typ das Ergebnis dieses Schrittes. - Die Zeichen
Attributewerden an den äußersten rechten Bezeichner im attribute_name angehängt und die resultierende Zeichenfolge wird als type_name (§7.8) aufgelöst, mit der Ausnahme, dass eventuelle Fehler unterdrückt werden. Wenn diese Auflösung erfolgreich ist und zu einem vonSystem.Attributeabgeleiteten Typ führt, ist der Typ das Ergebnis dieses Schrittes.
- Der attribute_name wird als type_name (§7.8) behoben, mit der Ausnahme, dass alle Fehler unterdrückt werden. Wenn diese Auflösung erfolgreich ist und zu einem von
Wenn genau einer der beiden obigen Schritte zu einem von System.Attribute abgeleiteten Typ führt, dann ist dieser Typ das Ergebnis von attribute_name. Andernfalls tritt ein Kompilierfehler auf.
Beispiel: Wenn eine Attributklasse sowohl mit als auch ohne dieses Suffix gefunden wird, ist eine Mehrdeutigkeit vorhanden und es kommt zu einem Kompilierfehler. Wenn der attribute_name so geschrieben wird, dass sein ganz rechter Identifikator ein wortwörtlicher Identifikator ist (§6.4.3), dann wird nur ein Attribut ohne Suffix gefunden, so dass eine solche Mehrdeutigkeit behoben 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 mit den Namen
ExampleundExampleAttribute. Das Attribut[Example]ist mehrdeutig, da es sich entweder aufExampleoderExampleAttributebeziehen könnte. Die Verwendung eines wortwörtlichen Bezeichners bietet die Möglichkeit, in solchen seltenen Fällen den genauen Intent zu spezifizieren. Das Attribut[ExampleAttribute]ist nicht mehrdeutig (auch wenn es eine Attributklasse mit dem NamenExampleAttributeAttribute!) gibt. Wenn die Deklaration für die KlasseExampleentfernt wird, beziehen sich beide Attribute auf die Attributklasse mit dem NamenExampleAttribute, wie folgt:[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 {}Ende des Beispiels
Es ist ein Kompilierfehler, eine Attributklasse, die nur einmal verwendet wird, mehr als einmal 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 Kompilierfehler, weil versucht wird,
HelpString, das eine Attributklasse zur einmaligen Verwendung ist, mehr als einmal für die Deklaration vonClass1zu verwenden.Ende des Beispiels
Ein Ausdruck E ist ein Attribut_Argument_Ausdruck, wenn alle der folgenden Aussagen wahr sind:
- Der Typ von
Eist ein Attributparametertyp (§23.2.4). - Bei der Kompilierung kann der Wert von
Ein einen der folgenden Werte behoben werden:- Ein konstanter Wert.
- Ein
System.Type-Objekt, das über einen typeof_expression (§12.8.18) abgerufen wird, der einen nicht-generischen Typ, einen abgeschlossenen konstruierten Typ (§8.4.3) oder einen ungebundenen generischen Typ (§8.4.4) angibt, aber 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; }Ende des Beispiels
Die Attribute eines Typs, der in mehreren Elementen deklariert ist, werden durch die Kombination der Attribute der einzelnen Elemente in einer nicht spezifizierten Reihenfolge bestimmt. Wenn dasselbe Attribut in mehreren Elementen vorkommt, ist dies gleichbedeutend mit der mehrfachen Angabe dieses Attributs in dem Element.
Beispiel: Die beiden Elemente:
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}sind äquivalent zu der folgenden einzelnen Deklaration:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}Ende des Beispiels
Attribute auf Typparametern werden auf die gleiche Weise kombiniert.
23.4-Attributinstanzen
23.4.1 Allgemein
Eine Attribut-Instanz ist eine Instanz, die ein Attribut zur Ausführungszeit repräsentiert. Ein Attribut wird mit einer Attributklasse, positionalen Argumenten und benannten Argumenten definiert. Eine Attributinstanz ist eine Instanz der Attributklasse, die mit den Positions- und benannten Argumenten initialisiert wird.
Der Abruf einer Attributinstanz umfasst sowohl die Verarbeitung zur Kompilierungszeit als auch zur Laufzeit, wie in den folgenden Unterabschnitten beschrieben.
23.4.2 Kompilierung eines Attributs
Die Kompilierung eines Attributs mit der Attributklasse T, positional_argument_listP, named_argument_listN, und spezifiziert auf einer Entität E, wird über die folgenden Schritte in eine Assembly A kompiliert:
- Folgen Sie den Verarbeitungsschritten zur Kompilierzeit für die Kompilierung eines object_creation_expression der Form new
T(P). Diese Schritte führen entweder zu einem Kompilierfehler oder bestimmen einen Instanz-KonstruktorCaufT, der zur Laufzeit aufgerufen werden kann. - Wenn
Ckeine öffentliche Zugriffsmöglichkeit hat, dann tritt ein Kompilierfehler auf. - Für jedes named_argument
ArginN:- Sei
Nameder Identifikator des named_argumentArg. -
Nameidentifiziert ein nicht-statisches öffentliches Feld oder eine Eigenschaft aufT. WennTkein solches Feld oder keine solche Eigenschaft hat, tritt ein Kompilierfehler auf.
- Sei
- Wenn eines der Werte innerhalb positional_argument_list
Poder eines der Werte innerhalb named_argument_listNvom TypSystem.Stringist und der Wert nicht wohlgeformt ist, wie durch den Unicode-Standard definiert, wird durch implementierungsdefiniert, ob der kompilierte Wert dem vom Laufzeitwert abgerufenen Wert entspricht (§23.4.3).Anmerkung: Eine Zeichenfolge, die eine UTF-16 Code-Einheit mit hohem Surrogat enthält, auf die nicht unmittelbar eine Code-Einheit mit niedrigem Surrogat folgt, ist beispielsweise nicht wohlgeformt. Hinweisende
- Speichern Sie die folgenden Informationen (für die Laufzeit-Instanziierung des Attributs) in der Assembly, die der Compiler als Ergebnis der Kompilierung des Programms, das das Attribut enthält, ausgibt: die Attributklasse
T, den Instanz-KonstruktorCaufT, die positional_argument_listP, die named_argument_listNund die zugehörige Entität des ProgrammsE, wobei die Werte bei der Kompilierung vollständig behoben werden.
23.4.3 Laufzeitabruf einer Attributinstanz
Mit den in §23.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:
- Folgen Sie den Verarbeitungsschritten zur Laufzeit für die Ausführung einer object_creation_expression der Form
new T(P), unter Verwendung des Instanz-KonstruktorsCund der bei der Kompilierung festgelegten Werte. Diese Schritte führen entweder zu einer Ausnahme, oder erzeugen eine InstanzOvonT. - Für jedes named_argument
ArginN, in dieser Reihenfolge:- Sei
Nameder Identifikator des named_argumentArg. WennNamekein nicht-statisches öffentliches Schreib-Lese-Feld oder eine Eigenschaft aufOidentifiziert, wird eine Ausnahme ausgelöst. - Sei
Valuedas Ergebnis der Evaluierung des Attribut_Argument_Ausdrucks vonArg. - Wenn
Nameein Feld aufOidentifiziert, dann legen Sie dieses Feld aufValuefest. - Andernfalls identifiziert Name eine Eigenschaft auf
O. Legen Sie diese Eigenschaft auf Value fest. - Das Ergebnis ist
O, eine Instanz der AttributklasseT, die mit der positional_argument_listPund der named_argument_listNinitialisiert wurde.
- Sei
Anmerkung: Das Format für die Speicherung von
T,C,P,N(und die Verknüpfung mitE) inAund der Mechanismus für die Angabe vonEund den Abruf vonT,C,P,NausA(und damit die Art und Weise, wie eine Attribut-Instanz zur Laufzeit abgerufen wird) liegt außerhalb des Bereichs dieser Spezifikation. Hinweisende
23.5 Reservierte Attribute
23.5.1 Allgemein
Eine Reihe von Attributen beeinflussen die Sprache in irgendeiner Weise. Zu diesen Attributen zählen Folgende:
-
System.AttributeUsageAttribute(§23.5.2), das verwendet wird, um die Verwendungsmöglichkeiten einer Attributklasse zu beschreiben. -
System.Diagnostics.ConditionalAttribute(§23.5.3) ist eine mehrverwendige Attributklasse, die zum Definieren bedingter Methoden und bedingter Attributklassen verwendet wird. Dieses Attribut zeigt eine Bedingung an, indem es ein bedingtes Kompilierungssymbol testet. -
System.ObsoleteAttribute(§23.5.4), das verwendet wird, um ein Element als veraltet zu kennzeichnen. -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute(§23.5.5), das zum Einrichten eines Aufgaben-Generators für eine asynchrone Methode verwendet wird. -
System.Runtime.CompilerServices.CallerLineNumberAttribute(§23.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute(§23.5.6.3) undSystem.Runtime.CompilerServices.CallerMemberNameAttribute(§23.5.6.4), die verwendet werden, um Informationen zum aufrufenden Kontext an optionale Parameter zu liefern. -
System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.8), das zum Angeben des Parameters für das Abbruchtoken in einem asynchronen Iterator verwendet wird.
Die attribute der nullablen statischen Analyse (§23.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 die Ausführung eines C#-Programms beeinflussen.
23.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 Attribut AttributeUsage ausgezeichnet ist, muss entweder direkt oder indirekt von System.Attribute abgeleitet sein. Andernfalls tritt ein Kompilierfehler auf.
Hinweis: Ein Beispiel für die Verwendung dieses Attributs finden Sie unter §23.2.2. Hinweisende
23.5.3 Das Attribut "Bedingt"
23.5.3.1 Allgemein
Das Attribut Conditional ermöglicht die Definition von bedingten Methodenund bedingten Attributklassenes.
23.5.3.2 Bedingte Methoden
Eine Methode, die mit dem Attribut Conditional ausgezeichnet ist, ist eine bedingte Methode. Jede bedingte Methode ist somit mit den in ihren Conditional Attributen deklarierten bedingten Kompilierungssymbolen verbunden.
Beispiel:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }deklariert
Eg.Mals eine bedingte Methode, die mit den beiden bedingten KompilierungssymbolenALPHAundBETAverbunden ist.Ende des Beispiels
Ein Aufruf einer bedingten Methode ist enthalten, wenn eines oder mehrere der zugehörigen bedingten Kompilierungssymbole an der Aufrufstelle definiert sind, andernfalls wird der Aufruf ausgelassen.
Eine bedingte Methode unterliegt den folgenden Einschränkungen:
- Die bedingte Methode muss eine Methode in einer class_declaration oder struct_declaration sein. Ein Kompilierfehler tritt auf, wenn das Attribut
Conditionalfür eine Methode in einer Schnittstellendeklaration angegeben wird. - Die bedingte Methode darf kein Accessor einer Eigenschaft, eines Indexers oder eines Ereignisses sein.
- Die bedingte Methode muss einen Rückgabetyp von
voidhaben. - Die bedingte Methode darf nicht mit dem Modifikator
overridegekennzeichnet sein. Eine bedingte Methode kann jedoch mit demvirtual-Modifikator gekennzeichnet werden. Überschreibungen einer solchen Methode sind implizit bedingt und dürfen nicht explizit mit einemConditional-Attribut gekennzeichnet werden. - Bei der bedingten Methode darf es sich nicht um eine Implementierung einer Schnittstellenmethode handeln. Andernfalls tritt ein Kompilierfehler auf.
- Die Parameter der bedingten Methode dürfen keine Ausgabeparameter sein.
Hinweis: Attribute mit einem
AttributeUsage(§23.2.2) einschließlichAttributeTargets.Methodkönnen normalerweise auf Accessoren von Eigenschaften, Indexern und Ereignissen angewendet werden. Die oben genannten Einschränkungen verbieten diese Verwendung desConditionalAttributs. Hinweisende
Darüber hinaus tritt ein Kompilierfehler auf, wenn ein Delegat von 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(); } }deklariert
Class1.Mals eine bedingte Methode. DieClass2-Methode vonTestruft diese Methode auf. Da das bedingte KompilierungssymbolDEBUGdefiniert ist, wird bei einem Aufruf vonClass2.TestauchMaufgerufen. Wäre das SymbolDEBUGnicht definiert worden, würdeClass2.TestnichtClass1.Maufrufen.Ende des Beispiels
Es ist wichtig zu verstehen, dass die Einbeziehung oder der Ausschluss eines Aufrufs einer bedingten Methode durch die bedingten Kompilierungssymbole 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
Class2undClass3enthalten jeweils Aufrufe der bedingten MethodeClass1.F, die davon abhängt, obDEBUGdefiniert ist oder nicht. Da dieses Symbol im Kontext vonClass2, aber nichtClass3definiert ist, ist der Aufruf vonFinClass2enthalten, während der Aufruf vonFinClass3ausgelassen wird.Ende des Beispiels
Die Verwendung von bedingten Methoden in einer Vererbungskette kann verwirrend sein. Aufrufe einer bedingten Methode durch base, der Form base.M, unterliegen den normalen Regeln für bedingte Methodenaufrufe.
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 } }
Class2enthält einen Aufruf des in seiner Basisklasse definiertenM. Dieser Aufruf wird ausgelassen, da die Basismethode durch das Vorhandensein des SymbolsDEBUGbedingt ist, das undefiniert ist. Daher schreibt die Methode nur „Class2.M executed“ in die Konsole. Eine umsichtige Verwendung von pp_declarations kann solche Probleme beseitigen.Ende des Beispiels
23.5.3.3 Bedingte Attributklassen
Eine Attributklasse (§23.2), die mit einem oder Conditional mehreren Attributen versehen ist, ist eine bedingte Attributklasse. Eine bedingte Attributklasse ist also mit den bedingten Kompilierungssymbolen verbunden, die in ihren Conditional Attributen deklariert sind.
Beispiel:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}deklariert
TestAttributeals eine bedingte Attributklasse, die mit den bedingten KompilierungssymbolenALPHAundBETAverbunden ist.Ende des Beispiels
Attributspezifikationen (§23.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 Einbeziehung oder der Ausschluss einer Attributspezifikation einer bedingten Attributklasse durch die bedingten Kompilierungssymbole am Punkt 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
Class1undClass2sind jeweils mit dem AttributTestausgezeichnet, das davon abhängt, obDEBUGdefiniert ist oder nicht. Da dieses Symbol im Kontext vonClass1, aber nichtClass2definiert ist, wird die Spezifikation des Attributs Test aufClass1einbezogen, während die Spezifikation des AttributsTestaufClass2weggelassen wird.Ende des Beispiels
23.5.4 Das veraltete Attribut
Das Attribut Obsolete wird verwendet, um Typen und Member von Typen zu markieren, die nicht mehr verwendet werden sollten.
Wenn ein Programm einen Typ oder ein Mitglied verwendet, der mit dem attribut Obsolete versehen ist, gibt ein Compiler eine Warnung oder einen Fehler aus. Insbesondere gibt ein Compiler eine Warnung aus, wenn kein Fehlerparameter angegeben wird oder wenn der Fehlerparameter angegeben wird und der Wert falsehat. Ein Compiler muss einen Fehler ausgeben, 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
Awird mit dem AttributObsoleteausgezeichnet. Jede Verwendung vonAinMainführt zu einer Warnung, die die angegebene Nachricht enthält: „Diese Klasse ist veraltet; verwenden Sie stattdessen KlasseB“.Ende des Beispiels
23.5.5 Das AsyncMethodBuilder-Attribut
Dieses Attribut wird in §15.14.1 beschrieben.
23.5.6 Attribute für Anruferinformationen
23.5.6.1 Allgemein
Für Zwecke wie z. B. die Protokollierung und das Reporting ist es manchmal nützlich, dass ein Funktionsmitglied bestimmte Informationen über den aufrufenden Code zur Kompilierzeit abruft. Die Caller-info-Attribute bieten eine Möglichkeit, solche Informationen transparent zu übergeben.
Wenn ein optionaler Parameter mit einem der Caller-info-Attribute annotiert ist, führt das Weglassen des entsprechenden Arguments in einem Aufruf nicht unbedingt dazu, dass der Standardparameterwert ersetzt wird. Wenn die angegebenen Informationen über den aufrufenden Kontext verfügbar sind, werden diese Informationen stattdessen 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 von
Log()ohne Argumente würde die Zeilennummer und den Dateipfad des Aufrufs sowie den Namen des Members ausgeben, in dem der Aufruf erfolgte.Ende des Beispiels
Caller-Info-Attribute können bei optionalen Parametern überall vorkommen, auch in Delegierungsdeklarationen. Die spezifischen Caller-Info-Attribute haben jedoch Beschränkungen für die Typen der Parameter, die sie attribuieren können, so dass es immer eine implizite Konversion von einem ersetzten Wert zum Parametertyp gibt.
Es ist ein Fehler, das gleiche Caller-Info-Attribut für einen Parameter sowohl des definierenden als auch des implementierenden Elements einer partiellen Methodendeklaration zu verwenden. Nur Caller-Info-Attribute im definierenden Element werden angewendet, während Caller-Info-Attribute, die nur im implementierenden Element vorkommen, ignoriert werden.
Die Informationen über den Aufrufer haben keinen Einfluss auf die Überladungsauflösung. Da die zugewiesenen optionalen Parameter im Quellcode des Aufrufers nach wie vor weggelassen werden, ignoriert die Überladungsauflösung diese Parameter auf die gleiche Weise wie andere weggelassene optionale Parameter (§12.6.4).
Informationen über den Aufrufer werden nur ersetzt, wenn eine Funktion explizit im Quellcode aufgerufen wird. Implizite Aufrufe wie z. B. implizite übergeordnete Konstruktoraufrufe haben keinen Speicherort im Quellcode und werden nicht durch Aufruferinformationen ersetzt. Auch bei Aufrufen, die dynamisch gebunden sind, werden keine Informationen über den Aufrufer ersetzt. Wenn in solchen Fällen ein Caller-Info-Attribut-Parameter weggelassen wird, wird stattdessen der angegebene Standardwert des Parameters verwendet.
Eine Ausnahme sind Abfrageausdrücke. Diese werden als syntaktische Erweiterungen betrachtet. Wenn die Aufrufe, zu denen sie erweitert werden, optionale Parameter mit Caller-Info-Attributen auslassen, werden die Caller-Informationen ersetzt. Als Speicherort wird der Speicherort der Abfrageklausel verwendet, aus der der Aufruf generiert wurde.
Wenn mehr als ein Caller-Info-Attribut für einen bestimmten Parameter angegeben ist, werden sie in der folgenden Reihenfolge erkannt: CallerLineNumber, CallerFilePath, CallerMemberName. Betrachten Sie die folgende Parameterdeklaration:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber hat Vorrang und die beiden anderen Attribute werden ignoriert. Wenn CallerLineNumber weggelassen würde, hätte CallerFilePath Vorrang und CallerMemberName würde ignoriert werden. Die lexikalische Reihenfolge dieser Attribute ist irrelevant.
23.5.6.2 Das CallerLineNumber-Attribut
Das Attribut System.Runtime.CompilerServices.CallerLineNumberAttribute ist bei optionalen Parametern möglich, wenn es eine standardmäßige implizite Konversion (§10.4.2) vom konstanten Wert int.MaxValue zum Typ des Parameters gibt. Dadurch wird sichergestellt, dass jede nicht-negative Zeilennummer bis zu diesem Wert ohne Fehler übergeben werden kann.
Wenn ein Funktionsaufruf von einem Speicherort im Quellcode einen optionalen Parameter mit dem CallerLineNumberAttribute auslässt, dann wird ein numerisches Literal, das die Zeilennummer dieses Speicherorts repräsentiert, als Argument für den Aufruf anstelle des Standardparameterwerts verwendet.
Wenn sich der Aufruf über mehrere Zeilen erstreckt, ist die gewählte Zeile von der Implementierung abhängig.
Die Zeilennummer kann durch #line-Direktiven beeinflusst werden (§6.5.8).
23.5.6.3 Das CallerFilePath-Attribut
Das Attribut System.Runtime.CompilerServices.CallerFilePathAttribute ist bei optionalen Parametern möglich, wenn es eine standardmäßige implizite Konversion (§10.4.2) von string zum Typ des Parameters gibt.
Wenn ein Funktionsaufruf von einem Speicherort im Quellcode einen optionalen Parameter mit dem CallerFilePathAttribute auslässt, dann wird eine Zeichenfolge, die den Dateipfad dieses Speicherorts darstellt, als Argument für den Aufruf anstelle des Standardparameterwerts verwendet.
Das Format des Dateipfads ist von der Implementierung abhängig.
Der Dateipfad kann durch #line Direktiven beeinflusst werden (§6.5.8).
23.5.6.4 Das CallerMemberName-Attribut
Das Attribut System.Runtime.CompilerServices.CallerMemberNameAttribute ist bei optionalen Parametern möglich, wenn es eine standardmäßige implizite Konversion (§10.4.2) von string zum Typ des Parameters gibt.
Wenn ein Funktionsaufruf von einem Speicherort innerhalb des Bodys eines Funktionsmitglieds oder innerhalb eines Attributs, das auf das Funktionsmitglied selbst oder dessen Rückgabetyp, Parameter oder Typparameter im Code angewendet wird, einen optionalen Parameter mit dem CallerMemberNameAttribute auslässt, dann wird eine Zeichenfolge, die den Namen dieses Mitglieds repräsentiert, als Argument für den Aufruf anstelle des Standardparameterwerts verwendet.
Bei Aufrufen, die innerhalb von generischen Methoden erfolgen, wird nur der Methodenname selbst verwendet, ohne die Liste der Typparameter.
Bei Aufrufen, die innerhalb von expliziten Implementierungen von Membern der Schnittstelle erfolgen, wird nur der Methodenname selbst verwendet, ohne die vorangehende Schnittstellenqualifikation.
Bei Aufrufen, die innerhalb von Property-Accessors oder Ereignis-Accessors erfolgen, wird der Member-Name der Property oder des Ereignisses selbst verwendet.
Für Aufrufe, die innerhalb von Indexer-Accessoren auftreten, wird der verwendete Membername von einem IndexerNameAttribute (§23.6) für das Indexermemmemm, sofern vorhanden, oder auf andere Weise der Standardname Item angegeben.
Bei Aufrufen, die innerhalb von Feld- oder Ereignisinitialisierern erfolgen, ist der verwendete Member-Name der Name des Feldes oder Ereignisses, das initialisiert wird.
Bei Aufrufen, die innerhalb von Deklarationen von Instanz-Konstruktoren, statischen Konstruktoren, Finalisierern und Operatoren erfolgen, ist der verwendete Membername von der Implementierung abhängig.
23.5.7 Codeanalyseattribute
23.5.7.1 Allgemein
Die Attribute in diesem Unterabschnitt werden verwendet, um zusätzliche Informationen bereitzustellen, zur Unterstützung eines Compilers, der Überprüfungen auf Nullbarkeit und Nullzustands-Diagnosen (§8.9.5) bietet. Ein Compiler ist nicht verpflichtet, eine Null-Status-Diagnose durchzuführen. Das Vorhandensein oder Nichtvorhandensein dieser Attribute hat keinen Einfluss auf die Sprache oder das Verhalten eines Programms. Ein Compiler, der keine Null-State-Diagnose anbietet, liest und ignoriert das Vorhandensein dieser Attribute. Ein Compiler, der Nullwertdiagnosen bereitstellt, verwendet die in diesem Unterabschnitt definierte Bedeutung für jedes dieser Attribute, die für seine Diagnose verwendet werden.
Die Code-Analyse-Attribute werden im Namespace System.Diagnostics.CodeAnalysis deklariert.
| Attribut | Bedeutung |
|---|---|
AllowNull (§23.5.7.2) |
Ein Non-Nullable-Argument darf NULL sein. |
DisallowNull (§23.5.7.3) |
Ein Nullable-Argument darf nie NULL sein. |
MaybeNull (§23.5.7.6) |
Ein Non-Nullable-Rückgabewert darf NULL sein. |
NotNull (§23.5.7.8) |
Ein Nullable-Rückgabetyp ist niemals NULL. |
MaybeNullWhen (§23.5.7.7) |
Ein Non-Nullable-Argument darf NULL sein, wenn die Methode den angegebenen bool-Wert zurückgibt. |
NotNullWhen (§23.5.7.10) |
Ein nullbares Argument ist nicht null, wenn die Methode den angegebenen bool Wert zurückgibt. |
NotNullIfNotNull (§23.5.7.9) |
Ein Rückgabewert ist nicht null, wenn das Argument für den angegebenen Parameter nicht null ist. |
DoesNotReturn (§23.5.7.4) |
Diese Methode gibt niemals zurück. |
DoesNotReturnIf (§23.5.7.5) |
Die Methode gibt niemals ein Ergebnis zurück, wenn der zugeordnete Parameter bool einen angegebenen Wert aufweist. |
Die folgenden Unterclauses in §23.5.7.1 sind bedingt normativ.
23.5.7.2 Das AllowNull-Attribut
Gibt an, dass ein Nullwert als Eingabe erlaubt ist, auch wenn der entsprechende Typ dies nicht zulässt.
Beispiel: Betrachten Sie die folgende Lese-/Schreibeigenschaft, die niemals
nullzurückgibt, weil sie einen vernünftigen Standardwert hat. Ein Benutzer kann jedoch dem Set-Accessor den Wert Null übergeben, 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 für diese Eigenschaft
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNullohne des Attributs kann ein Compiler möglicherweise eine Warnung ausgeben, da die Eigenschaft vom Non-Nullable-Typ auf einen Nullwert festgelegt zu sein scheint. Durch das Vorhandensein des Attributs wird diese Warnung unterdrückt. Ende des Beispiels
23.5.7.3 Das Attribut "DisallowNull"
gibt an, dass ein Nullwert als Eingabe nicht erlaubt ist, auch wenn der entsprechende Typ die Möglichkeit dazu bietet.
Beispiel: Betrachten Sie die folgende Eigenschaft, in der null der Standardwert ist, die Clients aber nur auf einen Wert festlegen können, der nicht null ist.
#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 könnte den Standardwert von
nullzurückgeben, sodass ein Compiler möglicherweise warnt, dass der Wert vor dem Zugriff überprüft werden muss. Außerdem wird der Aufrufer gewarnt, dass er sie nicht explizit auf null festlegen sollte, obwohl sie null sein könnte. Ende des Beispiels
23.5.7.4 Das Attribut DoesNotReturn
Gibt an, dass eine bestimmte Methode niemals zurückkehrt.
Beispiel: Betrachten Sie das Folgende:
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 einem Compiler auf verschiedene Weise. Zunächst kann ein Compiler eine Warnung ausgeben, wenn ein Pfad vorhanden ist, in dem die Methode beendet werden kann, ohne eine Ausnahme zu auslösen. Ausserdem kann ein Compiler nach einem Aufruf dieser Methode in jedem beliebigen Code Nullable-Warnungen unterdrücken, bis eine entsprechende catch-Klausel gefunden wird. Drittens: Der Code für die Nichterreichbarkeit hat keinen Einfluss auf den Status null.
Das Attribut hat keinen Einfluss auf die Analyse der Erreichbarkeit (§13.2) oder der definitiven Zuweisung (§9.4) aufgrund des Vorhandenseins dieses Attributs. Es wird nur verwendet, um Warnungen vor Nullzuständen zu beeinflussen. Ende des Beispiels
23.5.7.5 Das Attribut DoesNotReturnIf
Gibt an, dass eine bestimmte Methode niemals zurückkehrt, wenn der zugehörige bool Parameter den angegebenen Wert hat.
Beispiel: Betrachten Sie das Folgende:
#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!; }Ende des Beispiels
23.5.7.6 Das MaybeNull-Attribut
Legt fest, dass ein nicht-nullbarer 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
Tdurchstringersetzt wird,T?zu einer annullierbaren Annotation wird. Dieser Code ist jedoch nicht zulässig, daTnicht zwingend ein Referenztyp sein muss. Durch das Hinzufügen dieses Attributs wird das Problem jedoch gelöst:#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }Das Attribut informiert den Aufrufer darüber, dass der Vertrag einen nicht-nullbaren Typ impliziert, aber der Rückgabewert kann tatsächlich
nullsein. Ende des Beispiels
23.5.7.7 Das Attribut "MaybeNullWhen"
Gibt an, dass ein nicht-nullbares Argument null sein kann, wenn die Methode den angegebenen bool Wert zurückgibt. Dies ähnelt dem MaybeNull Attribut (§23.5.7.6), enthält jedoch einen Parameter für den angegebenen Rückgabewert.
23.5.7.8 Das Attribut "NotNull"
Gibt an, dass ein nullbarer Wert niemals null sein wird, wenn die Methode zurückkehrt (statt auszulösen).
Beispiel: Betrachten Sie das Folgende:
#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 nullable Verweistypen aktiviert sind, kompiliert die Methode
ThrowWhenNullohne Warnungen. Wenn diese Methode zurückkehrt, ist das Argumentvaluegarantiert nichtnull. Es ist jedoch akzeptabel,ThrowWhenNullmit einer Null-Referenz aufzurufen. Ende des Beispiels
23.5.7.9 Das Attribut "NotNullIfNotNull"
Gibt an, dass ein Rückgabewert nicht null ist, wenn das Argument für den angegebenen Parameter nicht null ist.
Beispiel: Der Status null eines Rückgabewertes könnte vom Status null eines oder mehrerer Argumente abhängen. Um die Analyse eines Compilers zu unterstützen, kann das Attribut
nullverwendet werden, wenn eine Methode immer einen von NULL unterschiedlichen Wert zurückgibt, solange bestimmte Argumente nichtNotNullIfNotNullsind. Sehen Sie sich die folgende Methode an:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }Wenn das Argument
urlnichtnullist, wirdnullnicht zurückgegeben. Wenn nullbare Referenzen aktiviert sind, funktioniert diese Signatur korrekt, sofern die API niemals ein Null-Argument akzeptiert. Wenn das Argument jedoch null sein könnte, dann könnte auch der Rückgabewert null sein. Um diesen Vertrag korrekt auszudrücken, annotieren Sie diese Methode wie folgt:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }Ende des Beispiels
23.5.7.10 Das Attribut "NotNullWhen"
gibt an, dass ein Argument, das nicht gelöscht werden kann, nicht null sein wird, wenn die Methode den angegebenen bool Wert zurückgibt.
Beispiel: Die Bibliotheksmethode
String.IsNullOrEmpty(String)gibttruezurück, wenn das Argumentnulloder eine leere Zeichenfolge ist. Das ist eine Form der Null-Prüfung: Der Aufrufer muss das Argument nicht auf Null prüfen, wenn die Methodefalsezurückgibt. Um eine Methode wie diese nullable aware zu machen, machen Sie den Parametertyp zu einem nullable reference type und fügen das NotNullWhen-Attribut hinzu:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }Ende des Beispiels
23.5.8 Das EnumeratorCancellation-Attribut
Gibt den Parameter an, der für CancellationToken einen asynchronen Iterator (§15.15) steht. Das Argument für diesen Parameter muss mit dem argument kombiniert werden, das an IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken). Dieses kombinierte Token wird von IAsyncEnumerator<T>.MoveNextAsync() (§15.15.5.2) abgefragt. Die Token müssen in ein einzelnes Token kombiniert werden, als ob von CancellationToken.CreateLinkedTokenSource und deren Token Eigenschaft. Das kombinierte Token wird abgebrochen, wenn eines der beiden Quelltoken abgebrochen wird. Das kombinierte Token wird als Argument für die asynchrone Iteratormethode (§15.15) im Textkörper dieser Methode betrachtet.
Es ist ein Fehler, wenn das System.Runtime.CompilerServices.EnumeratorCancellation Attribut auf mehrere Parameter angewendet wird. Der Compiler kann eine Warnung erzeugen, wenn:
- Das
EnumeratorCancellationAttribut wird auf einen Parameter eines anderenCancellationTokenTyps als , - oder wenn das
EnumeratorCancellationAttribut auf einen Parameter für eine Methode angewendet wird, die kein asynchroner Iterator (§15.15) ist, - oder wenn das Attribut auf einen Parameter für eine Methode angewendet wird, die
EnumeratorCancellationeine asynchrone aufzählbare Schnittstelle (§15.15.3) anstelle einer asynchronen Enumerationsschnittstelle (§15.15.2) zurückgibt.
Der Iterator hat keinen Zugriff auf das CancellationToken Argument, wenn GetAsyncEnumerator kein Attribut über diesen Parameter verfügt.
Beispiel: Die Methode
GetStringsAsync()ist ein asynchroner Iterator. Bevor Sie den nächsten Wert abrufen, überprüft er das Abbruchtoken, um zu ermitteln, ob die Iteration abgebrochen werden soll. Wenn eine Kündigung angefordert wird, wird keine weitere Aktion ausgeführt.public static async Task ExampleCombination() { var sourceOne = new CancellationTokenSource(); var sourceTwo = new CancellationTokenSource(); await using (IAsyncEnumerator<string> enumerator = GetStringsAsync(sourceOne.Token).GetAsyncEnumerator(sourceTwo.Token)) { while (await enumerator.MoveNextAsync()) { string number = enumerator.Current; if (number == "8") sourceOne.Cancel(); if (number == "5") sourceTwo.Cancel(); Console.WriteLine(number); } } } static async IAsyncEnumerable<string> GetStringsAsync( [EnumeratorCancellation] CancellationToken token) { for (int i = 0; i < 10; i++) { if (token.IsCancellationRequested) yield break; await Task.Delay(1000, token); yield return i.ToString(); } }Ende des Beispiels
23.6 Attribute für die Interoperabilität
Für die Zusammenarbeit mit anderen Sprachen kann ein Indexer mit indizierten Eigenschaften implementiert werden. Wenn kein IndexerName-Attribut für einen Indexer vorhanden ist, wird standardmäßig der Name Item verwendet. Mit dem Attribut IndexerName kann ein Entwickler diese Vorgabe überschreiben und einen anderen Namen angeben.
Beispiel: Standardmäßig lautet der Name eines Indexers
Item. Dies kann wie folgt überschrieben werden:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }Jetzt lautet der Name des Indexers
TheItem.Ende des Beispiels
ECMA C# draft specification