Detached Locals

Aus Wiki des Deutschsprachige Xbaseentwickler e. V.
Zur Navigation springen Zur Suche springen

Definition von detached LOCAL

Bisher wurde immer festgehalten, dass LOCAL Variablen nur in ihrem Kontext, d.h. in der Funktion, in der sie deklariert werden, sichtbar sind, und dass der Zugriff auf sie in dem Moment endet, wo die Funktion verlassen wird.

Es gibt jedoch die Möglichkeit, eine LOCAL Variable (oder auch mehrere) aus ihrem Kontext zu lösen und auch nach dem Ende der Funktion zu nutzen.

FUNCTION GetTime
   LOCAL bBlock
   LOCAL cTime
   cTime := Time()
   bBlock := {|| cTime}
RETURN(bBlock)

Da die LOCAL Variable cTime Bestandteil des Codeblocks bBlock ist, wird sie durch die RETURN-Anweisung aus ihrem Kontext herausgelöst, "detached". Jedes Mal, wenn bBlock mit Eval() ausgeführt wird, erfolgt ein Zugriff auf die detached LOCAL, die ihren Wert nicht mehr ändern kann.

Anwendungsbeispiel Schleife

oBro := XbpBrowse():new()
...
oBro:create()
nLen := Kunden->(fCount())
FOR nI := 1 TO nLen
   oBro:addColumn(Kunden->(FieldGet(nI), , Kunden->(FieldName(nI))
NEXT

Angenommen, die Datei Kunden enthält die Felder Name, PLZ und Ort, so erwarten wir dass das XbpBrowse() Objekt drei Spalten anzeigen wird mit den Überschriften Name, PLZ und Ort. Unterstellen wir weiterhin, dass die Schleifenvariable nI nicht weiter verwendet wird.

Stattdessen wird das XbpBrowse() Objekt einen Laufzeitfehler verursachen.

In dem XbpBrowse() Objekt gibt es drei Spalteneinträge:

(Kunden->FieldGet(nI)), , Kunden->(FieldName(nI))
(Kunden->FieldGet(nI)), , Kunden->(FieldName(nI))
(Kunden->FieldGet(nI)), , Kunden->(FieldName(nI))

Erwartet hätten wir:

(Kunden->FieldGet(1)), , Kunden->(FieldName(1))
(Kunden->FieldGet(2)), , Kunden->(FieldName(2))
(Kunden->FieldGet(3)), , Kunden->(FieldName(3))

Wir haben jedoch eine Referenz auf die LOCAL Variable nI in der addColumn() Methode verwendet, und daher wurde die Variable auch übernommen.

Nach vier (!) Durchläufe durch die DO - WHILE Schleife endete diese, und nI hat zu diesem Zeitpunkt den Wert 4: die Schleife sollte dreimal durchlaufen werden, und als die Variable nI den Wert 4 erreichte, war Schluss mit Schleife.

Wenn das XbpBrowse() Objekt jetzt versucht, die Spalten aufzubauen, greift es auf die Definition der Überschrift der ersten Spalte zu:

Kunden->(FieldName(nI))

nI hat zu diesem Zeitpunkt den Wert 4, also lautet die Anweisung

Kunden->(FieldName(4))

und führt zu einem Laufzeitfehler, da die Tabelle Kunden nur 3 Felder besitzt. Die Codeblocks, welche die :addColumn() Methode erzeugt hat, verweisen auf den aktuellen Inhalt der Variablen nI, wobei wir eigentlich den Wert zum Zeitpunkt der Definition haben wollten.

Um das zu erreichen, müssen wir nI aus ihrem Kontext lösen (detach):

nLen := Kunden->(fCount())
FOR nI := 1 TO nLen
   BroAdder(nI, oBro)
NEXT
...
FUNCTION BroAdder(nI, oBro)
   oBro:addColumn(Kunden->(FieldGet(nI), , Kunden->(FieldName(nI))
RETURN(.T.)

Was passiert: In der Schleife wird eine Funktion BroAdder() aufgerufen. Als Parameter werden die Schleifenvariable sowie eine Referenz auf das XbpBrowse() Objekt übergeben.

Parameter werden in Xbase++ grundsätzlich per Value übergeben, das bedeutet als Kopie. Beim ersten Aufruf erhält BroAdder() also folgende Werte:

1
XbpBrowse()-Referenz

Wenn wir nun den gleichen Befehl (!) ausführen, befindet sich nI in einem anderen Kontext. Jetzt ist es eine Variable mit einem festgelegten Wert, der durch das detach "festgefroren" wird.

Nach drei Durchläufen hat das Xbpbrowse() Objekte folgende Spaltendefinitionen:

(Kunden->FieldGet(1)), , Kunden->(FieldName(1))
(Kunden->FieldGet(2)), , Kunden->(FieldName(2))
(Kunden->FieldGet(3)), , Kunden->(FieldName(3))

Ohne das detach wäre dies nicht oder nur mit grossem Aufwand möglich gewesen.