Różne atrybuty interpretowane przez kompilator języka C#

Atrybuty Conditional, , Obsolete, AsyncMethodBuilderAttributeUsageInterpolatedStringHandler, ModuleInitializer, i Experimental można zastosować do elementów w kodzie. Dodają one semantyczne znaczenie do tych elementów. 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.NewMethodmetody 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
    • Param
    • 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 truewartość , 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 truewartość . Oba formaty wyświetlane do stosowania wielu atrybutów są prawidłowe.

Jeśli Inherited ma falsewartość , 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ć specyfikatorafield:, aby dodać atrybut do pola tworzenia kopii zapasowej właściwości automatycznie wdrożonej. 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:

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() lub void SetResult(T result) , która oznacza zadanie jako ukończone i opcjonalnie ustawia wynik zadania

  • Metoda 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:

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ą stackallocmetody . 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>

Zobacz też