22.4 Attributreflektion
 
Nachdem Attribute im Code definiert wurden, ist
es sinnvoll, die Attributwerte ermitteln zu können. Dies wird mit
Hilfe der Reflektion erreicht.
Der folgende Code zeigt eine Attributklasse, die
Anwendung des Attributs auf eine Klasse sowie die Reflektion einer Klasse
zum Abrufen der Attribute.
using System;
using System.Reflection;
[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
{
}
class Test
{
public static void Main()
{
System.Reflection.MemberInfo info;
info = typeof(Complex);
object[] atts;
atts = info.GetCustomAttributes(typeof(CodeReviewAttribute));
if (atts.GetLength(0) != 0)
{
CodeReviewAttribute att = (CodeReviewAttribute) atts[0];
Console.WriteLine("Reviewer: {0}", att.Reviewer);
Console.WriteLine("Date: {0}", att.Date);
Console.WriteLine("Comment: {0}", att.Comment);
}
}
}
Die Main()-Funktion ruft zunächst den mit Complex
verknüpften Objekttyp ab. Anschließend werden alle Attribute
geladen, die den Typ CodeReviewAttribute aufweisen. Verfügt
das Attributarray über Einträge, wird für das erste
Element ein cast in ein CodeReviewAttribute
durchgeführt, und der Wert wird ausgegeben. Es kann sich nur ein
Eintrag im Array befinden, da CodeReviewAttribute nur einmalig
verwendet werden kann.
Dieser Code erzeugt die folgende Ausgabe:
Reviewer: Eric
Date: 01-12-2000
Comment: Bitchin' Code
GetCustomAttribute() kann auch ohne
Typ aufgerufen werden, um alle benutzerdefinierten Attribute für
dieses Objekt zu erhalten.
Kapitel 23 Zuweisungen
Zuweisungen (delegate-Schlüsselwort) sind Schnittstellen dahingehend ähnlich,
dass sie Festlegungen zwischen einer aufrufenden Komponente und einer
implementierenden Komponente treffen. Statt jedoch eine ganze Schnittstelle
zu spezifizieren, wird über eine Zuweisung nur die Form einer einzelnen
Funktion festgelegt. Darüber hinaus werden Schnittstellen zur Kompilierungszeit,
Zuweisungen dagegen zur Laufzeit erstellt.
23.1 Verwenden von Zuweisungen
 
Die Spezifikation der Zuweisung (delegate)
bestimmt die Form der Funktion, und zum Erstellen einer Zuweisungsinstanz
muss eine Funktion vorliegen, die dieser Form entspricht. Zuweisungen
werden gelegentlich auch als »sichere Funktionszeiger« bezeichnet.
Im Gegensatz zu Funktionszeigern können C#-Zuweisungen jedoch mehr als eine Funktion aufrufen; werden
zwei Zuweisungen zusammengefügt, ergibt sich so eine Zuweisung,
mit der beide Zuweisungen aufgerufen werden.
Aufgrund ihrer dynamischeren Natur sind Zuweisungen
immer dort nützlich, wo der Benutzer das Verhalten ändern
möchte. Wenn beispielsweise eine Auflistungsklasse eine Sortierfunktion implementiert, sollen vielleicht verschiedene Sortierreihenfolgen
unterstützt werden. Die Sortierung kann basierend auf einer Zuweisung
gesteuert werden, mit der die Vergleichsfunktion definiert wird.
using System;
public class Container
{
public delegate int CompareItemsCallback(object obj1, object obj2);
public void Sort(CompareItemsCallback compare)
{
// keine richtige Sortierung, nur eine Demonstration dessen,
// was der innere Schleifencode tun kann
int x = 0;
int y = 1;
object item1 = arr[x];
object item2 = arr[y];
int order = compare(item1, item2);
}
object[] arr = new object[1]; // Elemente in der Auflistung
}
public class Employee
{
Employee(string name, int id)
{
this.name = name;
this.id = id;
}
public static int CompareName(object obj1, object obj2)
{
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(String.Compare(emp1.name, emp2.name));
}
public static int CompareId(object obj1, object obj2)
{
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
if (emp1.id > emp2.id)
return(1);
if (emp1.id < emp2.id)
return(-1);
else
return(0);
}
string name;
int id;
}
class Test
{
public static void Main()
{
Container employees = new Container();
// Hier einige Mitarbeiter erstellen und hinzufügen
// Zuweisung zur Sortierung nach Namen erstellen und Sortierung
durchführen
Container.CompareItemsCallback sortByName =
new Container.CompareItemsCallback(Employee.CompareName);
employees.Sort(sortByName);
// Mitarbeiter sind jetzt nach Name sortiert
}
}
Die in der Container-Klasse definierte Zuweisung (delegate) speichert
die zwei zu vergleichenden Objekte als Parameter und gibt einen integer-Wert
zurück, der die Reihenfolge der zwei Objekte angibt. Es werden
zwei statische Funktionen deklariert, die als Bestandteil der Employee-Klasse mit dieser Zuweisung übereinstimmen (bei allen
Zuweisungen muss es sich um statische Funktionen handeln). Hierbei wird
durch jede Funktion eine andere Sortierung beschrieben.
Wenn der Container sortiert werden soll, kann eine
Zuweisung übergeben werden, in der die zu verwendende Sortierung
beschrieben wird. Anschließend wird die Sortierung über die
Sortierfunktion durchgeführt.
Nun, das geschähe, wenn diese tatsächlich
implementiert wäre.
23.2 Zuweisungen als statische Mitglieder
 
Ein Nachteil bei diesem Ansatz ist der, dass der
Benutzer, der die Sortierung verwenden möchte, eine Zuweisungsinstanz
mit der geeigneten Funktion erstellen muss. Es wäre schöner,
wenn dies nicht erforderlich wäre – erreicht wird dies, indem
Sie die geeigneten Zuweisungen als statische Mitglieder von Employee
definieren:
using System;
public class Container
{
public delegate int CompareItemsCallback(object obj1, object obj2);
public void Sort(CompareItemsCallback compare)
{
// keine richtige Sortierung, nur eine Demonstration dessen,
// was der innere Schleifencode tun kann
int x = 0;
int y = 1;
object item1 = arr[x];
object item2 = arr[y];
int order = compare(item1, item2);
}
object[] arr = new object[1]; // Elemente in der Auflistung
}
class Employee
{
Employee(string name, int id)
{
this.name = name;
this.id = id;
}
public static readonly Container.CompareItemsCallback SortByName
=
new Container.CompareItemsCallback(CompareName);
public static readonly Container.CompareItemsCallback SortById =
new Container.CompareItemsCallback(CompareId);
public static int CompareName(object obj1, object obj2)
{
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(String.Compare(emp1.name, emp2.name));
}
public static int CompareId(object obj1, object obj2)
{
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
if (emp1.id > emp2.id)
return(1);
if (emp1.id < emp2.id)
return(-1);
else
return(0);
}
string name;
int id;
}
class Test
{
public static void Main()
{
Container employees = new Container();
// Hier einige Mitarbeiter erstellen und hinzufügen
employees.Sort(Employee.SortByName);
// Mitarbeiter sind jetzt nach Name sortiert
}
}
Diese Vorgehensweise ist sehr viel einfacher. Die
Benutzer von Employee müssen nicht wissen, wie die
Zuweisung erstellt wird – sie können einfach auf das statische
Mitglied verweisen.
23.3 Zuweisungen als statische Eigenschaften
 
Ein weiterer unschöner Punkt ist der, dass
die Zuweisung immer erstellt wird, selbst dann, wenn
sie nie eingesetzt wird. Das ist unnötig. Es wäre besser,
wenn die Zuweisung nach Bedarf erstellt würde. Dies kann erreicht
werden, indem die statischen Funktionen durch Eigenschaften ersetzt
werden:
using System;
class Container
{
public delegate int CompareItemsCallback(object obj1, object obj2);
public void SortItems(CompareItemsCallback compare)
{
// keine richtige Sortierung, nur eine Demonstration dessen,
// was der innere Schleifencode tun kann
int x = 0;
int y = 1;
object item1 = arr[x];
object item2 = arr[y];
int order = compare(item1, item2);
}
object[] arr; // Elemente in der Auflistung
}
class Employee
{
Employee(string name, int id)
{
this.name = name;
this.id = id;
}
public static Container.CompareItemsCallback SortByName
{
get
{
return(new Container.CompareItemsCallback(CompareName));
}
}
public static Container.CompareItemsCallback SortById
{
get
{
return(new Container.CompareItemsCallback(CompareId));
}
}
static int CompareName(object obj1, object obj2)
{
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(String.Compare(emp1.name, emp2.name));
}
static int CompareId(object obj1, object obj2)
{
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
if (emp1.id > emp2.id)
return(1);
if (emp1.id < emp2.id)
return(-1);
else
return(0);
}
string name;
int id;
}
class Test
{
public static void Main()
{
Container employees = new Container();
// Hier einige Mitarbeiter erstellen und hinzufügen
employees.SortItems(Employee.SortByName);
// Mitarbeiter sind jetzt nach Name sortiert
}
}
Bei dieser Version handelt es sich bei Employee.SortByName
nicht um eine Zuweisung, sondern um eine Funktion, die eine Zuweisung
zurückgibt, mit der eine Sortierung nach Name durchgeführt
werden kann.
Anfänglich verfügte dieses Beispiel über
die als privat deklarierten statischen Zuweisungsmitglieder SortByName
und SortById, und das statische Mitglied wurde über
die Eigenschaft erstellt, wenn es zuvor nicht benötigt worden war.
Dies würde gut funktionieren, wenn die Zuweisungserstellung ressourcenintensiv
und eine erneute Verwendung wahrscheinlich wäre.
In diesem Fall jedoch ist es sehr viel einfacher,
die Zuweisung nach Bedarf zu erstellen und einfach an den Benutzer zurückzugeben.
Sobald die Sort-Funktion über die Zuweisung für
Container ausgeführt wurde, kann eine Speicherbereinigung erfolgen.
|