Definieren und Lesen von benutzerdefinierten Attributen

Attribute bieten die Möglichkeit, Informationen in deklarativer Form mit Code zu verknüpfen. Attribute können außerdem als wiederverwendbare Elemente genutzt werden, die auf verschiedenste Ziele angewendet werden können. Sehen Sie sich das ObsoleteAttribute an. Es kann auf Klassen, Strukturen, Methoden, Konstruktoren und mehr angewendet werden. Mit ihm wird das Element als veraltet deklariert. Anschließend ist es die Aufgabe des C#-Compilers, nach diesem Attribut zu suchen und Aktionen auszuführen.

In diesem Tutorial erfahren Sie, wie Sie Attribute zu Ihrem Code hinzufügen sowie eigene Attribute erstellen und verwenden, und Sie lernen einige der Attribute kennen, die in .NET Core integriert sind.

Voraussetzungen

Sie müssen Ihren Computer zur Ausführung von .NET einrichten. Die Installationsanweisungen finden Sie auf der Seite mit .NET-Downloads. Sie können diese Anwendung unter Windows, Ubuntu Linux, macOS oder in einem Docker-Container ausführen. Sie müssen Ihren bevorzugten Code-Editor installieren. In den folgenden Beschreibungen wird Visual Studio Code verwendet. Hierbei handelt es sich um einen plattformübergreifenden Open-Source-Editor. Sie können jedoch auch ein beliebiges anderes Tool verwenden, mit dem Sie vertraut sind.

Erstellen der App

Nachdem Sie alle Tools installiert haben, erstellen Sie eine neue .NET-Konsolen-App. Um den Befehlszeilengenerator zu verwenden, führen Sie den folgenden Befehl in Ihrer bevorzugten Shell aus:

dotnet new console

Mit diesem Befehl werden .NET-Basisprojektdateien erstellt. Sie müssen dotnet restore ausführen, um die Abhängigkeiten wiederherzustellen, die zum Kompilieren dieses Projekts erforderlich sind.

Sie müssen dotnet restore nicht ausführen, da der Befehl implizit von allen Befehlen ausgeführt wird, die eine Wiederherstellung erfordern. Zu diesen zählen z. B. dotnet new, dotnet build, dotnet run, dotnet test, dotnet publish und dotnet pack. Verwenden Sie die Option --no-restore, um die implizite Wiederherstellung zu deaktivieren.

In bestimmten Fällen eignet sich der dotnet restore-Befehl dennoch. Dies ist etwa bei Szenarios der Fall, in denen die explizite Wiederherstellung sinnvoll ist. Beispiele hierfür sind Continuous Integration-Builds in Azure DevOps Services oder Buildsysteme, die den Zeitpunkt für die Wiederherstellung explizit steuern müssen.

Informationen zum Verwalten von NuGet-Feeds finden Sie in der dotnet restoreDokumentation.

Verwenden Sie zum Ausführen des Programms dotnet run. Es sollte „Hello, World“ auf der Konsole ausgegeben werden.

Hinzufügen von Attributen zum Code

In C# sind Attribute Klassen, die von der Attribute-Basisklasse erben. Alle Klassen, die von Attribute erben, können als eine Art von „Tag“ für andere Codeelemente verwendet werden. Beispielsweise gibt es das Attribut ObsoleteAttribute. Mit diesem Attribut wird gekennzeichnet, dass der Code veraltet ist und nicht mehr verwendet werden sollte. Sie können eine Klasse beispielsweise mit diesem Attribut markieren, indem Sie eckige Klammern verwenden.

[Obsolete]
public class MyClass
{
}

Obwohl der Name der Klasse ObsoleteAttribute lautet, kann im Code nur [Obsolete] verwendet werden. Der meiste C#-Code folgt dieser Konvention. Wenn Sie möchten, können Sie auch den vollständigen Namen, [ObsoleteAttribute], verwenden.

Wenn Sie eine Klasse als veraltet markieren, sollten Sie Informationen dazu geben, warum die Klasse veraltet ist und/oder was stattdessen verwendet werden sollte. Sie fügen einen Zeichenfolgenparameter in das Obsolete-Attribut ein, um diese Erklärung anzugeben.

[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]
public class ThisClass
{
}

Die Zeichenfolge wird als Argument an einen ObsoleteAttribute-Konstruktor übergeben, so als ob Sie var attr = new ObsoleteAttribute("some string") schreiben würden.

Die Parameter für einen Attributkonstruktor sind auf einfache Typen/Literale beschränkt: bool, int, double, string, Type, enums, etc und Arrays dieser Typen. Sie können keine Ausdrücke oder Variablen verwenden. Es ist möglich, Positionsparameter oder benannte Parameter einzusetzen.

Erstellen eigener Attribute

Sie erstellen ein Attribut, indem Sie eine neue Klasse definieren, die von der Attribute-Basisklasse erbt.

public class MySpecialAttribute : Attribute
{
}

Durch den Code oben kann [MySpecial] (oder [MySpecialAttribute]) als Attribut an anderer Stelle in der Codebasis verwendet werden.

[MySpecial]
public class SomeOtherClass
{
}

Attribute in der .NET-Basisklassenbibliothek, z.B. ObsoleteAttribute, lösen ein bestimmtes Verhalten im Compiler aus. Die erstellten Attribute dienen jedoch nur als Metadaten, sie führen nicht zu Code innerhalb der Attributklasse, der ausgeführt wird. Es liegt an Ihnen, diese Metadaten an anderer Stelle im Code einzusetzen.

Es gibt hierbei ein Problem, das beachtet werden muss. Wie zuvor erwähnt, können bei der Verwendung von Attributen nur bestimmte Typen als Argumente übergeben werden. Wenn Sie aber einen Attributtyp erstellen, hindert der C#-Compiler Sie nicht daran, diese Parameter zu erstellen. Im nachstehenden Beispiel haben Sie ein Attribut mit einem Konstruktor erstellt, der problemlos kompiliert werden kann.

public class GotchaAttribute : Attribute
{
    public GotchaAttribute(Foo myClass, string str)
    {
    }
}

Dieser Konstruktor kann aber nicht mit der Attributsyntax verwendet werden.

[Gotcha(new Foo(), "test")] // does not compile
public class AttributeFail
{
}

Der vorangehende Code verursacht einen Compilerfehler, z. B. Attribute constructor parameter 'myClass' has type 'Foo', which is not a valid attribute parameter type.

Einschränken der Attributverwendung

Attribute können für die folgenden „Ziele“ verwendet werden. In den obigen Beispielen wurde die Verwendung für Klassen gezeigt, aber Attribute können auch für folgende Elemente verwendet werden:

  • Assembly
  • Klasse
  • Konstruktor
  • Delegat
  • Enumeration
  • Ereignis
  • Feld
  • GenericParameter
  • Schnittstelle
  • Methode
  • Modul
  • Parameter
  • Eigenschaft
  • ReturnValue
  • Struktur

Wenn Sie eine Attributklasse erstellen, lässt C# standardmäßig die Verwendung dieses Attributs für alle möglichen Attributziele zu. Wenn Sie Ihr Attribut auf bestimmte Ziele beschränken möchten, erreichen Sie dies durch Verwendung von AttributeUsageAttribute für Ihre Attributklasse. Ganz richtig, ein Attribut für ein Attribut!

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
{
}

Wenn Sie versuchen, das oben gezeigte Attribut für ein Element zu verwenden, bei dem es sich nicht um eine Klasse oder eine Struktur handelt, erhalten Sie einen Compilerfehler wie Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type. It is only valid on 'class, struct' declarations

public class Foo
{
    // if the below attribute was uncommented, it would cause a compiler error
    // [MyAttributeForClassAndStructOnly]
    public Foo()
    { }
}

Verwenden von Attributen, die an Codeelemente angefügt sind

Attribute fungieren als Metadaten. Ohne Aktivität von außen bewirken sie zunächst nichts.

Um nach Attributen zu suchen und Aktionen auszuführen, ist im Allgemeinen eine Reflexion erforderlich. Mit einer Reflexion können Sie Code in C# schreiben, der anderen Code untersucht. Sie können beispielsweise mithilfe der Reflektion Informationen zu einer Klasse abrufen (fügen Sie using System.Reflection; im Kopf Ihres Codes hinzu):

TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();
Console.WriteLine("The assembly qualified name of MyClass is " + typeInfo.AssemblyQualifiedName);

Damit wird etwa Folgendes ausgegeben: The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Sobald Sie über ein TypeInfo-Objekt (oder ein MemberInfo-, FieldInfo- oder anderes Objekt) verfügen, können Sie die GetCustomAttributes-Methode verwenden. Diese Methode gibt eine Sammlung von Attribute-Objekten zurück. Sie können auch GetCustomAttribute verwenden und einen Attributtyp angeben.

Hier ein Beispiel zur Verwendung von GetCustomAttributes für eine MemberInfo-Instanz für MyClass (die, wie zuvor gezeigt, über ein [Obsolete]-Attribut verfügt).

var attrs = typeInfo.GetCustomAttributes();
foreach(var attr in attrs)
    Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);

Dies wird in der Konsole ausgegeben: Attribute on MyClass: ObsoleteAttribute. Versuchen Sie, weitere Attribute zu MyClass hinzuzufügen.

Es ist wichtig, darauf hinzuweisen, dass diese Attribute-Objekte verzögert instanziiert werden. Anders ausgedrückt: Sie werden erst instanziiert, wenn Sie GetCustomAttribute oder GetCustomAttributes verwenden. Sie werden außerdem jedes Mal instanziiert. Das zweimalige Aufrufen von GetCustomAttributes in einer Zeile führt zur Rückgabe von zwei unterschiedlichen Instanzen von ObsoleteAttribute.

Allgemeine Attribute in der Runtime

Attribute werden in vielen Tools und Frameworks verwendet. NUnit verwendet Attribute wie z.B. [Test] und [TestFixture], die vom NUnit Test Runner verwendet werden. ASP.NET MVC verwendet Attribute wie [Authorize] und stellt ein Aktionsfilterframework bereit, um übergreifende Anforderungen (Cross-Cutting Concerns) für MVC-Aktionen umzusetzen. PostSharp verwendet die Attributsyntax, um eine aspektorientierte Programmierung in C# zu ermöglichen.

Nachfolgend werden einige wichtige Attribute aufgeführt, die in die .NET Core-Basisklassenbibliotheken integriert sind:

  • [Obsolete]. Dieses Attribut wurde in den obigen Beispielen verwendet, es ist im System-Namespace enthalten. Es ist nützlich, um eine deklarative Dokumentation für eine sich ändernde Codebasis bereitzustellen. Eine Meldung kann in Form einer Zeichenfolge bereitgestellt werden, und ein weiterer, boolescher Parameter kann für eine Eskalation von einer Compilerwarnung zu einem Compilerfehler verwendet werden.
  • [Conditional]. Dieses Attribut ist im System.Diagnostics-Namespace enthalten. Es kann auf Methoden (oder Attributklassen) angewendet werden. Sie müssen eine Zeichenfolge an den Konstruktor übergeben. Wenn diese Zeichenfolge nicht einer #define-Anweisung entspricht, werden alle Aufrufe dieser Methode (aber nicht die Methode selbst) durch den C#-Compiler entfernt. In der Regel verwenden Sie diese Technik für Debuggingzwecke (Diagnosezwecke).
  • [CallerMemberName]. Dieses Attribut kann für Parameter verwendet werden und ist im System.Runtime.CompilerServices-Namespace enthalten. CallerMemberName ist ein Attribut, mit dem der Name der Methode eingeführt wird, die eine andere Methode aufruft. So können „magische Zeichenfolgen“ beim Implementieren von „INotifyPropertyChanged“ in verschiedenen UI-Frameworks beseitigt werden. Beispiel:
public class MyUIClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    public void RaisePropertyChanged([CallerMemberName] string propertyName = default!)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private string? _name;
    public string? Name
    {
        get { return _name;}
        set
        {
            if (value != _name)
            {
                _name = value;
                RaisePropertyChanged();   // notice that "Name" is not needed here explicitly
            }
        }
    }
}

Im Code oben ist keine literale "Name"-Zeichenfolge erforderlich. Die Verwendung von CallerMemberName kann dazu beitragen, Programmfehler zu vermeiden, und es erleichtert das Refactoring/Umbenennen. Attribute verschaffen C# ein deklaratives Potenzial, aber sie sind eine Metadatenform von Code und agieren nicht selbst.