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
, internal
und 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.Attribute
abgeleitet 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 Attribute
benannt. 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 desSimple
Attributs an.[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}
Obwohl dieses Attribut mit dem Namen
SimpleAttribute
definiert ist, kann dasAttribute
Suffix bei Verwendung dieses Attributs weggelassen werden, was zu dem KurznamenSimple
fü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 AllowMultiple
wird, 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 desAuthor
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 Inherited
wird, 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 Positionsparameterurl
und einen benannten ParameterTopic
enthält. Obwohl sie nicht statisch und öffentlich ist, definiert die EigenschaftUrl
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
, ,sbyte
long
,short
,string
, , ,uint
, .ulong
ushort
- 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 module
die 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 type
typevar
. 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 Methodereturn
— 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 Operatorreturn
— 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
type
enthalten 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 Attribute
benannt. 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.Attribute
Typ 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 VomSystem.Attribute
Typ abgeleiteten Typ führt, ist der Typ das Ergebnis dieses Schritts.
- 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
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
undExampleAttribute
. Das Attribut[Example]
ist mehrdeutig, da es auf entwederExample
oderExampleAttribute
. 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 NamenExampleAttributeAttribute
!vorhanden wäre). Wenn die Deklaration für die KlasseExample
entfernt wird, verweisen beide Attribute wie folgt auf die Attributklasse mit dem NamenExampleAttribute
:[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 vonClass1
.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 InstanzkonstruktorC
, derT
zur Laufzeit aufgerufen werden kann. - Wenn
C
keine öffentliche Barrierefreiheit vorhanden ist, tritt ein Kompilierungszeitfehler auf. - Für jede named_argument
Arg
inN
:- Lassen Sie uns
Name
der Bezeichner des named_argumentArg
sein. Name
muss ein nicht statisches öffentliches Feld oder eine eigenschaft mit Lese-/Schreibzugriff identifizieren.T
WennT
kein solches Feld oder eine solche Eigenschaft vorhanden ist, tritt ein Kompilierungszeitfehler auf.
- Lassen Sie uns
- Wenn eines der Werte innerhalb positional_argument_list
P
oder eines der Werte innerhalb named_argument_listN
vom TypSystem.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 Attributklasse
T
, der InstanzkonstruktorC
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
, P
und , und N
zugeordnet 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 InstanzkonstruktorsC
und der Werte, die zur Kompilierungszeit ermittelt wurden. Diese Schritte führen entweder zu einer Ausnahme oder zu einer InstanzO
vonT
. - Für jede named_argument
Arg
inN
der Reihenfolge:- Lassen Sie uns
Name
der Bezeichner des named_argumentArg
sein. WennName
ein nicht statisches öffentliches Lese-/Schreibfeld oder eine EigenschaftO
nicht identifiziert wird, wird eine Ausnahme ausgelöst. - Lassen Sie uns
Value
das Ergebnis der Auswertung der attribute_argument_expression vonArg
. - Wenn
Name
ein Feld angegebenO
wird, legen Sie dieses Feld aufValue
. - Andernfalls identifiziert Name eine Eigenschaft für
O
. Legen Sie diese Eigenschaft auf Value fest. - Das Ergebnis ist
O
eine Instanz der AttributklasseT
, die mit dem positional_argument_listP
und dem named_argument_listN
initialisiert wurde.
- Lassen Sie uns
Hinweis: Das Format zum Speichern
T
, ,C
,N
P
( und Zuordnen mitE
) inA
und dem Mechanismus zum AngebenE
und AbrufenT
von ,C
, vonP
N
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) undSystem.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.Attribute
abgeleitet 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 SymbolenALPHA
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 demvirtual
Modifizierer markiert werden. Außerkraftsetzungen einer solchen Methode sind implizit bedingt und dürfen nicht explizit mit einemConditional
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.Class2
DieTest
Methode ruft diese Methode auf. Da das SymbolDEBUG
für die bedingte Kompilierung definiert ist, wennClass2.Test
es aufgerufen wird, wird es aufgerufenM
. Wenn das SymbolDEBUG
nicht definiert wurde, würde dasClass2.Test
Symbol nicht aufgerufenClass1.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
undClass3
jede enthalten Aufrufe der bedingten MethodeClass1.F
, die abhängig davon, ob definiert ist oder nichtDEBUG
. Da dieses Symbol im Kontext vonClass2
, aber nichtClass3
definiert ist, wird der AufrufF
Class2
eingeschlossen, während der Aufruf vonF
"InClass3
" weggelassen wird.Endbeispiel
Die Verwendung bedingter Methoden in einer Vererbungskette kann verwirrend sein. Aufrufe an eine bedingte Methode über base
das Formular base.M
unterliegen 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 derM
definierten Basisklasse. Dieser Aufruf wird nicht angegeben, da die Basismethode auf der Grundlage des Vorhandenseins des SymbolsDEBUG
, 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 SymbolenALPHA
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
undClass2
sind jeweils mit AttributenTest
versehen, die abhängig davon sind, ob sie definiert sind oder nichtDEBUG
. Da dieses Symbol im Kontext vonClass1
, aber nichtClass2
definiert ist, wird die Spezifikation des Test-AttributsClass1
eingeschlossen, während die Spezifikation desTest
AttributsClass2
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 false
aufweist. Der Compiler gibt einen Fehler aus, wenn der Fehlerparameter angegeben ist und den Wert true
hat.
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 demObsolete
Attribut versehen ist. Jede Verwendung vonA
inMain
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
, , CallerFilePath
CallerMemberName
. 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 CallerLineNumberAttribute
Auslassungszeichen 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 CallerFilePathAttribute
Parameter 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.CodeAnalysis
deklariert.
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
null
zurü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 durchstring
ersetzt wird,T?
zu einer nullablen Anmerkung wird. Dieser Code ist jedoch nicht zulässig, daT
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 dasvalue
Argument garantiert nichtnull
. Es ist jedoch akzeptabel, einen Nullverweis aufzurufenThrowWhenNull
. 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 null
angegeben 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
dasNotNullIfNotNull
Attribut sind, kann verwendet werden. Sehen Sie sich die folgende Methode an:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
Wenn das
url
Argument nichtnull
angegeben 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ücktrue
, wenn das Argument oder eine leere Zeichenfolge istnull
. Dies ist eine Form der Nullüberprüfung: Aufrufer müssen das Argument nicht null überprüfen, wenn die Methode zurückgegeben wirdfalse
. 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
Item
der Name eines Indexers . Dies kann wie folgt überschrieben werden:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }
Nun lautet
TheItem
der Name des Indexers .Endbeispiel
ECMA C# draft specification