Code zur Programmiertechnik in .NET

Ermitteln von Zeilennummern und Methodennamen

Bereits in früheren Versionen von Visual Basic konnte die Nummer der Zeile, an der ein Laufzeitfehler aufgetreten ist, mit der Funktion Erl bestimmt werden. Diese Funktion ist nun eine Methode der Klasse Err und kann weiterhin eingesetzt werden. Die Zeilennummern, die Err.Erl zurückgibt, müssen manuell im Code angegeben werden:

Private Sub Foo()
0:  On Error GoTo ErrHandler
1:  Dim i As Short
2:  i = Integer.MaxValue
3:  i = i * 2
    Return
ErrHandler:
    MsgBox( _
        "Fehlernummer:  " & CStr(Err.Number) & ControlChars.NewLine & _
        "Zeile:  " & CStr(Err.Erl) & ControlChars.NewLine & _
        "Beschreibung:  " & Err.Description
    )
End Sub
Ermitteln von Fehlernummer, Zeilennummer und Beschreibung eines Laufzeitfehlers mit dem Err-Objekt.

Mittels MethodBase.GetCurrentMethod().Name kann der Name der gerade ausgeführten Methode ermittelt werden. Eine weitere Möglichkeit hierzu besteht in der Verwendung der Klasse StackTrace, mit deren Hilfe auch der Name der aufrufenden Methode und die aktuelle Zeilennummer bestimmt werden können. Damit die Zeilennummer ermittelt werden kann, müssen Debugsymbole (PDB-Dateien) vorliegen. Um das nachstehende Beispiel kompilieren zu können, muß der Namensraum System.Diagnostics importiert werden:

Dim CurrentStack As New StackTrace()
MsgBox("Current method:  " & CurrentStack.GetFrame(0).GetMethod().Name)
MsgBox("Calling method:  " & CurrentStack.GetFrame(1).GetMethod().Name)
MsgBox("Line number:  " & CStr(CurrentStack.GetFrame(0).GetFileLineNumber()))
Ermitteln von Zeilennummer und Methodenname aus einem StackTrace-Objekt.

Verzweigungen beliebigen Typs beim Select Case-Block

Der Select Case-Block von Visual Basic .NET ist eingeschränkt, was den Typ des Verzweigungsausdrucks anbelangt. So ist es nicht möglich, anhand eines Verweises zu verzweigen. Betrachten wir im Folgenden das Beispiel der Auswertung, welche Schaltfläche eines ToolBar-Steuerelements angeklickt wurde. In diesem Fall läßt Visual Basic .NET Select Case e.Button nicht zu. Will man trotzdem nicht auf die gute Lesbarkeit von Select Case verzichten, so kann man als Ausdruck, nach dem verzweigt wird, True angeben und die Verzweigungsbedingungen explizit formulieren:

Private Sub ToolBar1_ButtonClick( _
    ByVal sender As Object, _
    ByVal e As ToolBarButtonClickEventArgs _
) Handles ToolBar1.ButtonClick
    Select Case True
        Case e.Button Is Me.ToolBarButton1
            MsgBox("Schaltfläche 1 wurde gewählt!")
        Case e.Button Is Me.ToolBarButton2
            MsgBox("Schaltfläche 2 wurde gewählt!")
        ⋮
    End Select
End Sub
Beliebige Verzweigungen bei einem Select Case-Block.

Methodenaufruf bei Objektinstanzierung

In C# und einigen anderen Programmiersprachen besteht die Möglichkeit, in einer Anweisung ein Objekt zu instanzieren und eine seiner Methoden auszuführen. Im Falle von C# wird dazu einfach die Anweisung new SampleClass("George").DoSomething(3) benötigt. Will man diese Zeile direkt in Visual Basic .NET umsetzen, führt dies zu einem Kompilierungsfehler. Trotzdem gibt es eine interessante Lösung:

Call (New SampleClass("George")).DoSomething(3)
Aufruf einer Methode bei der Instanzierung.

Wie dem obenstehenden Listing zu entnehmen ist, machen wir hier Gebrauch vom Schlüsselwort Call, das dazu verwendet werden kann, eine Methode aufzurufen. Call ist also keinesfalls nur aus Gründen der Codekompatibilität weiterhin vorhanden, sondern macht gewisse Dinge überhaupt erst möglich.

Bestimmen der Anzahl der für ein Ereignis registrierten Ereignisbehandlungsprozeduren

Folgender Code resultierte auf eine Anfrage in einer Newsgroup, bei der es darum ging, festzustellen, wie viele Ereignisbehandlungsprozeduren für ein bestimmtes Ereignis registriert sind:

Public Module Program
    Public Sub Main()
        Dim c As New FooBar()
        AddHandler c.Foo, AddressOf Goo
        c.AddSampleHandler()
        c.AddSampleHandler()
        Console.WriteLine( _
            "Anzahl der Handler für Foo: {0}", _
            c.NumberOfFooHandlers _
        )
        RemoveHandler c.Foo, AddressOf Goo
        Console.Read()
    End Sub

    Private Sub Goo()
    End Sub
End Module

Public Class FooBar
    Public Event Foo()

    Public ReadOnly Property NumberOfFooHandlers() As Integer
        Get
            If FooEvent Is Nothing Then
                Return 0
            Else
                Return FooEvent.GetInvocationList().Length
            End If
        End Get
    End Property

    Public Sub AddSampleHandler()
        AddHandler Foo, AddressOf Moo
    End Sub

    Private Sub Moo()
    End Sub
End Class
Anzahl der Handler eines Ereignisses ermitteln.

Ein Ereignis ist nichts anderes als eine Maske für einen Delegaten: Wenn ein Ereignis deklariert wird, wird ein versteckter Delegattyp erstellt, der Methoden mit der gleichen Signatur wie jene des Ereignisses aufnehmen kann. Weiters wird eine versteckte Variable dieses Typs erstellt, deren Name sich aus dem Namen des Ereignisses mit angehängtem »Event« zusammensetzt. Über diese Variable, in unserem Fall FooEvent kann eine Liste der Delegate, die dazu hinzugefügt wurden, ermittelt werden.

Behandeln gemeinsamer Ereignisse

Folgender einfacher Code zeigt, wie man gemeinsame Ereignisse behandeln kann. Gemeinsame Ereignisse werden in Visual Basic .NET mit dem Modifizierer Shared markiert. Gemeinsame Ereignisse sind beispielsweise dann sinnvoll, wenn das Instanzieren von neuen Objekten der Klasse bzw. deren Operationen ein Ereignis auslösen sollen:

Public Module Program
    Public Sub Main()
        AddHandler SampleClass.SampleEvent, AddressOf SampleClass_SampleEvent
        SampleClass.RaiseSampleEvent()
        RemoveHandler SampleClass.SampleEvent, AddressOf SampleClass_SampleEvent
        Console.Read()
    End Sub

    Private Sub SampleClass_SampleEvent()
        Console.WriteLine("SampleEvent occured!")
    End Sub
End Module

Public Class SampleClass
    Public Shared Event SampleEvent()

    Public Shared Sub RaiseSampleEvent()
        RaiseEvent SampleEvent()
    End Sub
End Class
Behandeln eines gemeinsamen Ereignisses.

Instanzieren anhand des Klassennamens

Es kann vorkommen, daß man ein Formular oder eine beliebige andere Klasse instanzieren will, allerdings lediglich über ihren Namen in Form eines Strings verfügt. In diesem Fall kann die Klasse Activator mit ihrer Methode CreateInstance Abhilfe schaffen. Nehmen wir an, der Namensraum der Anwendung sei MyApplication und der Name der Formularklasse SampleForm; dann kann ein Formular der Klasse SampleForm folgendermaßen instanziert werden:

Dim frm As Form = _
    DirectCast( _
        Activator.CreateInstance( _
            Type.GetType("MyApplication.SampleForm") _
        ), _
        Form _
    )
frm.Show()
Instanzieren einer Formularklasse anhand ihres Klassennamens.

Oben angegebener Code funktioniert allerdings nur, wenn sich die Klasse in der Assembly befindet, die gerade ausgeführt wird. Soll eine Klasse aus einer anderen Assembly geladen werden, dann kann die Methode Activator.CreateInstance(String, String) benutzt werden, der man den vollständigen Namen der Assembly und den qualifizierten Typnamen übergibt. Ist der vollständige Name der Assembly nicht bekannt, kann folgende Funktion verwendet werden:

Private Function CreateObject( _
    ByVal AssemblyName As String, _
    ByVal TypeName As String _
) As Object
    For Each asm As [Assembly] In AppDomain.CurrentDomain.GetAssemblies()
        If asm.FullName.StartsWith(AssemblyName & ",") Then
            Return asm.CreateInstance(TypeName)
        End If
    Next asm
End Function
Instanzieren einer Klasse aus einer beliebigen geladenen Assembly andhand ihres Namens.

Damit obenstehender Code funktioniert, muß der Namensraum System.Reflection importiert werden. Folgendes Listing zeigt ein Beispiel für die Verwendung der Funktion CreateObject, in dem ein Button-Steuerelement instanziert und einem Formular hinzugefügt wird:

Dim c As Control = _
    DirectCast( _
        CreateObject( _
            "System.Windows.Forms", _
            "System.Windows.Forms.Button" _
        ), _
        Control _
    )
With c
    .Location = New Point(10, 10)
    .Size = New Size(80, 26)
    .Text = "Hello World"
End With
Me.Controls.Add(c)
Beispiel zur Verwendung der Funktion CreateObject.

Bedingte Vererbung

Bedingte Kompilierung ist nichts Außergewöhnliches, trotzdem wird oft vergessen, was damit möglich ist. Beispielsweise kann man dadurch ohne großen Aufwand zwischen unterschiedlichen Implementierungen der Basisklasse einer Klasse wechseln. Das erweist sich bei der Entwicklung von Formularen als nützlich. Die Windows-Forms-Entwurfsumgebung kann nämlich nicht mit Formularen umgehen, die von einer abstrakten Basisklasse erben. In diesem Falle könnte man den Kopf einer Klassendefinition mit und ohne MustInherit in entsprechende Zweige eines #If…#Then… packen:

Public Class Foo
#If DEBUG Then
    Inherits DebugFooBase
#Else
    Inherits FooBase
#End If

    ⋮
End Class
Bedingte Vererbung mit Hilfe der Konstante DEBUG.