Detached Locals
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.