Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
1 Einführung
2 Grundlagen der Sprachsyntax
3 Klassendesign
4 Weitere Datentypen
5 Multithreading
6 Collections und LINQ
7 Eingabe und Ausgabe
8 Anwendungen: Struktur und Installation
9 Code erstellen und debuggen
10 Einige Basisklassen
11 Windows-Anwendungen erstellen
12 Die wichtigsten Steuerelemente
13 Tastatur- und Mausereignisse
14 MDI-Anwendungen
15 Grafiken mit GDI+
16 Drucken
17 Entwickeln von Steuerelementen
18 Programmiertechniken
19 WPF – Grundlagen
20 Layoutcontainer
21 WPF-Steuerelemente
22 Konzepte von WPF
23 Datenbankverbindung mit ADO.NET
24 Datenbankabfragen mit ADO.NET
25 DataAdapter
26 Offline mit DataSet
27 Datenbanken aktualisieren
28 Stark typisierte DataSets
A Anhang: Einige Übersichten
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual Basic 2008 von Andreas Kuehnel, Stephan Leibbrandt
Das umfassende Handbuch
Buch: Visual Basic 2008

Visual Basic 2008
3., aktualisierte und erweiterte Auflage, geb., mit DVD
1.323 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1171-0
Pfeil 4 Weitere Datentypen
Pfeil 4.1 Module
Pfeil 4.2 Strukturen
Pfeil 4.2.1 Vor- und Nachteile von Referenz- und Werttypen
Pfeil 4.2.2 Initialisierung und Konstruktoren
Pfeil 4.2.3 Boxing
Pfeil 4.2.4 Innere Strukturen
Pfeil 4.2.5 ValueType
Pfeil 4.3 Enumerationen
Pfeil 4.3.1 Datentyp
Pfeil 4.3.2 System.Enum
Pfeil 4.4 Generisches
Pfeil 4.4.1 Einschränkungen
Pfeil 4.4.2 Innere generische Typen
Pfeil 4.4.3 Vererbung und Überladung
Pfeil 4.4.4 Sichtbarkeit
Pfeil 4.4.5 Bindung
Pfeil 4.4.6 Rekursive Typen
Pfeil 4.4.7 Generische Typen in .NET
Pfeil 4.5 Werttypen mit dem Wert Nothing
Pfeil 4.5.1 Typumwandlungen
Pfeil 4.5.2 Logische Operatoren
Pfeil 4.5.3 Boxing
Pfeil 4.6 Anonyme Klassen
Pfeil 4.6.1 Automatische Namen
Pfeil 4.6.2 Schlüssel und Equals
Pfeil 4.6.3 Innere anonyme Klassen
Pfeil 4.6.4 Generierter Code
Pfeil 4.7 Spezielle Typen
Pfeil 4.8 Attribute
Pfeil 4.8.1 Beispiel: Bedingte Kompilierung
Pfeil 4.8.2 Codeerzeugung: DesignerGenerated
Pfeil 4.8.3 Standardinstanzen: MyGroupCollectionAttribute
Pfeil 4.8.4 Klassenerweiterungen: Extension
Pfeil 4.8.5 Attribute abfragen
Pfeil 4.8.6 Benutzerdefinierte Attribute


Rheinwerk Computing - Zum Seitenanfang

4.8 Attribute Zur nächsten ÜberschriftZur vorigen Überschrift

Klassen und Klassenmitglieder können in ihrer Funktionalität durch Attribute erweitert werden. Damit verändern Sie nicht die Klasse selbst, sondern geben anderen Klassen ergänzende Informationen. Die Klasse selbst hat in der Regel kein Interesse an Attributen. Daher werden Attribute eingeführt, wenn einer anderen Klasse die Typdefinition dieser Klasse nicht ausreicht, um mit ihr zusammenzuarbeiten. Zum Beispiel gibt es Klassen im .NET, die Objekte speichern und dazu die objektgebundenen Variablen der Reihe nach durchlaufen. Soll dabei nicht alles gesichert werden, müssen die (nicht) zu sichernden Elemente gekennzeichnet werden. Zum Beispiel ist es bei der Sicherung eines Computerbenutzers sinnvoll, dessen Namen zu sichern, aber das Kennwort des Zugangs bei der Speicherung zu überspringen (sonst kann jeder es lesen). In einem solchen Fall würden Sie das Kennwort mittels eines Attributs als nicht zu sichern kennzeichnen. Ein weiteres Beispiel ist das Attribut DllAttribut zur Deklaration externer Funktionen (siehe Abschnitt 3.4.3, »Externe Funktionen«).

Attribute werden vor Modifikatoren wie Public in spitzen Klammern deklariert. In der folgenden Syntax sind optionale Teile in eckige Klammern gesetzt, Alternativen durch | getrennt, und kursive Namen müssen Sie Ihren Bedürfnissen anpassen. Mehrere Attribute können entweder als Einzelattribute hintereinander geschrieben oder kommagetrennt in dieselben spitzen Klammern gesetzt werden.


<[Assembly:|Module:] Name [([Positionsargumente [, Eigenschaftswerte]])] 
  [, weitere Attribute]>

Durch diese Syntax wird ein Attribut lediglich deklariert. An einer anderen Stelle muss eine Klasse mit dem Namen des Attributs definiert werden, die von System.Attribute abgeleitet ist. Ob sich daraufhin das Verhalten der Klasse oder Methode ändert, obliegt der Methode, die das Attribut auswertet (wie es einige Methoden des .NET Frameworks machen). Bei der Deklaration sind einige Dinge zu beachten:

  • Attribute sind optional, ihre Reihenfolge ist beliebig.
  • Wenn der Compiler nach dem Anhängen von Attribute an den Namen ein Attribut nicht findet, wird die Suche ohne das Suffix Attribute wiederholt.
  • Attribute müssen zur Compilezeit vollständig festgelegt sein, insbesondere sind alle Werte Konstanten.
  • Die runden Klammern parameterloser Attribute sind optional.
  • Typparameter können nicht in Attributargumenten verwendet werden (konkrete generische Typen sind erlaubt).
  • Durch Assembly: oder Module: gekennzeichnete Attribute müssen nach Option- und Imports-Anweisungen und vor allen anderen Deklarationen stehen.

Die Positionsargumente werden an den Konstruktor der Attributklasse weitergereicht. Die Werte von öffentlichen Instanzvariablen oder -eigenschaften werden in beliebiger Reihenfolge, aber nach den Positionsargumenten durch


Eigenschaft := Wert

festgelegt. Die folgenden Unterabschnitte werden den Gebrauch von Attributen und deren Definition deutlich machen. Dies ist nur eine sehr kleine Auswahl. Ich habe 443 Attribute im .NET Framework gezählt; einen kleinen Ausschnitt zeigt Tabelle 4.1. Ein Beispiel für ein Attribut haben Sie schon in Abschnitt 3.4.3, »Externe Funktionen«, kennengelernt.


Tabelle 4.1 Attribute im Namensraum »System«

Attribut Beschreibung

AttributeUsageAttribute

Anwendungsbereich eines benutzerdefinierten Attributs

CLSCompliantAttribute

Markierung als standardkonform

ContextStaticAttribute

Werte statischer Felder, nach Kontext getrennt

FlagsAttribute

Verwendung einer Enumeration als Bitfeld

LoaderOptimizationAttribute

Hinweis zur Optimierung für den Klassenlader

MTAThreadAttribute

Multithreading für COM-Interaktion

NonSerializedAttribute

Serialisierung eines Feldes unterdrücken

ObsoleteAttribute

Hinweis auf veraltete Programmelemente

ParamArrayAttribute

Das zu ParamArray korrespondierende Attribut

SerializableAttribute

Datentyp als serialisierbar markieren

STAThreadAttribute

Einzelthread für COM-Interaktion

ThreadStaticAttribute

Werte statischer Felder, nach Thread getrennt



Rheinwerk Computing - Zum Seitenanfang

4.8.1 Beispiel: Bedingte Kompilierung Zur nächsten ÜberschriftZur vorigen Überschrift

Oft wird das Flags-Attribut für Enumerationen zur Einführung verwendet. Da es nach meinen Tests optional zu sein scheint, das Attribut explizit anzugeben, zeige ich ein Beispiel für bedingte Kompilierung. Das Attribut Conditional steuert, ob ein Methodenaufruf in die ausführbare Datei übernommen wird. Dazu wird geprüft, ob die als Zeichenkette übergebene Compilerkonstante definiert ist. Dies können Sie entweder über die Projekteinstellungen machen, oder Sie platzieren eine #Const-Anweisung im Quelltext. Das folgende Beispiel definiert eine Ausgabemethode, die nur dann ausgeführt werden soll, wenn die Konstante An definiert ist. In der Methode Test() steht je ein Aufruf der Methode vor und nach der Definition der Konstanten. Die Zahl 0 und False als Wert für die Konstante sind gleichwertig.


'...\Datentypen\Attribute\Bedingung.vb

Option Strict On 
Namespace Datentypen 
  Module Bedingung

    <Conditional("An")> Sub Ausgabe(ByVal text As String) 
      Console.WriteLine(text) 
    End Sub

    Sub Test() 
      Ausgabe("Maier") 
#Const An = True 
      Ausgabe("Schulze") 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe des Programms zeigt, dass nur der zweite Aufruf erfolgt:

 Schulze

Rheinwerk Computing - Zum Seitenanfang

4.8.2 Codeerzeugung: DesignerGenerated Zur nächsten ÜberschriftZur vorigen Überschrift

Einige Werkzeuge in Visual Studio, wie zum Beispiel der grafische Designer für Windows-Anwendungen, markieren eine Klasse mit dem Attribut DesignerGenerated. Dies hat unter anderem den Effekt, dass der Standardkonstruktor implizit die Methode InitializeComponent aufruft. Dies zeigt zum einen, dass auch Klassen mit Attributen versehen werden können, und zum anderen, dass auch ausführbarer Code implizit durch Attribute erzeugt werden kann. Das nächste Codefragment zeigt eine Klasse Rechner, die mit dem Attribut DesignerGenerated markiert ist und keinen expliziten Konstruktor enthält.


'...\Datentypen\Attribute\Implizit.vb

Option Strict On 
Namespace Datentypen

  <Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> Class Rechner 
    Sub InitializeComponent() 
      Console.WriteLine("In InitializeComponent") 
    End Sub 
  End Class

  Module Implizit 
    Sub Test() 
      Dim r As New Rechner() 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Wie die Ausgabe zeigt, enthält der vom Compiler automatisch generierte parameterlose Standardkonstruktor einen Aufruf von InitializeComponent(), der nirgendwo im Quelltext steht:

In InitializeComponent

Rheinwerk Computing - Zum Seitenanfang

4.8.3 Standardinstanzen: MyGroupCollectionAttribute Zur nächsten ÜberschriftZur vorigen Überschrift

Manchmal ist es syntaktisch notwendig, statt einer Klasse ein Objekt vom Typ der Klasse zu verwenden. Dann ist es praktisch, einen Mechanismus zur Hand zu haben, der einem das benötigte Objekt verschafft. Das Attribut MyGroupCollection kennzeichnet eine Klasse als Fabrik für solche Standardinstanzen. Das erste Argument des Attributs spezifiziert eine oder mehrere durch Komma getrennte Klassen, für deren abgeleitete Klassen jeweils ein Objekt erstellt wird, auf das über eine gleichnamige Eigenschaft der Fabrik zugegriffen werden kann. Die Erstellung übernimmt die im zweiten Argument angegebene Methode, während das dritte Argument auf die Methode verweist, die aufgerufen wird, wenn ein Standardobjekt zerstört wird. Das vierte Argument würde einen Ausdruck spezifizieren, den der Compiler gegebenenfalls anstelle des Klassennamens verwenden darf, wenn dies im Compiler implementiert wäre. Der Mechanismus wird von Klassen zur Windows- und Webprogrammierung genutzt.

Das folgende Beispiel zeigt eine solche Erzeugungsklasse namens Fabrik und eine kleine Klassenhierarchie mit der Basisklasse Licht. Die Methode Test() nutzt die Standardinstanzen der abgeleiteten Klassen, ohne dass diese im Quelltext erzeugt werden müssen – der Compiler fügt den nötigen Code automatisch hinzu. Dieser Automatismus greift auch für jede neu definierte Klasse, die von Licht abgeleitet ist. So können Sie die Erzeugung einer Standardinstanz nicht aus Versehen vergessen – eine Standardinstanz existiert garantiert.


'...\Datentypen\Attribute\Standard.vb

Option Strict On 
Namespace Datentypen

  <Microsoft.VisualBasic.MyGroupCollection( _ 
    "Attribute.Datentypen.Licht,", "Neu", "Aus", "Pseudonym" _ 
  )> Class Fabrik 
    Private Shared Function Neu(Of T As {New, Licht})(ByVal obj As T) As T 
      If obj Is Nothing Then Return New T() Else Return obj 
    End Function

    Private Shared Sub Aus(Of T As Licht)(ByRef aktion As T) 
      Console.WriteLine("Licht {0} ausmachen", aktion.was) 
      aktion = Nothing 
    End Sub 
  End Class

  Class Licht : Public was As String = "hell" : End Class 
  Class Kerze : Inherits Licht : Public was As String = "Kerze" : End Class 
  Class Lampe : Inherits Licht : Public was As String = "Lampe" : End Class

  Module Standard 
    Sub Test() 
      Dim fab As New Fabrik() 
      Console.WriteLine("Standardkerze: {0}", fab.Kerze.was) 
      Console.WriteLine("Standardlampe: {0}", fab.Lampe.was) 
      fab.Kerze = Nothing 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die ersten beiden Ausgaben reflektieren die automatisch generierten Instanzen der abgeleiteten Klassen. Für die Basisklasse, hier Licht, wird kein Standardobjekt erzeugt. Wie die letzte Ausgabe zeigt, wird bei Vernichtung der Standardinstanz automatisch die Methode Aus() in Fabrik aufgerufen. Da diese eine Basisklassenreferenz vom Typ Licht verwendet und die Variable was nicht polymorph ist, wird die Bezeichnung der Basisklasse Licht ausgegeben.

Standardkerze: Kerze 
Standardlampe: Lampe 
Licht hell ausmachen

Rheinwerk Computing - Zum Seitenanfang

4.8.4 Klassenerweiterungen: Extension Zur nächsten ÜberschriftZur vorigen Überschrift

Mit Attributen lässt sich auch eine Klasse erweitern, ohne die Klasse selbst ändern zu müssen. Ich halte diese Art der Definition von Methoden zwar für komfortabel, aber sie macht die Klassendefinition sehr viel unübersichtlicher. Es reicht nicht mehr, die Beschreibung einer Klasse zu konsultieren, sondern es müssen alle Erweiterungen nachgeschlagen werden, um den vollen Funktionsumfang zu ermitteln. Sind diese nicht sauber bei der Klasse dokumentiert, endet das Ganze schnell im Chaos. Bei entsprechender Disziplin sind die Möglichkeiten aber enorm. Tabelle 4.2 zeigt Klassen im .NET Framework, die Erweiterungsmethoden bereitstellen. Wenn Sie bei einer Klasse mehr als die dokumentierte Funktionalität vorfinden, lohnt es sich, diese in einer der gelisteten Klassen zu suchen.


Tabelle 4.2 Klassen mit Erweiterungsmethoden

Klasse

Microsoft.VisualStudio.Tools.Applications.MarshalExtensions

System.Data.Linq.SqlClient.SqlNodeTypeOperators

System.Linq.Enumerable

System.Linq.Expressions.ReadOnlyCollectionExtensions

System.Linq.Queryable

System.Web.Query.Dynamic.DynamicQueryable

System.Xml.Linq.Extensions

System.Xml.XPath.Extensions


Beispiel

Als einfaches Beispiel dient uns der implizite Zugriff auf die Klasse Enumerable. Sie definiert für alle Klassen, die die Schnittstelle IEnumerable implementieren, eine ganze Reihe von Erweiterungsmethoden. Wir greifen uns hier die Methode ElementAt() heraus. In der Dokumentation zu Arrays werden Sie feststellen, dass die Methode für Arrays nicht definiert ist, aber Array die Schnittstelle IEnumerable implementiert. Um sicherzugehen, verwendet das Beispiel den Reflection-Mechanismus von .NET, um mit GetMethod() nach der Methode im Array und in Enumerable zu suchen. Bevor wir einen Zugriff mit ElementAt() in der letzten Ausgabe wagen, prüfen wir noch, ob das Array die Schnittstelle implementiert. Sollten Sie lieber darauf verzichten wollen, können Sie die Information auch anhand der Dokumentation des .NET Frameworks ermitteln.


'...\Datentypen\Attribute\Enumerable.vb

Option Strict On 
Namespace Datentypen 
  Module Enumerable 
    Sub Test() 
      Dim werte As Integer() = {2, 8, –2, 12}

      Console.WriteLine("Array hat Methode ElementAt: {0}", _ 
        werte.GetType().GetMethod("ElementAt") IsNot Nothing)

      Console.WriteLine("Enumerable hat Methode ElementAt: {0}", _ 
        GetType(System.Linq.Enumerable).GetMethod("ElementAt") IsNot Nothing)

      Console.WriteLine("Array hat Typ IEnumerable: {0}", _ 
        werte.GetType().GetInterfaces().Contains(GetType(IEnumerable)))

      Console.WriteLine("Position 2: {0}", werte.ElementAt(2))

      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die ersten beiden Ausgaben zeigen, dass die Methode ElementAt() nicht im Array enthalten ist, wohl aber in Enumerable. In der dritten Ausgabe sehen wir, dass das Array vom Typ IEnumerable ist. Für so einen Typ definiert Enumerable die Erweiterungsmethode ElementAt(), so dass das Element in der vierten Ausgabe gezeigt werden kann.

Array hat Methode ElementAt: False 
Enumerable hat Methode ElementAt: True 
Array hat Typ IEnumerable: True 
Position 2: –2

Hinweis
IntelliSense erkennt die Methode als Erweiterung und markiert sie speziell in der Auswahlliste, in Texten mit <Extension>.


Prinzip

Um die Idee der Erweiterungsmethoden zu erfassen, ist es am leichtesten, selbst eine Klasse mit einer Erweiterungsmethode zu definieren. Sie muss

  • in einem Module stehen (Klassen sind nicht erlaubt, auch nicht, wenn die Methode mit Shared an die Klasse gebunden wird),
  • durch das Attribut Extension markiert werden,
  • für die nutzende Klasse ausreichende Sichtbarkeit haben (zum Beispiel Public),
  • eine normale Methode sein und kein Operator,
  • als ersten Parameter ein Objekt der zu erweiternden Klasse haben, das nicht optional sein darf und
  • von einem Objekt und nicht einer Klasse angesprochen werden.

Darüber hinaus darf sie

  • generische Typen enthalten, die beim Aufruf aufgelöst werden,
  • Schnittstellen erweitern (ohne Einfluss auf Implements-Klauseln),
  • das erweiterte Objekt als ByRef-Parameter verwenden,
  • in AddressOf für ein Delegate verwendet werden und
  • implizit aufgerufen werden, zum Beispiel durch eine For Each–Schleife.

Hinweis
Object als Typ des ersten Parameters und eine frühe Bindung mit Option Strict On widersprechen sich.


Um die Methode zu verwenden, rufen Sie sie über eine Objektreferenz des richtigen Typs auf, wobei Sie den ersten Parameter weglassen. Wenn die Methode nur einen Parameter hat, rufen Sie sie also ohne Parameter auf. Ist der weggelassene Parameter ein mit ByVal deklarierter Werttyp, wird er wie üblich als Kopie an die Methode übergeben. Das folgende Beispiel zeigt eine Klasse Staat, die durch das Module Geldquellen um die Methode Steuern() erweitert wird.


'...\Datentypen\Attribute\Erweiterung.vb

Option Strict On 
Namespace Datentypen

  Module Geldquellen 
    <System.Runtime.CompilerServices.Extension()> _ 
    Friend Sub Steuer(ByVal s As Staat, ByVal wert As Double) 
      Console.WriteLine("Steuer {0}", wert) 
      If s IsNot Nothing Then s.budget += wert 
    End Sub 
  End Module

  Class Staat 
    Friend budget As Double = 100 
    Shared Sub Test() 
      Dim d As New Staat() 
      Console.WriteLine("Budget vor Steuer: {0}", d.budget) 
      d.Steuer(10) 
      Console.WriteLine("Budget nach Steuer: {0}", d.budget) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die neue Methode wird korrekt angesprochen:

Budget vor Steuer: 100 
Steuer 10 
Budget nach Steuer: 110

Hinweis
Sie müssen selbst auf Nullreferenzen in Erweiterungsmethoden aufpassen.


Priorität

Die Reihenfolge der Auswertung sorgt dafür, dass Erweiterungsmethoden nachrangig angesprochen werden (ist ein Schritt erfolgreich, fällt der Rest weg):

  • passende Methode in der Klasse selbst oder innerhalb der Klassenhierarchie aufrufen
  • implizite Typumwandlungen
  • passende Methode in der Klasse selbst oder innerhalb der Klassenhierarchie aufrufen
  • Erweiterungsmethode aufrufen

Das nächste Beispiel zeigt diese Reihenfolge anhand der Anrede einer Person. Dazu wird eine Klasse Herr ohne Anrede definiert und eine davon abgeleitete Klasse Doktor mit Anrede. Wenn nichts Konkretes in der Klasse festgelegt ist, sollen zwei Methoden im Module Anreden greifen, die zwei Arten der Namensspezifikation unterstützen. Die Methode Anrede wird über ein Objekt des Typs Doktor mit einer Zeichenkette und einem Feld von Zeichen aufgerufen. Schließlich erfolgt ein Aufruf mit einer Referenz vom Typ Herr.


'...\Datentypen\Attribute\Reihenfolge.vb

Option Strict On 
Namespace Datentypen

  Module Anreden 
    <System.Runtime.CompilerServices.Extension()> _ 
    Friend Sub Anrede(ByVal s As Herr, ByVal name As String) 
      Console.WriteLine("Hallo " & name) 
    End Sub

    <System.Runtime.CompilerServices.Extension()> _ 
    Friend Sub Anrede(ByVal s As Herr, ByVal name As Char()) 
      Console.WriteLine("Guten Tag " & name) 
    End Sub 
  End Module

  Class Herr : End Class 
  Class Doktor : Inherits Herr 
    Sub Anrede(ByVal name As String) 
      Console.WriteLine("Herr Dr. " & name) 
    End Sub 
  End Class

  Module Reihenfolge 
    Sub Test() 
      Dim dr As New Doktor() 
      Dim hr As Herr = dr 
      dr.Anrede("Schmidt") 
      dr.Anrede(New Char() {"S"c, "c"c, "h"c, "m"c, "i"c, "d"c, "t"c}) 
      hr.Anrede("Schmidt") 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die ersten beiden Ausgaben verwenden die in der Klasse definierte Methode. Dies zeigt, dass die implizite Typumwandlung noch vor der zweiten Erweiterungsmethode angewendet wird, die vom Typ her besser passt. Nur im dritten Fall hat die Klasse Herr keine passende Anrede, und es wird die Erweiterungsmethode verwendet.

Herr Dr. Schmidt 
Herr Dr. Schmidt 
Hallo Schmidt

Generisches: Map und Fold

In Bezug auf generische Typen stellen Erweiterungsmethoden nichts Besonderes dar. Das folgende Beispiel ist etwas schwerer verdaulich, dafür ist diese neue Idee extrem flexibel einsetzbar. Das Beispiel definiert Erweiterungsmethoden für Datensammlungen und verwendet, um es allgemein zu halten, statt eines konkreten Typs die generische Schnittstelle IEnumerable(Of TArg). Sie stellt sicher, dass wir eine For Each-Schleife verwenden können. Auf die Schnittstelle (und die verwendeten Listen) gehen wir im nächsten Kapitel ein. Hier reicht es zu wissen, dass Arrays diese Schnittstelle implementieren. Die Definition der Erweiterungsmethoden ist relativ komplex, aber der Gebrauch sehr einfach. Wenn Sie die Funktionalität nur verwenden wollen, reicht es, die Erklärung in den nächsten beiden Absätzen zu überfliegen.

Die erste Methode, Map(), wendet eine im zweiten Argument gegebene Funktion auf jedes Element der im ersten Argument (implizit) übergebenen Liste an. Dazu wird zuerst der Fall einer leeren Liste behandelt. Ist die Liste nicht leer, wird die Funktion auf das erste Listenelement angewendet und einer neuen Liste mit Add() hinzugefügt. Der Rest der Liste wird rekursiv hinzugefügt, indem Map() durch Skip(1) mit dem Rest der Eingabeliste aufgerufen und mit AddRange() der neuen Liste hinzugefügt wird (auf Listen gehen wir im nächsten Kapitel näher ein). Zum Beispiel ist das Ergebnis des Aufrufs Map({a, b, c}, f) die Liste {f(a), f(b), f(c)}.

Die zweite Methode, Fold(), startet mit einem Wert, der im dritten Argument übergeben wird. Danach wird dieser Wert mit dem ersten Element der im ersten Argument übergebenen Liste mittels der im zweiten Argument gegebenen Funktion »kombiniert«. Wie die Kombination aussieht, ist mehr oder weniger beliebig. Dieses neue Ergebnis wird dann mit dem dritten Listenelement verknüpft und so weiter. Zum Beispiel liefert der Aufruf Fold({a, b, c}, f, s) das Ergebnis f(f(f(s, a), b), c).

In der Methode Test() können Sie sehen, dass die Verwendung der Funktionen recht einfach ist. Zuerst wird eine beliebige Liste definiert, hier eine Liste ganzer Zahlen. In der ersten Ausgabe wird Map() auf dieser Liste mit einer Funktion aufgerufen, die die Listenelemente quadriert. Auf dem Ergebnis – das wieder eine Liste ist – wird Fold() aufgerufen, dessen Funktion die Listenelemente zu einer Zeichenkette verbindet. Die zweite Ausgabe verwendet Map() zum Verdoppeln der Listenelemente, während Fold() diese dann summiert. Schließlich werden die Initialen eines Namens bestimmt. Dazu wird ein Feld von Zeichenketten definiert sowie für Map() eine Funktion zur Umwandlung jedes Namensteils in Großbuchstaben und für Fold() eine Funktion zum Anfügen des ersten Buchstabens eines Namensteils.

Indem Sie die an Map() und Fold() übergebenen Funktionen ändern, können Sie also das Verhalten beliebig verändern, ohne den Quelltext von Map() oder Fold() antasten zu müssen.


'...\Datentypen\Attribute\Generisch.vb

Option Strict On 
Namespace Datentypen 
  Module Listenoperationen

    <System.Runtime.CompilerServices.Extension()> _ 
    Function Map(Of TArg, TErg)( _ 
      ByVal l As IEnumerable(Of TArg), _ 
      ByVal f As Func(Of TArg, TErg) _ 
    ) As IEnumerable(Of TErg) 
      If l Is Nothing OrElse l.Count() = 0 Then Return New List(Of TErg)() 
      Dim erg As New List(Of TErg)() 
      erg.Add(f(l.First())) 
      erg.AddRange(Map(l.Skip(1), f)) 
      Return erg 
    End Function

    <System.Runtime.CompilerServices.Extension()> _ 
    Public Function Fold(Of TArg, TErg)( _ 
      ByVal l As IEnumerable(Of TArg), _ 
      ByVal f As Func(Of TErg, TArg, TErg), _ 
      ByVal erg As TErg _ 
    ) As TErg 
      For Each i As TArg In l : erg = f(erg, i) : Next 
      Return erg 
    End Function

  End Module

  Module Generisch 
    Sub Test() 
      Dim z As Integer() = {2, 8, 5, 12} 
      Console.WriteLine( _ 
        z.Map(Function(x) x ^ 2).Fold(Function(s, n) s & " " & n, "")) 
      Console.WriteLine( _ 
        z.Map(Function(x) 2 * x).Fold(Function(s, n) s + n, 0))

      Dim name As String() = {"herbert", "maier"} 
      Dim gross As Func(Of String, String) = Function(x) x.ToUpper() 
      Dim initial As Func(Of String, String, String) = _ 
        Function(s, n) s & n.First() 
      Console.WriteLine(name.Map(gross).Fold(initial, "")) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Verschiedenartigkeit der Ausgabe unterstreicht noch einmal die Flexibilität des Konzepts:

4 64 25 144 
54 
HM

Rheinwerk Computing - Zum Seitenanfang

4.8.5 Attribute abfragen Zur nächsten ÜberschriftZur vorigen Überschrift

Die Deklaration eines benutzerdefinierten Attributs hat keine Konsequenzen, außer dass sich die Metadaten eines Datentyps vergrößern. Damit Sie ein selbst definiertes Attribut zum Leben erwecken können, müssen Sie zur Laufzeit an geeigneter Stelle abfragen, ob und wie ein Attribut gesetzt wurde. Dies ist auch der Weg, den die Implementation der Klassen des .NET Frameworks beschreiten muss. Wir greifen im folgenden Beispiel auf vorhandene Attribute zu und befassen uns mit der Definition eines eigenen Attributs und dessen Auswertung im nächsten Abschnitt. Die Schleife gibt alle Attribute für die Assembly, den Datentyp Abfrage und die Methode Test() mittels der Methode GetCustomAttributes() aus. An die Assembly und die Methode kommen wir über Methoden im Namensraum System.Reflection. Danach geben wir den Wert des Modulattributs mit einer anderen Klasse aus, um einige der sonst notwendigen Typumwandlungen zu vermeiden.


'...\Datentypen\Attribute\Abfrage.vb

Option Strict On 
Imports System.Reflection 
Namespace Datentypen 
  <DebuggerDisplay("Modul Abfrage")> Module Abfrage 
    <DebuggerHidden()> Sub Test() 
      Dim typ As Type = GetType(Abfrage) 
      Dim ass As Assembly = typ.Assembly 
      Dim meth As MethodInfo = typ.GetMethod("Test") 
      Dim typen As ICustomAttributeProvider() = {ass, typ, meth}

      For Each ca As ICustomAttributeProvider In _ 
        New ICustomAttributeProvider() {ass, typ, meth} 
        For Each attr As System.Attribute In ca.GetCustomAttributes(True) 
          Console.WriteLine(attr) 
        Next 
        Console.WriteLine() 
      Next

      Console.WriteLine(CustomAttributeData.GetCustomAttributes(typ)(1). _ 
                        ConstructorArguments(0).Value) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die Ausgabe zeigt, dass einige Attribute für die gesamte Assembly sowie für Typen automatisch definiert werden. Die Methode zeigt nur das explizit definierte Attribut.

System.Reflection.AssemblyProductAttribute 
... 
System.Runtime.InteropServices.GuidAttribute 
 
Microsoft.VisualBasic.CompilerServices.StandardModuleAttribute 
System.Diagnostics.DebuggerDisplayAttribute 
 
System.Diagnostics.DebuggerHiddenAttribute 
 
Modul Abfrage

Rheinwerk Computing - Zum Seitenanfang

4.8.6 Benutzerdefinierte Attribute topZur vorigen Überschrift

Sie können selbstverständlich auch eigene Attributklassen definieren. Dabei ist Folgendes zu beachten:

  • Der Name sollte auf Attribute enden.
  • Die Attributklasse muss von System.Attribute abgeleitet sein.
  • Die Attributklasse darf nicht mit MustInherit als abstrakt gekennzeichnet sein, aber die Vererbung sollte meistens mit NotInheritable unterbunden werden.
  • Die Attributklasse darf kein generischer Typ sein oder Teil eines generischen Typs sein.
  • Die Attributklasse sollte mit dem Attribut AttributUsage versehen sein.
  • Der Konstruktor muss ausreichend sichtbar sein (Public oder Friend).
  • Konstruktorparameter sind die Positionsargumente des Attributs und dürfen nicht mit ByRef deklariert werden.
  • Benannte Parameter können Felder oder parameterlose Eigenschaften sein (wenn der Bezeichner ein Schlüsselwort ist, muss er in der Klassendefinition wie üblich in eckige Klammern gesetzt werden).
  • Benannte Parameter müssen mit Public als öffentlich gekennzeichnet sein und dürfen weder mit ReadOnly schreibgeschützt noch mit Shared klassengebunden sein.
  • Benannte Parameter dürfen nur Zahlen mit Ausnahme von Decimal sein (Enumerationen sind also zulässig) oder den Typ Boolean, Char, String, Object oder Type haben (siehe Abschnitt 2.5.4, »Einfache Datentypen«); außerdem sind eindimensionale Arrays dieser Arten erlaubt.

Das Attribut AttributUsage legt das neue Attribut genauer fest. Es hat drei Parameter, die in Tabelle 4.3 aufgelistet sind.


Tabelle 4.3 Parameter der Klasse »AttributeUsageAttribute«

Parameter Typ Beschreibung Art

AllowMultiple

Boolean

Die mehrfache Anwendung des Attributs, ggf. mit verschiedenen Parametern, sichert es mehrfach in den Metadaten.

optional benannt

Inherited

Boolean

Das Attribut wird an Kindklassen weitergegeben.

optional benannt

ValidOn

AttributeTargets

Erlaubte Elemente für das Attribut, zum Beispiel Methode, Klasse

Pflicht ReadOnly


ValidOn ist zwingend und legt fest, auf welche Elemente das benutzerdefinierte Attribut angewendet werden darf (siehe Tabelle 4.4). Ein Module wird durch die Kompilierung einer einzelnen Datei erzeugt, eine Assembly fasst als Bibliothek oder ausführbare Datei diese zusammen. Der Datentyp Module wird vom Wert Class erfasst. Sollen verschiedene Stellen erlaubt sein, werden die Werte mit dem Operator Or verknüpft.


Tabelle 4.4 Werte der Eigenschaft in »AttributeTargets«

Auf was darf das Attribut wirken? Werte

Alles

All

Dateien

Assembly, Module

Datentyp

Class, Struct, Enum, Interface, Delegate

Klassenelement

Constructor, Method, Property, Field, Event

Parameter

Parameter, GenericParameter

Rückgabewert

ReturnValue


Das nächste Beispiel nutzt Attribute, um die Preisgabe von Informationen zu steuern. Dazu wird eine Klasse Arbeiter mit den Feldern Name und Gehalt definiert, die eine Methode Info() enthält, die abhängig von den Attributen der aufrufenden Methode das Gehalt ausgibt oder nicht. Die Methode, die Info() aufruft, wird über ein Objekt der Klasse StackTrace ermittelt, und dessen Attribute werden in einer For Each-Schleife durchlaufen. Wenn das Feld Art des Attributs gleich "Berater" ist, wird neben dem Namen auch das Gehalt ausgegeben. Das Attribut Funktion ist von Attribute abgeleitet und wird durch das Attribut AttributeUsage auf Methoden beschränkt. Es definiert einen erforderlichen Parameter namens Art im Konstruktor und einen optionalen im Feld Name. Danach werden zwei Klassen mit Methoden definiert, die die Methode Arbeiter.Info() aufrufen. Nur die Methode Klient() der Klasse Steuerberater hat dabei ein Funktion-Attribut, das die Ausgabe des Gehalts erlaubt. In der Methode Test() werden schließlich die drei Methoden der beiden Klassen aufgerufen.


'...\Datentypen\Attribute\Benutzer.vb

Option Strict On 
Imports System.Reflection 
Namespace Datentypen

  Class Arbeiter 
    Private Name As String 
    Private Gehalt As Double 
    Sub New(ByVal name As String, ByVal gehalt As Double) 
      Me.Name = name : Me.Gehalt = gehalt 
    End Sub

    Sub Info() 
      Dim frager As MethodBase = (New StackTrace()).GetFrame(1).GetMethod() 
      Dim meth As Object() = frager.GetCustomAttributes(True) 
      Dim cls As Object() = frager.DeclaringType.GetCustomAttributes(True) 
      For Each att As Object In meth.Concat(cls) 
        If att.GetType() IsNot GetType(FunktionAttribute) Then Continue For 
        Dim fun As FunktionAttribute = CType(att, FunktionAttribute) 
        If fun.Art = "Berater" Then 
          Console.WriteLine("{0}: {1} verdient {2}", fun.Name, Name, Gehalt) 
        Else 
          Console.WriteLine("{0}: Arbeiter heißt {1}", fun.Name, Name) 
        End If 
        Return 
      Next 
      Console.WriteLine("Arbeiter heißt {0}", Name) 
    End Sub 
  End Class

  <AttributeUsage(AttributeTargets. Method Or AttributeTargets.Class)> _ 
  Class FunktionAttribute : Inherits System.Attribute 
    Sub New(ByVal art As String) 
      Me.Art = art 
    End Sub 
    Public ReadOnly Art As String 
    Public Name As String    'keine Property, um das Beispiel kurz zu halten 
  End Class

  Class Nachbar 
    <Funktion("Bekannter", Name:="Danton")> Sub Frage(ByVal an As Arbeiter) 
      Console.Write("Nachbar ") : an.Info() 
    End Sub 
  End Class

  <Funktion("Berater", Name:="Gouge")> Class Freund 
    Sub Frage(ByVal an As Arbeiter) 
      Console.Write("Freund ") : an.Info() 
    End Sub 
  End Class

  Class Steuerberater 
    <Funktion("Berater", Name:="Necker")> Sub Klient(ByVal an As Arbeiter) 
      Console.Write("Steuerberater ") : an.Info() 
    End Sub 
    Sub Fremder(ByVal an As Arbeiter) 
      Console.Write("Steuerberater: ") : an.Info() 
    End Sub 
  End Class

  Module Benutzer 
    Sub Test() 
      Dim nb As New Nachbar(), fr As New Freund(), sb As New Steuerberater() 
      Dim ar As New Arbeiter("Peroux", 19750) 
      nb.Frage(ar) 
      fr.Frage(ar) 
      sb.Klient(ar) 
      sb.Fremder(ar) 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Nur der Steuerberater und der Freund haben die richtigen Werte im Attribut Funktion, um das Gehalt auszugeben:

Nachbar Danton: Arbeiter heißt Peroux 
Freund Gouge: Peroux verdient 19750 
Steuerberater Necker: Peroux verdient 19750 
Steuerberater: Arbeiter heißt Peroux

Mehrfachanwendung

Als Beispiel für Mehrfachanwendung definieren wir ein Attribut namens DateiAttribute zur Speicherung von Informationen über die Quelltextdatei. Um das Beispiel klein zu halten, befinden sich die Attributdefinition und -verwendung in derselben Datei. Die Spezifikation der Information sollte nur auf Dateiebene erfolgen, und daher legen wir als Ziel Module fest. Die Mehrfachanwendung erlauben wir durch AllowMultiple:=True. Bei seiner Verwendung in der dritten und vierten Zeile, die bei datei- und anwendungsweiten Attributen immer vor den Klassendefinitionen erfolgen muss, geht dem Attribut Module: voraus, um es an die Datei zu binden.


'...\Datentypen\Attribute\Quelle.vb

Option Strict On 
Imports System.Reflection 
<Module: Attribute.Datentypen.Datei("Quelle.vb"), _ 
  Module: Attribute.Datentypen.Datei("Stephan Leibbrandt")>

Namespace Datentypen

  <AttributeUsage(AttributeTargets.Module, AllowMultiple:=True)> _ 
  Public Class DateiAttribute : Inherits System.Attribute 
    Sub New(ByVal info As String) 
      Me.Info = info 
    End Sub 
    Public ReadOnly Info As String 
  End Class

  Module Quelle 
    Sub Test() 
      Dim datei As [Module] = GetType(Quelle).Module 
      For Each att As System.Attribute In datei.GetCustomAttributes(True) 
        If att.GetType() Is GetType(DateiAttribute) Then 
          Console.Write(CType(att, DateiAttribute).Info & " : ") 
        End If 
      Next 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Die For Each-Schleife in der Methode Test() erfasst alle Dateiattribute. Sie sollten sich nicht auf die Reihenfolge verlassen.

Stephan Leibbrandt : Quelle.vb :

Vererbung

Attribute können Klassenhierarchien bilden wie ganz normale Klassen. Bei der Verwendung bestimmt der Parameter Inherited des Attributs AttributeUsage, ob eine Kindklasse das deklarierte Attribut übernimmt. Die Weitergabe ist unabhängig davon, ob das Attribut mehrfach angegeben werden darf. Wenn ein Attribut nur einfach angegeben werden darf, überschreibt eine Spezifikation in der Kindklasse die in der Elternklasse vollständig. Schnittstellen, Eigenschaften und Ereignisse geben auch mit Inherited:=True Attribute nicht weiter. Die Methoden innerhalb von Eigenschaften und Ereignissen wiederum verhalten sich ganz normal. Das folgende Codefragment testet diese Aussage.


'...\Datentypen\Attribute\Vererbung.vb

Option Strict On 
Imports System.Reflection 
Namespace Datentypen

  <AttributeUsage(AttributeTargets.All, Inherited:=True)> _ 
  Class Farbe : Inherits System.Attribute : End Class

  <Farbe()> Interface Graphik : End Interface 
  Interface Zeichnung : Inherits Graphik : End Interface 
  <Farbe()> Class Ellipse : Implements Zeichnung 
    <Farbe()> Overridable ReadOnly Property Art() As String 
      <Farbe()> Get 
        Return ("Ellipse") 
      End Get 
    End Property 
  End Class

  Class Kreis : Inherits Ellipse 
    Overrides ReadOnly Property Art() As String 
      Get 
        Return ("Kreis") 
      End Get 
    End Property 
  End Class

  Module Vererbung 
    Sub Ausgabe(ByVal c As ICustomAttributeProvider, ByVal s As String) 
      For Each a As Object In c.GetCustomAttributes(True) 
        If a.GetType() Is GetType(Farbe) Then Console.Write(s & " farbig ") 
      Next 
    End Sub

    Sub Test() 
      Ausgabe(GetType(Zeichnung), "Schnittstelle") 
      Ausgabe(GetType(Kreis), "Klasse") 
      Ausgabe(GetType(Kreis).GetProperty("Art"), "Eigenschaft") 
      Ausgabe(GetType(Kreis).GetProperty("Art").GetGetMethod(), "Get") 
      Console.ReadLine() 
    End Sub 
  End Module 
End Namespace

Wie erwartet geben Eigenschaften und Schnittstellen keine Attribute weiter:

Klasse farbig Get farbig



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual Basic 2008
Visual Basic 2008
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Visual Basic 2012






 Visual Basic 2012


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de