Różne atrybuty interpretowane przez kompilator języka C#
Istnieje kilka atrybutów, które można zastosować do elementów w kodzie, które dodają do tych elementów znaczenie semantyczne:
Conditional
: wykonaj metodę zależną od identyfikatora preprocesora.Obsolete
: Oznacz typ lub element członkowski w celu (potencjalnego) przyszłego usunięcia.AttributeUsage
: zadeklaruj elementy języka, w których można zastosować atrybut.AsyncMethodBuilder
: Zadeklaruj typ konstruktora metody asynchronicznej.InterpolatedStringHandler
: Zdefiniuj konstruktor ciągów interpolowanych dla znanego scenariusza.ModuleInitializer
: zadeklaruj metodę, która inicjuje moduł.SkipLocalsInit
: Elide kodu, który inicjuje magazyn zmiennych lokalnych do 0.UnscopedRef
: Zadeklaruj, że zmiennaref
zwykle interpretowana jakoscoped
powinna być traktowana jako niezakresowa.OverloadResolutionPriority
: Dodaj atrybut tiebreaker, aby wpłynąć na rozpoznawanie przeciążeń w przypadku prawdopodobnie niejednoznacznych przeciążeń.Experimental
: oznacz typ lub element członkowski jako eksperymentalny.
Kompilator używa tych semantycznych znaczeń w celu zmiany danych wyjściowych i zgłaszania możliwych błędów przez deweloperów przy użyciu kodu.
Atrybut Conditional
Atrybut Conditional
sprawia, że wykonanie metody jest zależne od identyfikatora przetwarzania wstępnego. Atrybut Conditional
jest aliasem dla ConditionalAttributeelementu i można go zastosować do metody lub klasy atrybutów.
W poniższym przykładzie Conditional
zastosowano metodę w celu włączenia lub wyłączenia wyświetlania informacji diagnostycznych specyficznych dla programu:
#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.");
}
}
TRACE_ON
Jeśli identyfikator nie jest zdefiniowany, dane wyjściowe śledzenia nie są wyświetlane. Zapoznaj się ze sobą w oknie interaktywnym.
Atrybut Conditional
jest często używany z identyfikatorem DEBUG
w celu włączenia funkcji śledzenia i rejestrowania kompilacji debugowania, ale nie w kompilacjach wydania, jak pokazano w poniższym przykładzie:
[Conditional("DEBUG")]
static void DebugMethod()
{
}
Gdy wywoływana jest metoda oznaczona jako warunkowa, obecność lub brak określonego symbolu przetwarzania wstępnego określa, czy kompilator zawiera lub pomija wywołania metody. Jeśli symbol jest zdefiniowany, zostanie dołączone wywołanie; w przeciwnym razie wywołanie zostanie pominięte. Metoda warunkowa musi być metodą w deklaracji klasy lub struktury i musi mieć typ zwracany void
. Użycie Conditional
jest czystsze, bardziej eleganckie i mniej podatne na błędy niż otaczające metody wewnątrz #if…#endif
bloków.
Jeśli metoda ma wiele Conditional
atrybutów, kompilator zawiera wywołania metody, jeśli zdefiniowano co najmniej jeden symbol warunkowy (symbole są logicznie połączone za pomocą operatora OR). W poniższym przykładzie obecność A
metody lub B
powoduje wywołanie metody:
[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
// ...
}
Używanie z Conditional
klasami atrybutów
Atrybut Conditional
można również zastosować do definicji klasy atrybutu. W poniższym przykładzie atrybut Documentation
niestandardowy dodaje informacje do metadanych, jeśli DEBUG
jest zdefiniowany.
[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());
}
}
Atrybut Obsolete
Atrybut Obsolete
oznacza element kodu, który nie jest już zalecany do użycia. Użycie jednostki oznaczonej jako przestarzałe generuje ostrzeżenie lub błąd. Atrybut Obsolete
jest atrybutem pojedynczego użycia i może być stosowany do dowolnej jednostki, która zezwala na atrybuty. Obsolete
to alias dla elementu ObsoleteAttribute.
W poniższym przykładzie Obsolete
atrybut jest stosowany do klasy A
i metody B.OldMethod
. Ponieważ drugi argument konstruktora atrybutu zastosowanego do B.OldMethod
jest ustawiony na true
, ta metoda powoduje błąd kompilatora, podczas gdy użycie klasy A
generuje ostrzeżenie. Wywołanie B.NewMethod
metody nie powoduje jednak ostrzeżenia ani błędu. Jeśli na przykład używasz go z poprzednimi definicjami, następujący kod generuje dwa ostrzeżenia i jeden błąd:
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();
}
}
}
Ciąg podany jako pierwszy argument konstruktora atrybutu jest wyświetlany jako część ostrzeżenia lub błędu. Generowane są dwa ostrzeżenia dla klasy A
: jeden dla deklaracji odwołania do klasy i jeden dla konstruktora klasy. Atrybut Obsolete
może być używany bez argumentów, ale w tym wyjaśnienie, co zamiast tego należy użyć, jest zalecane.
W języku C# 10 można użyć interpolacji ciągów stałych i nameof
operatora, aby upewnić się, że nazwy są zgodne:
public class B
{
[Obsolete($"use {nameof(NewMethod)} instead", true)]
public void OldMethod() { }
public void NewMethod() { }
}
Atrybut Experimental
Począwszy od języka C# 12, typy, metody i zestawy można oznaczyć za System.Diagnostics.CodeAnalysis.ExperimentalAttribute pomocą elementu , aby wskazać funkcję eksperymentalną. Kompilator wyświetla ostrzeżenie, jeśli uzyskujesz dostęp do metody lub wpisz adnotację za pomocą elementu ExperimentalAttribute. Wszystkie typy zadeklarowane w zestawie lub module oznaczonym atrybutem Experimental
są eksperymentalne. Kompilator wyświetla ostrzeżenie, jeśli uzyskujesz dostęp do któregokolwiek z nich. Możesz wyłączyć te ostrzeżenia, aby pilotować funkcję eksperymentalną.
Ostrzeżenie
Funkcje eksperymentalne podlegają zmianom. Interfejsy API mogą ulec zmianie lub mogą zostać usunięte w przyszłych aktualizacjach. Uwzględnienie funkcji eksperymentalnych to sposób na uzyskanie opinii autorów bibliotek na temat pomysłów i pojęć związanych z przyszłym opracowywaniem. Należy zachować szczególną ostrożność w przypadku używania dowolnej funkcji oznaczonej jako eksperymentalna.
Więcej informacji na temat atrybutu Experimental
można znaleźć w specyfikacji funkcji.
Atrybut SetsRequiredMembers
Atrybut SetsRequiredMembers
informuje kompilator, że konstruktor ustawia wszystkie required
elementy członkowskie w tej klasie lub strukturę. Kompilator zakłada, że każdy konstruktor z atrybutem inicjuje System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute wszystkie required
elementy członkowskie. Każdy kod, który wywołuje taki konstruktor, nie potrzebuje inicjatorów obiektów, aby ustawić wymagane elementy członkowskie. Dodanie atrybutu SetsRequiredMembers
jest przydatne przede wszystkim w przypadku rekordów pozycyjnych i konstruktorów podstawowych.
Atrybut AttributeUsage
Atrybut AttributeUsage
określa, jak można użyć niestandardowej klasy atrybutów. AttributeUsageAttribute to atrybut stosowany do definicji atrybutów niestandardowych. Atrybut AttributeUsage
umożliwia sterowanie:
- Do których elementów programu można zastosować atrybut. Jeśli nie ograniczysz jego użycia, atrybut można zastosować do dowolnego z następujących elementów programu:
- Zestaw
- Moduł
- Pole
- Zdarzenie
- Method
- Parametr
- Właściwości
- Powrót
- Typ
- Czy atrybut można zastosować do pojedynczego elementu programu wiele razy.
- Czy klasy pochodne dziedziczą atrybuty.
Ustawienia domyślne wyglądają jak w poniższym przykładzie po zastosowaniu jawnie:
[AttributeUsage(AttributeTargets.All,
AllowMultiple = false,
Inherited = true)]
class NewAttribute : Attribute { }
W tym przykładzie klasę NewAttribute
można zastosować do dowolnego obsługiwanego elementu programu. Można go jednak zastosować tylko raz do każdej jednostki. Klasy pochodne dziedziczą atrybut zastosowany do klasy bazowej.
Argumenty AllowMultiple i Inherited są opcjonalne, więc następujący kod ma taki sam efekt:
[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }
Pierwszy AttributeUsageAttribute argument musi być co najmniej jednym elementem AttributeTargets wyliczenia. Wiele typów docelowych można połączyć razem z operatorem OR, jak pokazano w poniższym przykładzie:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }
Atrybuty można zastosować do właściwości lub pola zapasowego dla właściwości wdrożonej automatycznie. Atrybut ma zastosowanie do właściwości, chyba że określisz field
specyfikator atrybutu. Oba są wyświetlane w poniższym przykładzie:
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;
}
AllowMultiple Jeśli argument ma true
wartość , wynikowy atrybut można zastosować więcej niż raz do pojedynczej jednostki, jak pokazano w poniższym przykładzie:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }
[MultiUse]
[MultiUse]
class Class1 { }
[MultiUse, MultiUse]
class Class2 { }
W takim przypadku można wielokrotnie stosować metodę , MultiUseAttribute
ponieważ AllowMultiple
jest ustawiona na true
wartość . Oba formaty wyświetlane do stosowania wielu atrybutów są prawidłowe.
Jeśli Inherited ma false
wartość , klasy pochodne nie dziedziczą atrybutu z przypisanej klasy bazowej. Na przykład:
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }
[NonInherited]
class BClass { }
class DClass : BClass { }
W takim przypadku NonInheritedAttribute
nie jest stosowany do DClass
metody dziedziczenia.
Możesz również użyć tych słów kluczowych, aby określić, gdzie ma zostać zastosowany atrybut. Na przykład można użyć specyfikatora field:
, aby dodać atrybut do pola zapasowego automatycznie zaimplementowanej właściwości. Możesz też użyć specyfikatora field:
property:
lub param:
, aby zastosować atrybut do dowolnego elementu wygenerowanego na podstawie rekordu pozycyjnego. Aby zapoznać się z przykładem, zobacz Składnia pozycyjna definicji właściwości.
Atrybut AsyncMethodBuilder
Atrybut można System.Runtime.CompilerServices.AsyncMethodBuilderAttribute dodać do typu, który może być typem zwrotnym asynchronicznego. Atrybut określa typ, który kompiluje implementację metody asynchronicznej, gdy określony typ jest zwracany z metody asynchronicznej. Atrybut AsyncMethodBuilder
można zastosować do typu, który:
- Ma dostępną
GetAwaiter
metodę. - Obiekt zwracany przez metodę
GetAwaiter
System.Runtime.CompilerServices.ICriticalNotifyCompletion implementuje interfejs.
Konstruktor atrybutu AsyncMethodBuilder
określa typ skojarzonego konstruktora. Konstruktor musi zaimplementować następujące dostępne elementy członkowskie:
Metoda statyczna
Create()
zwracająca typ konstruktora.Właściwość czytelna
Task
zwracająca typ zwracany asynchroniczny.void SetException(Exception)
Metoda, która ustawia wyjątek w przypadku błędów zadania.Metoda
void SetResult()
lubvoid SetResult(T result)
, która oznacza zadanie jako ukończone i opcjonalnie ustawia wynik zadaniaMetoda
Start
z następującym podpisem interfejsu API:void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Metoda
AwaitOnCompleted
z następującym podpisem:public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Metoda
AwaitUnsafeOnCompleted
z następującym podpisem:public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Aby dowiedzieć się więcej na temat konstruktorów metod asynchronicznych, zapoznaj się z następującymi konstruktorami dostarczonymi przez platformę .NET:
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<TResult>
W języku C# 10 lub nowszym AsyncMethodBuilder
atrybut można zastosować do metody asynchronicznej, aby zastąpić konstruktora tego typu.
InterpolatedStringHandler
i InterpolatedStringHandlerArguments
atrybuty
Począwszy od języka C# 10, należy użyć tych atrybutów, aby określić, że typ jest interpolowanym programem obsługi ciągów. Biblioteka platformy .NET 6 zawiera System.Runtime.CompilerServices.DefaultInterpolatedStringHandler już scenariusze, w których jako argument string
parametru jest używany ciąg interpolowany. Mogą istnieć inne wystąpienia, w których chcesz kontrolować sposób przetwarzania ciągów interpolowanych. Zastosuj element System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute do typu, który implementuje procedurę obsługi. Należy zastosować System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute element do parametrów konstruktora tego typu.
Więcej informacji na temat tworzenia procedury obsługi ciągów interpolowanych można znaleźć w specyfikacji funkcji języka C# 10 na potrzeby ulepszeń ciągów interpolowanych.
Atrybut ModuleInitializer
Atrybut ModuleInitializer
oznacza metodę wywoływaną przez środowisko uruchomieniowe podczas ładowania zestawu. ModuleInitializer
to alias dla elementu ModuleInitializerAttribute.
Atrybut ModuleInitializer
można zastosować tylko do metody, która:
- Jest statyczny.
- Jest bez parametrów.
- Zwraca wartość
void
. - Jest dostępny z modułu zawierającego , czyli
internal
.public
- Nie jest metodą ogólną.
- Nie jest zawarta w klasie ogólnej.
- Nie jest funkcją lokalną.
Atrybut ModuleInitializer
można zastosować do wielu metod. W takim przypadku kolejność wywoływana przez środowisko uruchomieniowe jest deterministyczna, ale nie jest określona.
Poniższy przykład ilustruje użycie wielu metod inicjatora modułu. Metody Init1
i Init2
są uruchamiane przed elementem Main
, a każda z nich dodaje ciąg do Text
właściwości . Dlatego po Main
uruchomieniu Text
właściwość zawiera już ciągi z obu metod inicjatora.
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! ";
}
}
Generator kodu źródłowego czasami musi generować kod inicjowania. Inicjatory modułów zapewniają standardowe miejsce dla tego kodu. W większości innych przypadków należy napisać konstruktor statyczny zamiast inicjatora modułu.
Atrybut SkipLocalsInit
Atrybut SkipLocalsInit
uniemożliwia kompilatorowi ustawienie flagi .locals init
podczas emitowania do metadanych. Atrybut SkipLocalsInit
jest atrybutem pojedynczego użycia i może być stosowany do metody, właściwości, klasy, struktury, interfejsu lub modułu, ale nie do zestawu. SkipLocalsInit
to alias dla elementu SkipLocalsInitAttribute.
Flaga .locals init
powoduje, że CLR inicjuje wszystkie zmienne lokalne zadeklarowane w metodzie do ich wartości domyślnych. Ponieważ kompilator upewnia się również, że nigdy nie używasz zmiennej przed przypisaniem do niej pewnej wartości, .locals init
zwykle nie jest to konieczne. Jednak dodatkowa inicjalizacja zerowa może mieć wymierny wpływ na wydajność w niektórych scenariuszach, takich jak użycie obiektu stackalloc do przydzielenia tablicy na stosie. W takich przypadkach można dodać SkipLocalsInit
atrybut . Jeśli zastosowano metodę bezpośrednio, atrybut ma wpływ na tę metodę i wszystkie jej zagnieżdżone funkcje, w tym lambdy i funkcje lokalne. W przypadku zastosowania do typu lub modułu ma wpływ na wszystkie metody zagnieżdżone wewnątrz. Ten atrybut nie ma wpływu na metody abstrakcyjne, ale ma wpływ na kod wygenerowany na potrzeby implementacji.
Ten atrybut wymaga opcji kompilatora AllowUnsafeBlocks . To wymaganie sygnalizuje, że w niektórych przypadkach kod może wyświetlać nieprzypisaną pamięć (na przykład odczytywanie z niezainicjowanej pamięci przydzielonej do stosu).
Poniższy przykład ilustruje wpływ atrybutu SkipLocalsInit
na metodę używającą stackalloc
metody . Metoda wyświetla niezależnie od tego, co było w pamięci, gdy tablica liczb całkowitych została przydzielona.
[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.
Aby samodzielnie wypróbować ten kod, ustaw opcję kompilatora AllowUnsafeBlocks
w pliku csproj :
<PropertyGroup>
...
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
Atrybut UnscopedRef
Atrybut UnscopedRef
oznacza deklarację zmiennej jako niezakresową, co oznacza, że odwołanie może uciec.
Dodasz ten atrybut, w którym kompilator traktuje ref
element niejawnie scoped
:
- Parametr
this
dlastruct
metod wystąpień. ref
parametry odwołujące się doref struct
typów.out
Parametry.
Stosowanie oznaczeń System.Diagnostics.CodeAnalysis.UnscopedRefAttribute elementu jako niezakresowego.
Atrybut OverloadResolutionPriority
Dzięki OverloadResolutionPriorityAttribute temu autorzy bibliotek preferują jedno przeciążenie przez inne, gdy dwa przeciążenia mogą być niejednoznaczne. Jego podstawowym przypadkiem użycia jest napisanie przeciążeń przez autorów bibliotek, a jednocześnie obsługę istniejącego kodu bez przerw.
Można na przykład dodać nowe przeciążenie, które używa ReadOnlySpan<T> do zmniejszenia alokacji pamięci:
[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");
Rozpoznawanie przeciążenia uwzględnia dwie metody równie dobre dla niektórych typów argumentów. W przypadku argumentu parametru int[]
preferuje pierwsze przeciążenie. Aby kompilator preferować ReadOnlySpan
wersję, możesz zwiększyć priorytet tego przeciążenia. W poniższym przykładzie pokazano efekt dodawania atrybutu:
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"
Wszystkie przeciążenia o niższym priorytecie niż najwyższy priorytet przeciążenia są usuwane z zestawu odpowiednich metod. Metody bez tego atrybutu mają priorytet przeciążenia ustawiony na wartość domyślną zero. Autorzy bibliotek powinni używać tego atrybutu jako ostateczności podczas dodawania nowego i lepszego przeciążenia metody. Autorzy bibliotek powinni mieć głęboką wiedzę na temat wpływu rozpoznawania przeciążeń na wybór lepszej metody. W przeciwnym razie mogą wynikać nieoczekiwane błędy.