Kapitel 22 Attribute
In den meisten Programmiersprachen werden einige
der Informationen durch Deklarationen ausgedrückt, andere über
den Code. In der folgenden Klassenmitgliederdeklaration beispielsweise
public int Test;
reservieren Compiler und Laufzeitumgebung Speicherplatz
für eine integer-Variable und legen deren Schutz so fest, dass diese überall
sichtbar ist. Hierbei handelt es sich um ein Beispiel deklarativer Informationen;
diese Methode ist sehr schön, da es sich um einen ökonomischen
Ausdruck handelt, bei dem der Compiler die Details übernimmt.
Üblicherweise werden die Typen deklarativer
Informationen durch den Sprachentwickler vorgegeben und können
durch den Benutzer der Sprache nicht erweitert werden. Ein Benutzer,
der ein bestimmtes Datenbankfeld mit einem Feld einer Klasse verknüpfen möchte,
muss beispielsweise eine Methode finden, mit der in der jeweiligen Sprache
eine solche Beziehung ausgedrückt wird, die Beziehung gespeichert
wird und zur Laufzeit auf die Informationen zugegriffen werden kann.
In einer Sprache wie C++ kann ein Makro definiert werden, das die Informationen
in einem Feld speichert, das Bestandteil des Objekts ist. Solche Schemata
funktionieren, sind aber fehleranfällig und nicht allgemein einsetzbar.
Und sie sind hässlich.
Die .NET-Laufzeitumgebung unterstützt Attribute, bei denen es sich im
Prinzip um Anmerkungen handelt, die für Elemente des Quellcodes
gesetzt werden, beispielsweise für Klassen, Mitglieder, Parameter
usw. Attribute können zur Änderung des Laufzeitverhaltens,
zur Bereitstellung von Transaktionsinformationen zu einem Objekt oder zur Lieferung von strukturellen
Informationen für einen Designer verwendet werden. Die Attributinformationen
werden mit den Metadaten des Elements gespeichert und können zur
Laufzeit über die Reflektion leicht abgerufen werden.
C# verwendet ein Bedingungsattribut, um zu steuern, welche Mitgliederfunktion aufgerufen wird. Eine Verwendung des Bedingungsattributs
sähe folgendermaßen aus:
using System.Diagnostics;
class Test
{
[Conditional("DEBUG")]
public void Validate()
{
}
}
Die meisten Programmierer verwenden eher vordefinierte
Attribute, als selbst Attributklassen zu schreiben.
22.1 Verwenden von Attributen
 
Angenommen, für ein Projekt müssen die
Codeumarbeitungen verfolgt werden, die für Klassen vorgenommen
werden, damit ermittelt werden kann, welche Codeänderungen abgeschlossen
sind. Die Änderungsinformationen zum Code können in einer Datenbank gespeichert
werden, damit der Status leicht abgefragt werden kann, oder die Informationen
werden in Kommentaren gespeichert, wodurch die benötigten Informationen
direkt im Code verfügbar sind.
Alternativ könnte jedoch auch ein Attribut
eingesetzt werden, mit dem beide Zugriffsmöglichkeiten gegeben
sind.
Hierzu wird eine Attributklasse benötigt. Eine
Attributklasse definiert den Namen eines Attributs, die Art der Erstellung
und die gespeicherten Informationen. Die Einzelheiten bei der Definition
einer Attributklasse werden im Abschnitt »Eigene Attribute«
besprochen.
Die Attributklasse sieht folgendermaßen aus:
using System;
[AttributeUsage(AttributeTargets.Class)]
public class CodeReviewAttribute: System.Attribute
{
public CodeReviewAttribute(string reviewer, string date)
{
this.reviewer = reviewer;
this.date = date;
}
public string Comment
{
get
{
return(comment);
}
set
{
comment = value;
}
}
public string Date
{
get
{
return(date);
}
}
public string Reviewer
{
get
{
return(reviewer);
}
}
string reviewer;
string date;
string comment;
}
[CodeReview("Eric", "01-12-2000", Comment="Bitchin' Code")]
class Complex
{
}
Das AttributeUsage-Attribut vor der
Klasse gibt an, dass dieses Attribut nur für Klassen verwendet
werden kann. Wenn ein Attribut für ein Programmelement verwendet
wird, prüft der Compiler, ob die Verwendung des Attributs für
das jeweilige Programmelement zulässig ist.
Die Benennungskonvention für Attribute schreibt vor, dass Attribute
an das Ende des Klassennamens gehängt werden muss. So kann leichter
ermittelt werden, welche Klassen Attributklassen und welche Klassen normale Klassen darstellen.
Alle Attribute müssen von System.Attribute abgeleitet
werden.
Die Klasse definiert eine einzige Erstellungsroutine, die als Parameter den Prüfer und das Datum
umfasst sowie über die öffentliche Zeichenfolge Comment
verfügt.
Wenn der Compiler die Attributverwendung der Klasse
Complex prüft, wird zunächst nach einer von Attribute
abgeleiteten Klasse namens CodeReview gesucht. Diese ist
nicht vorhanden, daher wird als Nächstes nach einer Klasse CodeReviewAttribute
gesucht, die vorhanden ist.
Als Nächstes prüft der Compiler, ob das
Attribut für die Klasse zulässig ist.
Anschließend wird überprüft, ob eine
Erstellungsroutine vorliegt, die mit den in der Attributverwendung angegebenen
Parametern übereinstimmt. Ist eine solche Erstellungsroutine vorhanden,
wird eine Instanz des Objekts erstellt – die Erstellungsroutine
wird mit den angegebenen Werten aufgerufen.
Sind benannte Parameter vorhanden, werden die Namen
der Parameter mit einem Feld oder einer Eigenschaft in der Attributklasse
abgeglichen, anschließend werden Feld oder Eigenschaft auf den
angegebenen Wert gesetzt.
Im nächsten Schritt wird der aktuelle Status
der Attributklasse in den Metadaten des entsprechenden Programmelements
gespeichert.
Dies ist zumindest das, was logisch gesehen
passiert. Tatsächlich sieht es jedoch nur so aus, als würde
dies geschehen; siehe hierzu den gesondert formatierten Abschnitt »Einlegen
von Attributen«.
22.1.1 Noch ein paar Details
 
Einige Attribute können nur einmal auf ein
vorgegebenes Element angewendet werden. Andere, auch Mehrfachattribute genannt, können häufiger verwendet werden.
Diese Eigenschaft kann beispielsweise dazu genutzt werden, verschiedene
Sicherheitsattribute auf eine einzelne Klasse anzuwenden. Die Dokumentation
zu einem Attribut beschreibt, ob dieses einmalig oder mehrfach verwendet
werden kann.
In den meisten Fällen ist klar, dass sich das
Attribut auf ein bestimmtes Programmelement bezieht. Betrachten Sie
jedoch den folgenden Fall:
class Test
{
[ReturnsHResult]
public void Execute() {}
}
Normalerweise würde ein Attribut an dieser
Position für die Mitgliederfunktion gelten, dieses Attribut bezieht
sich jedoch auf den Rückgabetyp. Wie kann der Compiler dies erkennen?
Es gibt verschiedene Situationen, in denen dieser
Fall auftreten kann:
|
Methode kontra Rückgabewert |
|
Ereignis kontra Feld
oder Eigenschaft |
|
Zuweisung kontra Rückgabewert |
|
Eigenschaft kontra Erstellungsroutine
kontra Rückgabewert der Abrufroutine kontra Wertparameter der Festlegungsroutine |
In jeder dieser Situationen hat sich einer der Fälle
aufgrund seiner häufigeren Verwendung als Standardfall durchgesetzt.
Zur Festlegung von Attributen für alle anderen Fälle muss
das Element angegeben werden, auf das sich das Attribut bezieht.
class Test
{
[return:ReturnsHResult]
public void Execute() {}
}
Das return: gibt an, dass dieses Attribut
auf den Rückgabewert angewendet werden soll.
Das Element kann auch dann angegeben werden, wenn
keine Unklarheit besteht. Die Bezeichner lauten folgendermaßen:
Bezeichner
|
Beschreibung
|
assembly
|
Attribut gilt für Assemblierung
|
module
|
Attribut gilt für Modul
|
type
|
Attribut gilt für eine Klasse oder eine Struktur
|
method
|
Attribut gilt für eine Methode
|
property
|
Attribut gilt für eine Eigenschaft
|
event
|
Attribut gilt für ein Ereignis
|
field
|
Attribut gilt für ein Feld
|
param
|
Attribut gilt für einen Parameter
|
return
|
Attribut gilt für den Rückgabewert
|
Attribute, die auf Assemblierungen oder Module angewendet
werden, müssen nach jeder using-Klausel und vor dem
Code angegeben werden.
using System;
[assembly:CLSCompliant(true)]
class Test
{
Test() {}
}
In diesem Beispiel wird das ClsCompliant-Attribut
auf die gesamte Assemblierung angewendet. Alle Attribute auf Assemblierungsebene,
die in einer Datei der Assemblierung deklariert werden, werden zusammen
gruppiert und der Assemblierung angehängt.
Zur Verwendung eines vordefinierten Attributs ermitteln
Sie zunächst die Erstellungsroutine, die am ehesten mit den bereitzustellenden
Informationen übereinstimmt. Als Nächstes schreiben Sie das
Attribut und übergeben die Parameter an die Erstellungsroutine.
Abschließend verwenden Sie die Syntax für benannte Parameter,
um zusätzliche Informationen zu übergeben, die nicht Teil
der Erstellungsroutinenparameter sind.
Weitere Beispiele zur Attributverwendung finden Sie in Kapitel 29, Interoperabilität.
22.2 Einlegen von Attributen
 
Es gibt ein paar Gründe, warum das Erstellen
von Attributen nicht wirklich wie beschrieben funktioniert, und die
Gründe sind alle leistungsbezogen. Damit der Compiler das Attributobjekt
auch tatsächlich erstellen kann, muss die .NET-Laufzeitumgebung
ausgeführt werden, daher würde jede Kompilierung zu einem
Umgebungsstart führen, d. h., jeder Compiler müsste als
verwaltete ausführbare Datei ausgeführt werden.
Darüber hinaus ist die Objekterstellung nicht
wirklich erforderlich, da die Informationen nur gespeichert (»eingelegt«)
werden sollen.
Der Compiler stellt demnach sicher, dass er das
Objekt erstellen könnte, ruft die Erstellungsroutine
auf und setzt die Werte für benannte Parameter. Die Attributparameter
werden anschließend in einem Chunk binärer Informationen abgelegt,
die mit den Metadaten des Objekts verstaut werden.
22.3 Eigene Attribute
 
Bei der Definition von Attributklassen und deren
Reflektion zur Laufzeit sind einige weitere Punkte zu berücksichtigen.
In diesem Abschnitt werden einige Dinge besprochen, die beim Entwurf
eines Attributs von Bedeutung sind.
Beim Schreiben von Attributen müssen zwei Dinge
festgelegt werden. Hierzu zählen a) die Programmelemente, auf die
das Attribut angewendet werden kann, und b) die Informationen, die mit
dem Attribut gespeichert werden.
22.3.1 Attributverwendung
 
Durch Anwendung des AttributeUsage-Attributs
auf eine Attributklasse wird gesteuert, wo das Attribut eingesetzt werden
kann. Die möglichen Werte des Attributs werden in der Aufzählung
AttributeTargets aufgelistet und lauten folgendermaßen:
Wert
|
Bedeutung
|
Assembly
|
Die Programmassemblierung
|
Module
|
Die aktuelle Programmdatei
|
Class
|
Eine Klasse
|
Struct
|
Eine Struktur
|
Enum
|
Ein Aufzählungsbezeichner
|
Constructor
|
Eine Erstellungsroutine
|
Method
|
Eine Methode (Mitgliedsfunktion)
|
Property
|
Eine Eigenschaft
|
Field
|
Ein Feld
|
Event
|
Ein Ereignis
|
Interface
|
Eine Schnittstelle
|
Parameter
|
Ein Methodenparameter
|
Return
|
Der Rückgabewert für die Methode
|
Delegate
|
Eine Zuweisung
|
All
|
Überall
|
ClassMembers
|
Klasse, Struktur, Aufzählungsbezeichner, Erstellungsroutine,
Methode, Eigenschaft, Feld, Ereignis, Zuweisung, Schnittstelle
|
Als Teil des Attributs AttributeUsage
kann einer dieser Werte angegeben werden oder per OR zu einer Liste
zusammengestellt werden.
Über das Attribut AttributeUsage
wird auch festgelegt, ob ein Attribut einmalig oder mehrfach eingesetzt
werden kann. Dies wird mit dem benannten Parameter AllowMultiple
erreicht. Ein solches Attribut würde wie folgt aussehen:
[AttributeUsage( AttributeTargets.Method | AttributeTargets.Event,
AllowMultiple = true)]
22.3.2 Attributparameter
 
Die vom Attribut gespeicherten Informationen sollten
in zwei Gruppen unterteilt werden: die Informationen, die bei jeder
Verwendung erforderlich sind, und die optionalen Informationen.
Die bei jeder Verwendung erforderlichen Informationen
sollten über die Erstellungsroutine für die Attributklasse
abgerufen werden. Dies zwingt den Benutzer, bei der Attributverwendung
alle Parameter anzugeben.
Optionale Elemente sollten als benannte Parameter
implementiert werden, was dem Benutzer das Angeben der optionalen Elemente
bei Bedarf ermöglicht.
Kann ein Attribut anhand verschiedener Methoden
erstellt werden, die jeweils unterschiedliche Informationen erfordern,
können für jede Verwendung separate Erstellungsroutinen deklariert
werden. Verwenden Sie separate Erstellungsroutinen nicht als Alternative
zu optionalen Elementen.
Attributparametertypen
Das Attributspeicherformat unterstützt nur einen Teilsatz aller .NET-Laufzeitumgebung-Typen, daher können nur einige Typen als Attributparameter
eingesetzt werden. Folgende Typen sind erlaubt:
|
bool, byte, char, double,
float, int, long, short, string |
|
object |
|
System.Type |
|
Eine Aufzählung
(enum) mit öffentlichem Zugriff (keine Verschachtelung
in nicht öffentlichen Abschnitten) |
|
Ein eindimensionales
Array der vorstehenden Typen. |
|