Home > Artikel > Ausgabe 1/2015 > Die VBA-With-Anweisung

Die VBA-With-Anweisung

Achtung: Sie sind nicht angemeldet. Wenn Sie Abonnent sind und sich anmelden, lesen Sie den kompletten Artikel, laden das PDF herunter oder probieren die Beispieldatenbank aus (sofern vorhanden).

Unter den Elementen, die den Programm-Code unter VBA strukturierter erscheinen lassen, findet die With-Anweisung oft wenig Berücksichtigung. Dabei spart man mit ihr nicht nur Programmtext ein, sondern erleichtert sich über IntelliSense zusätzlich das Schreiben von Objektmethoden.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1501_WithAnweisung.mdb.

Grundlegende Syntax

Die With-Anweisung stellt einen Bezug zu einer Variablen her, der entweder ein benutzerdefinierter Datentyp (UDT) zugrunde liegt, oder ein Objekt. Ob diese in ihren eigenen Modulen deklariert sind, oder in einer Verweisbibliothek, spielt dabei keine Rolle. Die Blockanweisung besteht aus zwei Elementen, dem Blockstart und dem Blockende:

Dim Variable As ...

With [Variable] 'Blockstart

.[Methode oder]

.[Eigenschaft] = ...

End With 'Blockende

Nach dem reservierten Wort With folgt der Name einer Variablen, auf die sich die Methoden, Eigenschaften oder Typelemente im Folgenden beziehen sollen.

Wird in einer Prozedur die Variable nicht mehr weiter benötigt, so schließt man den Bezug mit der Anweisung End With.

With im Einsatz

Einen Code aus dem wahren Datenbankprogrammiererleben finden Sie im Listing 1 der Beispieldatenbank, wo auf eine Tabelle tblTest eine Datensatzgruppe (Recordset) geöffnet wird, um sie mit Datensätzen zu füllen und jene anschließend im VBA-Direktfenster auszugeben. In der ersten Zeile der Routine wird die Tabelle zunächst mit einer SQL-Anweisung per Database.Execute geleert. Dann erfolgt das Setzen des Recordsets rsTest auf die Tabelle. Die Recordset-Variable rsTest wurde übrigens bereits im Modulkopf deklariert, ebenso die Zählervariable i:

Sub OhneWITH()

     CurrentDb.Execute "DELETE * FROM tblTest"

     Set rsTest = CurrentDb.OpenRecordset("SELECT * FROM tblTest", dbOpenDynaset)

     For i = 1 To 100

         rsTest.AddNew

         rsTest!ID = i

         rsTest!Code = Chr$(65 + 28 * Rnd) & Chr$(65 + 28 * Rnd) & Chr$(65 + 28 * Rnd)

         rsTest.Update

     Next i

     

     rsTest.MoveFirst

     Do While Not rsTest.EOF

         Debug.Print rsTest!ID, rsTest!Code

         rsTest.MoveNext

     Loop

     rsTest.Close

End Sub

Listing 1: Durchlaufen der Datensätze einer Tabelle

Private rsTest As DAO.Recordset

Private i As Long

Im weiteren Verlauf werden in einer Schleife auf den Zähler i der Tabelle hundert Datensätze hinzugefügt, wobei für das Feld Code jeweils ein dreistelliger zufälliger Buchstabencode generiert wird. Zum Schluss werden alle Datensatzinhalte wieder in einer Schleife über Debug.Print in das VBA-Direktfenster ausgegeben. Das Ergebnis wäre etwa dieses:

  1 J\Q

  2 PEZ

  3 BWU

  4 H[N

  5 ULG

...

Auffallend ist, dass der Variablenname rsTest sehr häufig in der Routine auftaucht. Genau dies lässt sich mit der With-Anweisung umgehen, indem zum Blockstart der Verweis auf rsTest gesetzt wird und im Folgenden darauf nur noch indirekt Bezug genommen wird. Der abgewandelte Code steht in Listing 2.

Sub MitWITH()

     CurrentDb.Execute "DELETE * FROM tblTest"

     Set rsTest = CurrentDb.OpenRecordset("SELECT * FROM tblTest", dbOpenDynaset)

     With rsTest

         For i = 1 To 100

             .AddNew

             !ID = i

             !Code = Chr$(65 + 28 * Rnd) & Chr$(65 + 28 * Rnd) & Chr$(65 + 28 * Rnd)

             .Update

         Next i

         .MoveFirst

         Do While Not .EOF

             Debug.Print !ID, !Code

             .MoveNext

         Loop

         .Close

     End With

End Sub

Listing 2: Der gleiche Code unter Einsatz der With-Anweisung

Statt rsTest.AddNew zum Anlegen eines neuen Datensatzes wird nun nur noch der Ausdruck .AddNew benötigt – VBA denkt sich vor den Punkt den oben definierten Objektbezug.

Statt des Punktes ist auch der VBA-Ausrufezeichen-Operator für Auflistungen erlaubt. Deshalb lässt sich nun ein Feld des Recordsets statt über rsTest!Code einfach mit !Code ansprechen.

Kürzer wird die Routine damit nicht. Sie ist im Gegenteil wegen der With-Anweisungen sogar um zwei Zeilen länger. Der Zeichenumfang ist jedoch geringer. Das spielt allerdings weniger eine Rolle als die nun erhöhte Übersichtlichkeit des Codes. Die Methoden, Eigenschaften und Feldbezeichnungen des Recordsets treten nun optisch deutlicher zutage, weil sie isolierter dastehen.

Aber nicht nur die Gestalt des Programmcodes ändert sich damit, sondern auch die Art, wie VBA das Modul kompiliert. Denn bei jedem Ansprechen der Variablen rsTest in der ersten Variante sucht es sich gesondert einen Zeiger auf die Objektvariable, um dann deren Methoden auszuführen. Bei With-Blöcken wird intern nur ein Zeiger auf die Objektvariable angelegt und dann für die Methodenaufrufe mehrfach verwendet. Das führt zu einer geringfügig besseren Ausführungsgeschwindigkeit der Routine. Ehemals wurde die With-Anweisung deshalb auch in Sammlungen zu Performance-Tipps aufgenommen, spielt aber in dieser Hinsicht heute keine nennenswerte Rolle mehr – ein paar Nanosekunden mehr oder weniger sind unerheblich.

Objektvariablen überflüssig machen

Die Variante in Listing 1 benötigt für das Ansprechen der Recordset-Funktionen die Objektvariable rsTest. Durch die With-Anweisung können Sie darauf aber auch verzichten, und damit tatsächlich Zeilen einsparen, wie Listing 3 zeigt. Hier wird in der zweiten Zeile die With-Anweisung statt mit einer Variablen mit einem Ausdruck bestückt, dem Öffnen des Recordsets über OpenRecordset. Da das Ergebnis dieses Aufrufs einer Variablen gleichkommt, funktioniert dieser Bezug ebenfalls. Die Performance ist sogar nochmals um Micro-Prozent höher. Ob der Verzicht auf die Objektvariable jedoch der Code-Verständlichkeit zugutekommt, ist hingegen eher fraglich.

Sub MitWITHKurz()

     CurrentDb.Execute "DELETE * FROM tblTest"

     With CurrentDb.OpenRecordset( _

         "SELECT * FROM tblTest", dbOpenDynaset)

         For i = 1 To 100

             .AddNew

             !ID = i

             !Code = Chr$(65 + 28 * Rnd) & Chr$(65 + 28 * Rnd) & Chr$(65 + 28 * Rnd)

             .Update

         Next i

         .MoveFirst

         Do While Not .EOF

             Debug.Print !ID, !Code

             .MoveNext

         Loop

         .Close

     End With

End Sub

Listing 3: Gekürzte Fassung ohne Recordset-Variable

With verschachteln

Es lassen sich With-Blöcke auch ineinander einfügen. Im Prinzip sieht das dann so aus:

With [Var1] 'Blockstart 1

   .[Methoden auf Var1]

   With [Var2] 'Blockstart 2

     .[Methoden auf Var2]

End With 'Blockende 2

.[Methoden auf Var1]

End With 'Blockende 1

Der Punkt-Operator von Objektmethoden wirkt hier immer nur auf den zuvor definierten With-Block. Ein End With setzt den Objektbezug wieder auf den äußeren Block, im Beispiel also auf Var1. Vermischen lassen sich beide nicht.

Für das vorangegangene Beispiel lässt sich eine solche Verschachtelung realisieren. Um den Bezug auf das Recordset rsTest wird in Listing 4 noch ein Bezug auf das Database-Objekt (CurrentDb) gebaut. Die OpenRecordset-Methode etwa bezieht sich dann auf CurrentDb, das AddNew auf rsTest. Innerhalb von Block 2 wiederum kann kein Bezug zu CurrentDb hergestellt werden.

Sub MitDoppelWITH()

     With CurrentDb 'Block 1

Sie haben das Ende des frei verfügbaren Teil dieses Artikels erreicht!

Wenn Sie mehr lesen und auf viele weitere Artikel zugreifen möchten, melden Sie sich als Abonnent unter Login an. Falls nicht, bestellen Sie doch einfach ein Jahresabonnement!