User Tools

Site Tools


vba:tutorials:cachedobjectforfunction

[VBA] Cached Object in einer Function

Objekte Cachen, so dass sie nicht jedesmal neu erstellt werden müssen. Sinnvoll bei FSO, RegExp etc.

Ein Klassiker: Wir haben eine selbstgeschriebene Funktion (user definied function) die ein RegExp-Objekt enthält. RegExp-Objekte können sehr schnell Texte parsen. Am schnellsten sind sie aber, wenn nicht jedesmal der Pattern und die Settings neu initialisiert werden müssen. Bei einzelnen Aufrufen ist das nicht relevant. Wenn man die Funktion hingegen in einem Query (Abgfrage) verwendet, hat man schnell den Fall dass das RegExp-Objekt 1000 mail initialisiert und 1000 mal entfernt wird.
Dank der Static-Daklaration und dem Private Property kann man das relativ einfach umgehen.

Funktion ohne Cache

Hier mal eine einfache Funktion in einem Modul

tutorial_cachedObject.bas
Attribute VB_Name = "tutorial_cachedObject"
Option Explicit
 
'/**
' * Extrahiert die ID aus einem String
' * @example: extractId("user=c123,id=34,name=erb")
' * @param  String
' * @return String
' */
Public Function extractId(ByVal iString As String) As Integer
    Dim rx As Object: Set rx = CreateObject("VBScript.RegExp")
    rx.pattern = "id=(\d+)"
    rx.Global = False
    rx.IgnoreCase = True
    rx.MultiLine = False
 
    extractId = CInt(rx.execute(iString)(0).subMatches(0))
End Function

Funktion mit Cache im Funktionscode

Jetzt baue ich mal ein Cache mit der Static Dimensionierung ein. Das ganze ist aber noch mässig lesbar

tutorial_cachedObject.bas
Attribute VB_Name = "tutorial_cachedObject"
Option Explicit
 
'/**
' * Extrahiert die ID aus einem String
' * @example: extractId("user=c123,id=34,name=erb")
' * @param  String
' * @return String
' */
Public Function extractId(ByVal iString As String) As Integer
    Static rx As Object
 
    If rx Is Nothing Then
        Set rx = CreateObject("VBScript.RegExp")
        rx.pattern = "id=(\d+)"
        rx.Global = False
        rx.IgnoreCase = True
        rx.MultiLine = False
    End If
 
    extractId = CInt(rx.execute(iString)(0).subMatches(0))
End Function

Funktion mit Cache als Property

Ich kapsle gerne einzelne Elemente. Darum schreibe ich jetzt das ganze Initializieren des RegExp-Objektes in ein Property.

Natürlich ist es in diesem Beispiel ein wenig übertrieben. Wenn man jedoch komplexere Funktionen hat, verbessert es die Lesbarkeit.

tutorial_cachedObject.bas
Attribute VB_Name = "tutorial_cachedObject"
Option Explicit
 
'/**
' * Extrahiert die ID aus einem String
' * @example: extractId("user=c123,id=34,name=erb")
' * @param  String
' * @return String
' */
Public Function extractId(ByVal iString As String) As Integer
    extractId = CInt(rxId.execute(iString)(0).subMatches(0))
End Function
 
'/**
' * Cache für das RegExp-Object um die ID zu extrahieren
' * @return RegExp
' */
Private Property Get rxId() As Object
    Static rxCached As Object
 
    If rxCached Is Nothing Then
        Set rxCached = CreateObject("VBScript.RegExp")
        rxCached.pattern = "id=(\d+)"
        rxCached.Global = False
        rxCached.IgnoreCase = True
        rxCached.MultiLine = False
    End If
 
    Set rxId = rxCached
End Property

Ein weiterer Vorteil, ich muss mich in meinem eigentlichen Programm nicht darum kümmern, ob ein Objekt bereits initialisiert ist oder nicht.

Komplexeres Beispiel mit Kapselung

Ein Beispiel aus der [VBA][Access] XlsxExporter 2.x. Dort brauche in ein Objekt Excel, ein Workbook und ein Worksheet. Je nach Aufruf der Methoden der Klasse müsste ich an mehreren Stellen prüfen, ob das Excel-Objekt bereits initialisiert ist oder nicht. Und wenn nicht, dass muss ich es erstellen. Mit den Property und Static kümmert sich das Program selber drum

'/**
' * DieExcel-Instance
' */
Private Property Get xlsx() As Excel.application
    Static pXlsx As Excel.application
    If pXlsx Is Nothing Then Set pXlsx = New Excel.application
    Set xlsx = pXlsx
End Property

Dasselbe noch fürs Workbook, das wiederum auf auf dem obigem Property basiert

'/**
' * Das Workbook mitden Exportierten Daten
' */
Private Property Get wb() As Excel.Workbook
    Static pWb As Excel.Workbook
    If pWb Is Nothing Then Set pWb = xlsx.Workbooks.add
    Set wb = pWb
End Property

Und für das Worksheet FORMATS. Falls das noch nicht existiert, wird es sofort angelegt

'/**
' * Temporäres Sheet um die Formate zu speichern
' * Erste Zeile: Spalte(DAO.DataTypeEnum) beinhaltet das entsprechende Format
' */
Private Property Get wsFomats() As Excel.Worksheet
    Static ws As Excel.Worksheet
On Error GoTo Err_Handler
    If ws Is Nothing Then Set ws = wb.Worksheets(C_FORMAT_SHEET_NAME)
 
Exit_Handler:
    Set wsFomats = ws
    Exit Property
 
Err_Handler:
    If Err.Number = 9 Then
        Set ws = wb.Worksheets.add(): ws.Name = C_FORMAT_SHEET_NAME
        Resume Exit_Handler
    Else
        Err.Raise Err.Number, Err.source, Err.Description, Err.HelpFile, Err.helpContext
    End If
End Property

In meinem eigentlichen Code kann ich gemütlich auf wsFomats zugreifen ohne mich darum zu kümmern, ob es das schon gibt oder nicht. Hier zum Beispiel werden Daten aus einer an vor das Format-Sheet kopiert

    'Temporäres Workbook/sheet öffnen
    Dim wbTemp As Excel.Workbook:   Set wbTemp = xlsx.Workbooks.Open(tempExportFilePath)
    Dim wsTemp As Excel.Worksheet:  Set wsTemp = wbTemp.Worksheets(source)
    'Export in das aktuelle Worksheet kopieren
    wsTemp.Copy before:=wsFomats
    'Temporäres WOrkbook schliessen/löschen
    wbTemp.Close

Weitere Anwendung

In vielen meiner Funktionen kommt dies zur Anwendung. Hier einige Beispiele:

vba/tutorials/cachedobjectforfunction.txt · Last modified: 12.09.2019 08:17:36 by yaslaw