Sonstige vom C#-Compiler interpretierte Attribute auf Assemblyebene
Es gibt mehrere Attribute, die auf Elemente in Ihrem Code angewendet werden können und die diesen Elementen eine semantische Bedeutung hinzufügen:
Conditional
: Ausführung einer Methode von einem Vorverarbeitungsbezeichner abhängig machen.Obsolete
: Typ oder Mitglied für (potenzielle) zukünftige Entfernung markieren.AttributeUsage
: Sprachelemente deklarieren, auf die ein Attribut angewendet werden kann.AsyncMethodBuilder
: Asynchronen Methodengeneratortyp deklarieren.InterpolatedStringHandler
: Interpolierten Zeichenfolgengenerator für ein bekanntes Szenario definieren.ModuleInitializer
: Methode deklarieren, die ein Modul initialisiert.SkipLocalsInit
: Den Code übergehen, der den lokalen Variablenspeicher auf 0 initialisiert.UnscopedRef
: Deklarieren, dass eineref
-Variable, die normalerweise alsscoped
interpretiert wird, als Variable ohne Bereich behandelt werden soll.OverloadResolutionPriority
: Fügen Sie ein Tiebreaker-Attribut hinzu, um die Überladungsauflösung bei möglicherweise mehrdeutigen Überladungen zu beeinflussen.Experimental
: Typ oder Mitglied als experimentell kennzeichnen.
Der Compiler verwendet diese semantischen Bedeutungen, um seine Ausgabe zu ändern und mögliche Entwicklerfehler im Code zu melden.
Conditional
-Attribut
Das Conditional
-Attribut macht die Ausführung einer Methode abhängig von einem Vorverarbeitungsbezeichner. Das Conditional
-Attribut ist ein Alias für ConditionalAttribute und kann auf eine Methode oder Attributklasse angewendet werden.
Im folgenden Beispiel wird Conditional
auf eine Methode angewendet, um die Anzeige programmspezifischer Diagnoseinformationen zu aktivieren oder zu deaktivieren:
#define TRACE_ON
using System.Diagnostics;
namespace AttributeExamples;
public class Trace
{
[Conditional("TRACE_ON")]
public static void Msg(string msg)
{
Console.WriteLine(msg);
}
}
public class TraceExample
{
public static void Main()
{
Trace.Msg("Now in Main...");
Console.WriteLine("Done.");
}
}
Wenn der TRACE_ON
-Bezeichner nicht definiert ist, wird die Ausgabe der Ablaufverfolgung nicht angezeigt. Untersuchen Sie den Code im interaktiven Fenster.
Das Conditional
-Attribut wird oft zusammen mit dem DEBUG
-Bezeichner verwendet, um die Ablaufverfolgung und Protokollierung für Debugbuilds – nicht jedoch in Releasebuilds – wie im folgenden Beispiel gezeigt zu aktivieren:
[Conditional("DEBUG")]
static void DebugMethod()
{
}
Wird eine als bedingt gekennzeichnete Methode aufgerufen, bestimmt das Vorhandensein oder Fehlen des angegebenen Vorverarbeitungssymbols, ob Aufrufe der Methode vom Compiler eingeschlossen oder ausgelassen werden. Wenn das Symbol definiert ist, wird der Aufruf einbezogen; andernfalls wird der Aufruf ausgelassen. Eine bedingte Methode muss eine Methode in einer Klassen- oder Strukturdeklaration sein und einen void
-Rückgabetyp aufweisen. Die Verwendung von Conditional
ist eine sauberere, elegantere und auch weniger fehleranfällige Alternative zum Einschließen von Methoden innerhalb von #if…#endif
-Blöcken.
Besitzt eine Methode mehrere Conditional
-Attribute, schließt der Compiler Aufrufe der Methode ein, wenn mindestens eines der bedingten Symbole definiert ist (und die Symbole durch den OR-Operator logisch miteinander verknüpft sind). Im folgenden Beispiel führt das Vorhandensein von A
oder B
zu einem Methodenaufruf:
[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
// ...
}
Verwenden von Conditional
mit Attributklassen
Das Conditional
-Attribut kann auch auf die Definition einer Attributklasse angewendet werden. Im folgenden Beispiel fügt das benutzerdefinierte Attribut Documentation
Informationen zu den Metadaten hinzu, wenn DEBUG
definiert ist.
[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
string text;
public DocumentationAttribute(string text)
{
this.text = text;
}
}
class SampleClass
{
// This attribute will only be included if DEBUG is defined.
[Documentation("This method displays an integer.")]
static void DoWork(int i)
{
System.Console.WriteLine(i.ToString());
}
}
Obsolete
-Attribut
Das Obsolete
-Attribut markiert ein Codeelement als nicht länger zur Verwendung empfohlen. Die Verwendung einer als veraltet markierten Entität führt zu einer Warnung oder einem Fehler. Das Obsolete
-Attribut ist ein Attribut zur einmaligen Nutzung und kann auf jede Entität angewendet werden, die Attribute zulässt. Obsolete
ist ein Alias für ObsoleteAttribute.
Das folgende Beispiel zeigt, wie das Obsolete
-Attribut auf die Klasse A
und die Methode B.OldMethod
angewendet wird. Da das zweite Argument des Attributkonstruktors, das auf B.OldMethod
angewendet wurde, auf true
festgelegt wird, verursacht diese Methode einen Compilerfehler, wobei die Verwendung der A
-Klasse hingegen nur eine Warnung erzeugt. Wenn Sie B.NewMethod
aufrufen, werden weder Warnungen noch Fehler erzeugt. Wenn Sie es mit den vorherigen Definitionen verwenden, generiert der folgende Code zwei Warnungen und einen Fehler:
namespace AttributeExamples
{
[Obsolete("use class B")]
public class A
{
public void Method() { }
}
public class B
{
[Obsolete("use NewMethod", true)]
public void OldMethod() { }
public void NewMethod() { }
}
public static class ObsoleteProgram
{
public static void Main()
{
// Generates 2 warnings:
A a = new A();
// Generate no errors or warnings:
B b = new B();
b.NewMethod();
// Generates an error, compilation fails.
// b.OldMethod();
}
}
}
Die Zeichenfolge, die als erstes Argument für den Attributkonstruktor bereitgestellt wurde, wird als Teil der Warnung oder des Fehlers angezeigt. Es werden zwei Warnungen für die Klasse A
generiert: eine für die Deklaration des Klassenverweises und eine für den Klassenkonstruktor. Das Obsolete
-Attribut kann ohne Argumente verwendet werden. Es wird jedoch empfohlen, in einer Erläuterung anzugeben, was stattdessen verwendet werden soll.
In C# 10 können Sie konstante Zeichenfolgeninterpolation und den nameof
-Operator verwenden, um sicherzustellen, dass die Namen übereinstimmen:
public class B
{
[Obsolete($"use {nameof(NewMethod)} instead", true)]
public void OldMethod() { }
public void NewMethod() { }
}
Experimental
-Attribut
Ab C# 12 können Typen, Methoden und Assemblys mit System.Diagnostics.CodeAnalysis.ExperimentalAttribute gekennzeichnet werden, um eine experimentelle Funktion anzugeben. Der Compiler gibt eine Warnung aus, wenn Sie auf eine Methode zugreifen oder Anmerkungen mit ExperimentalAttribute eingeben. Alle Typen, die in einer Assembly oder einem Modul deklariert werden, die bzw. das mit dem Experimental
-Attribut gekennzeichnet ist, sind experimentell. Der Compiler gibt eine Warnung aus, wenn Sie darauf zugreifen. Sie können diese Warnungen deaktivieren, um eine experimentelle Funktion zu testen.
Warnung
Experimentelle Funktionen unterliegen Änderungen. Die APIs können sich ändern oder in zukünftigen Aktualisierungen entfernt werden. Das Einschließen experimenteller Funktionen ist eine Möglichkeit für Bibliotheksautoren, Feedback zu Ideen und Konzepten für die zukünftige Entwicklung zu erhalten. Seien Sie äußerst vorsichtig, wenn Sie eine als experimentell gekennzeichnete Funktion verwenden.
Weitere Details zum Experimental
-Attribut finden Sie in der Featurespezifikation.
SetsRequiredMembers
-Attribut
Das SetsRequiredMembers
-Attribut informiert den Compiler darüber, dass ein Konstruktor alle required
-Member in dieser Klasse oder Struktur festlegt. Der Compiler geht davon aus, dass jeder Konstruktor mit dem System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute-Attribut alle required
-Member initialisiert. Jeder Code, der einen solchen Konstruktor aufruft, benötigt keine Objektinitialisierer, um die erforderlichen Member festzulegen. Das Hinzufügen des SetsRequiredMembers
-Attributs ist vor allem für Positionsdatensätze und primäre Konstrukteure nützlich.
AttributeUsage
-Attribut
Das AttributeUsage
-Attribut bestimmt, wie eine benutzerdefinierte Attributklasse verwendet werden kann. Bei AttributeUsageAttribute handelt es sich um ein Attribut, das Sie auf benutzerdefinierte Attributdefinitionen anwenden. Mithilfe des AttributeUsage
-Attribut können Sie Folgendes steuern:
- Auf welche Programmelemente das Attribut angewendet werden kann. Sofern Sie die Verwendung nicht einschränken, kann ein Attribut auf jedes der folgenden Programmelemente angewendet werden:
- Assembly
- Modul
- Feld
- Ereignis
- Methode
- Parameter
- Eigenschaft
- Rückgabewert
- type
- Ob ein Attribut mehrfach auf ein einzelnes Programmelement angewendet werden kann.
- Gibt an, ob abgeleitete Klassen Attribute erben.
Die Standardeinstellungen ähneln folgendem Beispiel, wenn Sie explizit angewendet werden:
[AttributeUsage(AttributeTargets.All,
AllowMultiple = false,
Inherited = true)]
class NewAttribute : Attribute { }
In diesem Beispiel kann die NewAttribute
-Klasse auf jedes unterstützte Programmelement angewendet werden. Es kann jedoch nur einmal auf jede Entität angewendet werden. Abgeleitete Klassen erben das Attribut, das auf eine Basisklasse angewendet wird.
Die Argumente AllowMultiple und Inherited sind optional, sodass folgender Code die gleiche Wirkung hat:
[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }
Das erste AttributeUsageAttribute-Argument muss mindestens ein Element der AttributeTargets-Enumeration sein. Mehrere Zieltypen können wie im folgenden Beispiel dargestellt mithilfe des OR-Operators verknüpft werden:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }
Attribute können entweder auf die Eigenschaft oder das Hintergrundfeld für eine automatisch implementierte Eigenschaft angewendet werden. Das Attribut wird auf die Eigenschaft angewendet, sofern Sie den field
-Bezeichner des Attributs nicht angeben. Beides wird im folgenden Beispiel veranschaulicht:
class MyClass
{
// Attribute attached to property:
[NewPropertyOrField]
public string Name { get; set; } = string.Empty;
// Attribute attached to backing field:
[field: NewPropertyOrField]
public string Description { get; set; } = string.Empty;
}
Wenn das AllowMultiple-Argument auf true
festgelegt ist, kann das daraus entstehende Attribut wie im folgenden Beispiel dargestellt mehr als einmal auf eine einzelne Entität angewendet werden:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }
[MultiUse]
[MultiUse]
class Class1 { }
[MultiUse, MultiUse]
class Class2 { }
In diesem Fall kann MultiUseAttribute
wiederholt angewendet werden, da AllowMultiple
auf true
festgelegt wurde. Beide gezeigten Formate für das Anwenden von mehreren Attributen sind gültig.
Wenn Inherited ja false
, erben abgeleitete Klassen das Attribut nicht von einer attributierten Basisklasse. Beispiel:
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }
[NonInherited]
class BClass { }
class DClass : BClass { }
In diesem Fall wird NonInheritedAttribute
nicht durch Vererbung auf DClass
angewendet.
Sie können diese Schlüsselwörter auch verwenden, um anzugeben, wo ein Attribut angewendet werden soll. Sie können z. B. den field:
Bezeichner verwenden, um dem Sicherungsfeld einer automatisch implementierten Eigenschaft ein Attribut hinzuzufügen. Sie können auch die Spezifizier field:
, property:
oder param:
verwenden, um ein Attribut auf eines der Elemente anzuwenden, die aus einem Positionsdatensatz generiert wurden. Ein Beispiel finden Sie unter Positionssyntax für die Eigenschaftendefinition.
AsyncMethodBuilder
-Attribut
Sie fügen das Attribut System.Runtime.CompilerServices.AsyncMethodBuilderAttribute einem Typ hinzu, der ein asynchroner Rückgabetyp sein kann. Das Attribut gibt den Typ an, mit dem die Implementierung der asynchronen Methode erstellt wird, wenn der angegebene Typ von einer asynchronen Methode zurückgegeben wird. Das Attribut AsyncMethodBuilder
kann auf einen Typ angewendet werden, auf den Folgendes zutrifft:
- Er verfügt über eine zugängliche
GetAwaiter
-Methode. - Das von der
GetAwaiter
-Methode zurückgegebene Objekt implementiert die Schnittstelle System.Runtime.CompilerServices.ICriticalNotifyCompletion.
Der Konstruktor des Attributs AsyncMethodBuilder
gibt den Typ des zugeordneten Generators an. Der Generator muss die folgenden zugänglichen Member implementieren:
Eine statische
Create()
-Methode, die den Generatortyp zurückgibt.Eine lesbare
Task
-Eigenschaft, die den asynchronen Rückgabetyp zurückgibt.Eine
void SetException(Exception)
-Methode, die die Ausnahme für einen Fehler bei der Aufgabe festlegt.Eine
void SetResult()
- odervoid SetResult(T result)
-Methode, die die Aufgabe als abgeschlossen kennzeichnet und optional deren Ergebnis festlegt.Eine
Start
-Methode mit der folgenden API-Signatur:void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Eine
AwaitOnCompleted
-Methode mit der folgenden Signatur:public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Eine
AwaitUnsafeOnCompleted
-Methode mit der folgenden Signatur:public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Weitere Informationen zu Generatoren für asynchrone Methoden finden Sie in den Artikeln zu den folgenden von .NET bereitgestellten Generatoren:
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<TResult>
Ab C# 10 kann das AsyncMethodBuilder
-Attribut auf eine asynchrone Methode angewendet werden, um den Generator für diesen Typ zu überschreiben.
InterpolatedStringHandler
- und InterpolatedStringHandlerArguments
-Attribute
Ab C# 10 verwenden Sie diese Attribute, um anzugeben, dass es sich bei einem Typ um einen Handler für interpolierte Zeichenfolgen handelt. Die .NET 6-Bibliothek enthält System.Runtime.CompilerServices.DefaultInterpolatedStringHandler bereits für Szenarios, in denen Sie eine interpolierte Zeichenfolge als Argument für einen string
-Parameter verwenden. Vielleicht möchten Sie auch in anderen Fällen steuern, wie interpolierte Zeichenfolgen verarbeitet werden. Sie wenden System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute auf den Typ an, der den Handler implementiert. Sie wenden System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute auf Parameter des Konstruktors dieses Typs an.
Weitere Informationen zum Erstellen eines Handlers für interpolierte Zeichenfolgen finden Sie in der C# 10-Featurespezifikation für Verbesserungen interpolierter Zeichenfolgen.
ModuleInitializer
-Attribut
Das Attribut ModuleInitializer
kennzeichnet eine Methode, die von der Laufzeit beim Laden der Assembly aufgerufen wird. ModuleInitializer
ist ein Alias für ModuleInitializerAttribute.
Das Attribut ModuleInitializer
kann nur auf eine Methode angewendet werden, die
- statisch ist.
- parameterlos ist.
- Gibt
void
zurück. - für das enthaltende Modul (
internal
oderpublic
) zugänglich ist. - keine generische Methode ist.
- in keiner generischen Klasse enthalten ist.
- keine lokale Funktion ist.
Das Attribut ModuleInitializer
kann auf mehrere Methoden angewendet werden. In diesem Fall ruft die Laufzeit die Methoden in einer deterministischen, jedoch nicht angegebenen Reihenfolge auf.
Das folgende Beispiel veranschaulicht, wie mehrere Modulinitialisierermethoden verwendet werden. Die Methoden Init1
und Init2
werden vor Main
ausgeführt und fügen der Text
-Eigenschaft jeweils eine Zeichenfolge hinzu. Bei der Ausführung von Main
verfügt die Text
-Eigenschaft also bereits über Zeichenfolgen von beiden Initialisierermethoden.
using System;
internal class ModuleInitializerExampleMain
{
public static void Main()
{
Console.WriteLine(ModuleInitializerExampleModule.Text);
//output: Hello from Init1! Hello from Init2!
}
}
using System.Runtime.CompilerServices;
internal class ModuleInitializerExampleModule
{
public static string? Text { get; set; }
[ModuleInitializer]
public static void Init1()
{
Text += "Hello from Init1! ";
}
[ModuleInitializer]
public static void Init2()
{
Text += "Hello from Init2! ";
}
}
Quellcode-Generatoren müssen gelegentlich Initialisierungscode generieren. Modulinitialisierer stellen für diesen Code einen Standardspeicherort bereit. In den meisten übrigen Fällen sollten Sie einen statischen Konstruktor anstelle eines Modulinitialisierers schreiben.
SkipLocalsInit
-Attribut
Das SkipLocalsInit
-Attribut verhindert, dass der Compiler bei der Ausgabe an Metadaten das Flag .locals init
festlegt. SkipLocalsInit
ist ein einwertiges Attribut, das auf eine Methode, eine Eigenschaft, eine Klasse, eine Struktur, eine Schnittstelle oder ein Modul angewendet werden kann, jedoch nicht auf eine Assembly. SkipLocalsInit
ist ein Alias für SkipLocalsInitAttribute.
Das Flag .locals init
bewirkt, dass die CLR alle in einer Methode deklarierten lokalen Variablen mit ihrem Standardwert initialisiert. Da auch der Compiler dafür sorgt, dass eine Variable erst nach dem Zuweisen eines Werts verwendet werden kann, ist das .locals init
-Attribut in der Regel nicht erforderlich. Allerdings kann die zusätzliche Null-Initialisierung in einigen Szenarien messbare Auswirkungen auf die Leistung haben, z. B. wenn Sie stackalloc verwenden, um ein Array auf dem Stack zuzuweisen. In diesen Fällen können Sie das Attribut SkipLocalsInit
hinzufügen. Wird das Attribut direkt auf eine Methode angewendet, wirkt es sich auf die Methode und alle darin geschachtelten Funktionen, wie etwa Lambda- und lokale Funktionen, aus. Wird es auf einen Typ oder ein Modul angewendet, werden alle darin geschachtelten Methoden beeinflusst. Das Attribut hat keine Auswirkungen auf abstrakte Methoden, jedoch auf Code, der für die Implementierung generiert wurde.
Dieses Attribut erfordert die Compileroption AllowUnsafeBlocks. Diese Anforderung signalisiert, dass in einigen Fällen nicht zugewiesener Arbeitsspeicher vom Code angezeigt werden kann (z. B. durch das Lesen aus dem nicht initialisierten, im Stapel zugeordneten Arbeitsspeicher).
Das folgende Beispiel veranschaulicht, wie sich das SkipLocalsInit
-Attribut auf eine Methode auswirkt, die stackalloc
verwendet. Mit der Methode wird der gesamte Inhalt des Arbeitsspeichers zum Zeitpunkt angezeigt, als ein Array aus ganzen Zahlen zugeordnet wurde.
[SkipLocalsInit]
static void ReadUninitializedMemory()
{
Span<int> numbers = stackalloc int[120];
for (int i = 0; i < 120; i++)
{
Console.WriteLine(numbers[i]);
}
}
// output depends on initial contents of memory, for example:
//0
//0
//0
//168
//0
//-1271631451
//32767
//38
//0
//0
//0
//38
// Remaining rows omitted for brevity.
Wenn Sie diesen Code ausprobieren möchten, legen Sie in Ihrer CSPROJ-Datei die Compileroption AllowUnsafeBlocks
fest:
<PropertyGroup>
...
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
UnscopedRef
-Attribut
Das Attribut UnscopedRef
kennzeichnet eine Variablendeklaration als ohne Bereich, d. h. der Verweis darf übergangen werden.
Sie fügen dieses Attribut hinzu, wenn der Compiler ref
implizit als scoped
behandelt:
- Der
this
-Parameter fürstruct
-Instanzmethoden. ref
-Parameter, die aufref struct
-Typen verweisen.out
-Parameter.
Durch Anwenden von System.Diagnostics.CodeAnalysis.UnscopedRefAttribute wird das Element als ohne Bereich markiert.
OverloadResolutionPriority
-Attribut
Mit OverloadResolutionPriorityAttribute können Bibliotheksautoren eine Überladung gegenüber einer anderen bevorzugen, wenn zwei Überladungen möglicherweise mehrdeutig sind. Der primäre Anwendungsfall richtet sich an Bibliotheksautoren, damit diese leistungsstärkere Überladungen schreiben und gleichzeitig vorhandenen Code ohne Unterbrechungen unterstützen können.
Sie können z. B. eine neue Überladung hinzufügen, bei der ReadOnlySpan<T> zum Reduzieren der Arbeitsspeicherbelegung verwendet wird:
[OverloadResolutionPriority(1)]
public void M(params ReadOnlySpan<int> s) => Console.WriteLine("Span");
// Default overload resolution priority of 0
public void M(params int[] a) => Console.WriteLine("Array");
Die Überladungsauflösung berücksichtigt bei einigen Argumenttypen beide Methoden gleichermaßen. Bei einem Argument von int[]
bevorzugt sie die erste Überladung. Damit der Compiler die Version ReadOnlySpan
vorzieht, können Sie die Priorität dieser Überladung erhöhen. Das folgende Beispiel zeigt, wie sich das Hinzufügen des Attributs auswirkt:
var d = new OverloadExample();
int[] arr = [1, 2, 3];
d.M(1, 2, 3, 4); // Prints "Span"
d.M(arr); // Prints "Span" when PriorityAttribute is applied
d.M([1, 2, 3, 4]); // Prints "Span"
d.M(1, 2, 3, 4); // Prints "Span"
Alle Überladungen mit einer niedrigeren Priorität als die höchste Überladungspriorität werden aus der Gruppe anwendbarer Methoden entfernt. Bei Methoden ohne dieses Attribut ist die Überladungspriorität auf den Standardwert 0 festgelegt. Bibliotheksautoren sollten dieses Attribut beim Hinzufügen einer neuen und besseren Methodenüberladung als letzte Möglichkeit verwenden. Bibliotheksautoren benötigen ein umfassendes Verständnis dafür, wie sich die Überladungsauflösung auf die Auswahl der besseren Methode auswirkt. Andernfalls können unerwartete Fehler auftreten.