Ein Objekt ist eine Kombination aus Eigenschaften und Methoden.
Verwandte Themen:
Mit IsObject kann festgestellt werden, ob ein Wert ein Objekt ist:
Ergebnis := IsObject(Ausdruck)
Eine Liste von Standardobjekttypen finden Sie unter Interne Klassen. Es gibt zwei Grundtypen:
Erstellen eines Arrays:
MeinArray := [Element1, Element2, ..., ElementN] MeinArray := Array(Element1, Element2, ..., ElementN)
Abrufen eines Elements:
Wert := MeinArray[Index]
Ändern eines Elementwertes (Index
muss zwischen 1 und Länge liegen oder ein äquivalenter umgekehrter Index sein):
MeinArray[Index] := Wert
Einfügen eines oder mehrerer Elemente ab einem bestimmten Index via InsertAt-Methode:
MeinArray.InsertAt(Index, Wert, Wert2, ...)
Anfügen eines oder mehrerer Elemente via Push-Methode:
MeinArray.Push(Wert, Wert2, ...)
Entfernen eines Elements via RemoveAt-Methode:
EntfernterWert := MeinArray.RemoveAt(Index)
Entfernen eines Elements via Pop-Methode:
EntfernterWert := MeinArray.Pop()
Length gibt die Anzahl der Elemente im Array zurück. Um die Elemente eines Arrays einzeln durchzugehen, verwenden Sie entweder A_Index oder eine For-Schleife. Zum Beispiel:
MeinArray := ["eins", "zwei", "drei"] ; Von 1 bis zum Ende des Arrays iterieren: Loop MeinArray.Length MsgBox MeinArray[A_Index] ; Den Inhalt des Arrays enumerieren: For Index, Wert in MeinArray MsgBox "Element " Index " ist '" Wert "'" ; Dasselbe noch einmal: For Wert in MeinArray MsgBox "Element " A_Index " ist '" Wert "'"
Ein Map oder assoziatives Array ist ein Objekt, das eine Sammlung von eindeutigen Schlüsseln und eine Sammlung von Werten enthält, wobei jeder Schlüssel mit einem Wert verbunden ist. Schlüssel können Zeichenketten, Integer oder Objekte sein, während Werte von beliebigem Typ sein können. Ein assoziatives Array kann wie folgt erstellt werden:
MeinMap := Map("SchlüsselA", WertA, "SchlüsselB", WertB, ..., "SchlüsselZ", WertZ)
Abrufen eines Elements, wobei Schlüssel
eine Variable oder ein Ausdruck ist:
Wert := MeinMap[Schlüssel]
Zuweisen eines Elements:
MeinMap[Schlüssel] := Wert
Entfernen eines Elements via Delete-Methode:
EntfernterWert := MeinMap.Delete(Schlüssel)
Enumerieren von Elementen:
MeinMap := Map("zehn", 10, "zwanzig", 20, "dreißig", 30) For Schlüssel, Wert in MeinMap MsgBox Schlüssel ' = ' Wert
Ein Objekt kann Eigenschaften und Elemente (z.B. Array-Elemente) haben. Der Zugriff auf Elemente erfolgt über []
, wie in den vorherigen Abschnitten gezeigt. Der Zugriff auf Eigenschaften erfolgt durch Anfügen eines Punktes und einem Identifikator (einfach ein Name). Methoden sind Eigenschaften, die aufgerufen werden können.
Beispiele:
Abrufen oder Setzen einer Eigenschaft mit dem direkt geschriebenen Namen Eigenschaft:
Wert := MeinObjekt.Eigenschaft
MeinObjekt.Eigenschaft := Wert
Abrufen oder Setzen einer Eigenschaft mit einem via Ausdruck oder Variable ermittelten Namen:
Wert := MeinObjekt.%Ausdruck%
MeinObjekt.%Ausdruck% := Wert
Aufrufen einer Eigenschaft/Methode mit dem direkt geschriebenen Namen Methode:
RückgabeWert := MeinObjekt.Methode(Params)
Aufrufen einer Eigenschaft/Methode mit einem via Ausdruck oder Variable ermittelten Namen:
RückgabeWert := MeinObjekt.%Ausdruck%(Params)
Manchmal werden beim Abrufen oder Zuweisen von Eigenschaften Parameter akzeptiert:
Wert := MeinObjekt.Eigenschaft[Params] MeinObjekt.Eigenschaft[Params] := Wert
Ein Objekt kann auch Indexierung unterstützen: MeinArray[Index]
ruft indirekt die __Item-Eigenschaft von MeinArray
auf und übergibt Index
als Parameter.
Ein Objektliteral (direkt geschriebenes Objekt) kann in einem Ausdruck verwendet werden, um ein improvisiertes Objekt zu erstellen. Ein Objektliteral besteht aus zwei geschweiften Klammern ({}
), die eine Liste von kommagetrennten Name-Wert-Paaren umschließen. Jedes Paar besteht aus einem direkt geschriebenen (anführungszeichenlosen) Eigenschaftsnamen und einem Wert (Teilausdruck), die mit einem Doppelpunkt (:
) voneinander getrennt sind. Zum Beispiel:
Koord := {X: 13, Y: 240}
Folgendes ist äquivalent:
Koord := Object() Koord.X := 13 Koord.Y := 240
Jedes Name-Wert-Paar definiert eine Werteigenschaft, mit der Ausnahme, dass Base gesetzt werden kann (mit den gleichen Einschränkungen wie bei einer normalen Zuweisung).
Namenssubstitution ermöglicht die Bestimmung eines Eigenschaftsnamens durch Auswertung eines Ausdrucks oder einer Variable. Zum Beispiel:
Teile := StrSplit("Schlüssel = Wert", "=", " ") Teil := {%Teile[1]%: Teile[2]} MsgBox Teil.Schlüssel
Skripte geben Objekte nicht explizit frei. Sobald die letzte Referenz zu einem Objekt freigegeben ist, wird das Objekt automatisch freigegeben. Eine Referenz, die in einer Variable gespeichert ist, wird automatisch freigegeben, sobald dieser Variable ein anderer Wert zugewiesen wird. Zum Beispiel:
obj := {} ; Erstellt ein Objekt. obj := "" ; Gibt die letzte Referenz frei, wodurch das Objekt freigegeben wird.
Gleichermaßen wird eine Referenz, die in einer Eigenschaft oder einem Array-Element gespeichert ist, freigegeben, wenn diese Eigenschaft oder dieses Array-Element einen anderen Wert zugewiesen bekommt oder aus dem Objekt entfernt wird.
arr := [{}] ; Erstellt ein Array, das ein Objekt enthält. arr[1] := {} ; Erstellt ein zweites Objekt, wodurch das erste Objekt indirekt freigegeben wird. arr.RemoveAt(1) ; Entfernt das zweite Objekt und gibt es frei.
Da alle Referenzen zu einem Objekt freigegeben werden müssen, bevor das Objekt freigegeben werden kann, können Objekte mit Zirkelbezügen nicht automatisch freigegeben werden. Wenn z.B. x.child
auf y
und y.parent
auf x
verweist, genügt es nicht, x
und y
zu leeren, weil das Parent-Objekt noch eine Referenz zum Child-Objekt enthält, und umgekehrt. Um diese Situation zu lösen, entfernen Sie den Zirkelbezug.
x := {}, y := {} ; Zwei Objekte erstellen. x.child := y, y.parent := x ; Einen Zirkelbezug erstellen. y.parent := "" ; Der Zirkelbezug muss entfernt werden, bevor die Objekte freigegeben werden können. x := "", y := "" ; Ohne die obige Zeile würde dies die Objekte nicht freigeben.
Eine ausführlichere Verwendung und Informationen finden Sie unter Referenzzählung.
Obwohl "mehrdimensionale" Arrays nicht unterstützt werden, kann ein Skript mehrere Arrays oder Maps kombinieren. Zum Beispiel:
Gitter := [[1,2,3], [4,5,6], [7,8,9]] MsgBox Gitter[1][3] ; 3 MsgBox Gitter[3][2] ; 8
Ein benutzerdefiniertes Objekt kann mehrdimensionale Unterstützung durch Definition einer __Item-Eigenschaft implementieren. Zum Beispiel:
class Array2D extends Array { __new(x, y) { this.Length := x * y this.Breite := x this.Höhe := y } __Item[x, y] { get => super.Has(this.i[x, y]) ? super[this.i[x, y]] : false set => super[this.i[x, y]] := value } i[x, y] => this.Breite * (y-1) + x } Gitter := Array2D(4, 3) Gitter[4, 1] := "#" Gitter[3, 2] := "#" Gitter[2, 2] := "#" Gitter[1, 3] := "#" GitterText := "" Loop Gitter.Höhe { y := A_Index Loop Gitter.Breite { x := A_Index GitterText .= Gitter[x, y] || "-" } GitterText .= "`n" } MsgBox GitterText
Ein echtes Skript sollte eine Fehlerprüfung durchführen und andere Methoden überschreiben, wie z.B. __Enum, um die Enumeration zu unterstützen.
Es gibt zwei verschiedene Möglichkeiten, benutzerdefinierte Objekte zu erstellen:
Metafunktionen können verwendet werden, um das Verhalten eines Objekts genauer zu steuern.
Hinweis: In diesem Abschnitt ist ein Objekt eine Instanz der Object-Klasse. Dieser Abschnitt gilt nicht für COM-Objekte.
Grundsätzlich kann ein neues Objekt jederzeit um Eigenschaften und Methoden (aufrufbare Eigenschaften) erweitert werden. Das folgende Beispiel zeigt, wie ein Objekt mit einer Eigenschaft und einer Methode konstruiert werden kann:
; Ein Objekt erstellen. obj := {} ; Einen Wert speichern. obj.foo := "bar" ; Eine Methode definieren. obj.test := obj_test ; Die Methode aufrufen. obj.test() obj_test(this) { MsgBox this.foo }
Das obige Objekt kann auch mit obj := {foo: "bar"}
erstellt werden. Bei der {Eigenschaft:Wert}-Schreibweise dürfen die Eigenschaften nicht in Anführungszeichen gesetzt werden.
Beim Aufruf von obj.test()
wird obj automatisch an den Anfang der Parameterliste gesetzt. Konventionsgemäß setzt sich der Name der Funktion aus dem "Typ" des Objekts und dem Namen der Methode zusammen, kann aber auch anders lauten.
Im obigen Beispiel kann test, nachdem es definiert wurde, eine andere Funktion oder ein anderer Wert zugewiesen werden; in diesem Fall geht die ursprüngliche Funktion verloren und kann nicht über diese Eigenschaft aufgerufen werden. Alternativ kann eine schreibgeschützte Methode definiert werden:
obj.DefineProp 'test', {call: obj_test}
Siehe auch: DefineProp
Objekte sind prototypenbasiert. Das heißt, dass alle Eigenschaften, die nicht im Objekt selbst definiert sind, stattdessen in der Basis des Objekts definiert sein können. Dies wird als Vererbung durch Delegation oder differentielle Vererbung bezeichnet, da ein Objekt nur die Teile implementieren kann, die sich vom Objekt unterscheiden, und den Rest an seine Basis delegiert.
Obwohl ein Basisobjekt allgemein auch als Prototyp bekannt ist, verwenden wir "Prototyp einer Klasse" für das Objekt, auf dem jede Instanz der Klasse basiert, und "Basis" für das Objekt, auf dem eine Instanz basiert.
Das Objektdesign von AutoHotkey wurde hauptsächlich von JavaScript und Lua und ein wenig von C# beeinflusst. Wir verwenden obj.base
anstelle von JavaScripts obj.__proto__
und cls.Prototype
anstelle von JavaScripts func.prototype
. (Klassenobjekte werden anstelle von Konstruktorfunktionen verwendet.)
Die Basis eines Objekts wird auch verwendet, um seinen Typ oder seine Klasse zu identifizieren. Zum Beispiel erstellt x := []
ein Objekt basierend auf Array.Prototype
, d.h. die Ausdrücke x is Array
und x.HasBase(Array.Prototype)
sind True und type(x)
gibt "Array" zurück. Der Prototyp jeder Klasse basiert auf dem Prototyp ihrer Basisklasse, daher ist x.HasBase(Object.Prototype)
ebenfalls True.
Jede Instanz von Object oder eine abgeleitete Klasse kann ein Basisobjekt sein, aber ein Objekt kann nur als Basis eines Objekts mit dem gleichen nativen Typ zugewiesen werden. Dadurch wird sichergestellt, dass interne Methoden immer den nativen Typ eines Objekts identifizieren können und dass sie nur mit Objekten arbeiten, die die korrekte binäre Struktur aufweisen.
Basisobjekte können auf zwei verschiedene Arten definiert werden:
Ein Basisobjekt kann der Base-Eigenschaft eines anderen Objekts zugewiesen werden, aber normalerweise wird die Basis eines Objekts implizit beim Erstellen festgelegt.
Jedes Objekt kann als Basis eines anderen Objekts mit demselben nativen Typ verwendet werden. Das folgende Beispiel baut auf dem vorherigen Beispiel unter Ad Hoc auf (kombinieren Sie beide vor der Ausführung):
anderesObj := {} anderesObj.base := obj anderesObj.test()
anderesObj erbt in diesem Fall foo und test von obj. Diese Vererbung ist dynamisch, d.h. wenn obj.foo
geändert wird, wird diese Änderung durch anderesObj.foo
widergespiegelt. Wenn das Skript anderesObj.foo
einen Wert zuweist, wird dieser Wert in anderesObj gespeichert; weitere Änderungen an obj.foo
hätten keinen Effekt auf anderesObj.foo
. Beim Aufruf von anderesObj.test()
enthält dessen this-Parameter eine Referenz zu anderesObj statt zu obj.
Unter einer Klasse (auch Objekttyp genannt) versteht man in der objektorientierten Programmierung ein abstraktes Modell bzw. einen Bauplan für eine Reihe von ähnlichen Objekten. Wikipedia
Allgemein ausgedrückt ist eine Klasse eine Gruppe oder Kategorie von Dingen, die gemeinsame Eigenschaften oder Attribute haben. In AutoHotkey definiert class
Eigenschaften, die von Instanzen der Klasse gemeinsam verwendet werden (und Methoden, die aufrufbare Eigenschaften sind). Eine Instanz ist schlicht ein Objekt, das Eigenschaften von der Klasse erbt und typischerweise auch als Teil dieser Klasse verstanden werden kann (z.B. mit dem Ausdruck Instanz is KlasseName
). Instanzen werden typischerweise durch den Aufruf von KlasseName() erzeugt.
Da Objekte dynamisch und prototypenbasiert sind, besteht jede Klasse aus zwei Teilen:
static
fehlt.static
und alle verschachtelten Klassen. Diese gelten nicht für eine bestimmte Instanz und können verwendet werden, indem via Name auf die Klasse selbst verwiesen wird.Das Folgende zeigt die meisten Elemente einer Klassendefinition:
class KlasseName extends BasisklasseName { InstanzVar := Ausdruck static KlasseVar := Ausdruck class VerschachtelteKlasse { ... } Methode() { ... } static Methode() { ... } Eigenschaft[Parameter] ; Eckige Klammern nur verwenden, wenn Parameter vorhanden sind. { get { return Eigenschaftswert } set { Speichere oder verarbeite value } } KurzeEigenschaft { get => Ausdruck zum Ermitteln des Eigenschaftswertes set => Ausdruck zum Speichern oder Verarbeiten von value } KürzereEigenschaft => Ausdruck zum Ermitteln des Eigenschaftswertes }
Dies bewirkt, dass beim Laden des Skripts ein Class-Objekt konstruiert und in eine globale Konstante (schreibgeschützte Variable) namens KlasseName gespeichert wird. Wenn extends BasisklasseName
vorhanden ist, muss BasisklasseName der vollständige Name einer anderen Klasse sein. Der vollständige Name jeder Klasse ist in KlasseName.Prototype.__Class
gespeichert.
Da der Zugriff auf die Klasse selbst über eine Variable erfolgt, kann der Klassenname nicht verwendet werden, um im selben Kontext sowohl die Klasse zu referenzieren als auch eine separate Variable zu erstellen (um z.B. eine Instanz der Klasse zu enthalten). Zum Beispiel wird box := Box()
nicht funktionieren, da sowohl box
als auch Box
in dasselbe aufgelöst werden. Der Versuch, eine Top-Level-Klasse (nicht verschachtelte Klasse) auf diese Weise neu zuzuweisen, führt zu einem Ladezeitfehler.
In dieser Dokumentation bezieht sich das Wort "Klasse" in der Regel auf ein Klassenobjekt, das mit dem Schlüsselwort class
erstellt wurde.
Klassendefinitionen können Variablendeklarationen, Methodendefinitionen und Unterklassendefinitionen enthalten.
Eine Instanzvariable ist eine Variable, von der jede Instanz der Klasse eine eigene Kopie hat. Sie werden wie normale Zuweisungen deklariert und verhalten sich auch wie solche, aber ohne Angabe des Präfixes this.
(nur direkt im Körper der Klasse):
InstanzVar := Ausdruck
Solche Deklarationen werden jedes Mal ausgewertet, wenn eine neue Instanz der Klasse mit KlasseName() erstellt wird, nachdem alle Basisklassendeklarationen ausgewertet wurden, aber bevor __New aufgerufen wird. Dies wird erreicht, indem automatisch eine Methode namens __Init erstellt wird, die einen Aufruf von super.__Init()
enthält, und jede Deklaration in diese eingefügt wird. Daher darf eine einzelne Klassendefinition weder eine __Init-Methode noch eine Instanzvariablendeklaration enthalten.
Ausdruck kann mit this
auf andere Instanzvariablen und Methoden zugreifen. Globale Variablen können gelesen, aber nicht zugewiesen werden. Eine zusätzliche Zuweisung (oder die Verwendung des Referenzoperators) innerhalb des Ausdrucks erzeugt in der Regel eine Variable, die lokal für die __Init-Methode ist. Zum Beispiel bewirkt x := y := 1
, dass this.x
und eine lokale Variable y
gesetzt wird (die nach der Auswertung aller Initialisierungen wieder freigegeben wird).
Um auf eine Instanzvariable zuzugreifen (auch innerhalb einer Methode), geben Sie immer das Zielobjekt an, z.B. this.InstanzVar
.
Deklarationen wie x.y := z
werden ebenfalls unterstützt, sofern vorher x
in dieser Klasse definiert wurde. Zum Beispiel bewirkt x := {}, x.y := 42
, dass x
deklariert und außerdem this.x.y
initialisiert wird.
Statische bzw. Klassenvariablen gehören nur der Klasse selbst, aber ihre Werte können von Unterklassen geerbt werden. Solche Variablen müssen mit dem Schlüsselwort "static" deklariert werden:
static KlasseVar := Ausdruck
Diese Deklarationen werden nur einmal beim Initialisieren der Klasse ausgewertet. Zu diesem Zweck wird automatisch eine statische Methode namens __Init definiert.
Jede Deklaration fungiert wie eine normale Eigenschaftszuweisung, mit dem Klassenobjekt als Ziel. Ausdruck wird genauso interpretiert wie die Instanzvariablen, außer dass this
auf die Klasse selbst verweist.
Um einer Klassenvariable an anderer Stelle etwas zuzuweisen, geben Sie immer das Klassenobjekt an, z.B. KlasseName.KlasseVar := Wert
. Wenn eine Unterklasse keine Eigenschaft mit diesem Namen hat, kann auch mit Unterklasse.KlasseVar
der Wert abgerufen werden, d.h. wenn der Wert eine Referenz zu einem Objekt ist, teilen sich die Unterklassen standardmäßig dieses Objekt. Allerdings würde Unterklasse.KlasseVar := y
den Wert in Unterklasse speichern, nicht in KlasseName.
Deklarationen wie static x.y := z
werden ebenfalls unterstützt, sofern vorher x
in dieser Klasse definiert wurde. Zum Beispiel bewirkt static x := {}, x.y := 42
, dass x
deklariert und außerdem KlasseName.x.y
initialisiert wird. Da Prototype implizit in jeder Klasse definiert ist, können mit static Prototype.geteilterWert := 1
Werte gesetzt werden, die dynamisch von allen Instanzen der Klasse geerbt werden (bis sie durch eine Eigenschaft der Instanz selbst "überschrieben" werden).
Verschachtelte Klassendefinitionen ermöglichen es, ein Klassenobjekt mit einer statischen bzw. Klassenvariable der äußeren Klasse zu assoziieren, anstatt mit einer separaten globalen Variablen. Im obigen Beispiel konstruiert class VerschachtelteKlasse
ein Class-Objekt und speichert es in KlasseName.VerschachtelteKlasse
. Unterklassen können VerschachtelteKlasse erben oder es mit ihrer eigenen verschachtelten Klasse überschreiben (in diesem Fall kann mit WelcheKlasse.VerschachtelteKlasse()
die entsprechende Klasse instanziiert werden).
class VerschachtelteKlasse { ... }
Das Verschachteln einer Klasse impliziert keine besondere Beziehung zur äußeren Klasse. Weder wird die verschachtelte Klasse automatisch instanziiert, noch haben Instanzen der verschachtelten Klasse eine Verbindung zu einer Instanz der äußeren Klasse, es sei denn, das Skript stellt diese Verbindung explizit her.
Jede verschachtelte Klassendefinition erzeugt eine dynamische Eigenschaft mit Get- und Call-Akzessorfunktionen anstelle einer einfachen Werteigenschaft. Damit soll das folgende Verhalten unterstützt werden (wobei die Klasse X die verschachtelte Klasse Y enthält):
X.Y()
übergibt X nicht an X.Y.Call
und letztlich an __New
, was sonst passieren würde, da dies das normale Verhalten für Funktionsobjekte ist, die als Methoden aufgerufen werden (so wie die verschachtelte Klasse hier verwendet wird).X.Y := 1
ist standardmäßig ein Fehler (die Eigenschaft ist schreibgeschützt).Methodendefinitionen sehen genauso aus wie Funktionsdefinitionen. Jede Methodendefinition erstellt ein Func mit einem versteckten ersten Parameter namens this
und definiert eine Eigenschaft, die zum Aufrufen der Methode oder zum Abrufen ihres Funktionsobjekts verwendet wird.
Es gibt zwei Arten von Methoden:
this
auf eine Instanz der Klasse.static
voran. Diese sind an das Klassenobjekt selbst gebunden, werden aber auch von Unterklassen geerbt, so dass this
entweder auf die Klasse selbst oder auf eine Unterklasse verweist.Die Methodendefinition unten erstellt eine Eigenschaft vom gleichen Typ wie Ziel.DefineProp('Methode', {call: funcObj})
. Standardmäßig gibt Ziel.Methode
funcObj zurück, und der Versuch, Ziel.Methode
etwas zuzuweisen, löst einen Fehler aus. Diese Standardverhalten können durch Definieren einer Eigenschaft oder Aufrufen von DefineProp überschrieben werden.
Methode() { ... }
Mit der Fat-Arrow-Syntax kann eine einzeilige Methode definiert werden, die ein Ausdruck zurückgibt:
Methode() => Ausdruck
Innerhalb einer Methode oder eines Eigenschafts-Getters/Setters kann das Schlüsselwort super
anstelle von this
verwendet werden, um auf Superklassenversionen von Methoden oder Eigenschaften zuzugreifen, die in einer abgeleiteten Klasse überschrieben werden. Zum Beispiel würde super.Methode()
in der oben definierten Klasse typischerweise eine Version von Methode aufrufen, die innerhalb von BasisklasseName definiert wurde. Hinweis:
super.Methode()
ruft immer indirekt die Basis der Klasse oder des Prototypobjekts auf, das mit der ursprünglichen Definition der aktuellen Methode assoziiert ist, auch dann, wenn this
von einer Unterklasse dieser Klasse oder von einer ganz anderen Klasse abgeleitet wurde.super.Methode()
übergibt implizit this
als ersten (versteckten) Parameter.super.Methode()
meistens äquivalent zu (KlasseName.Prototype.base.Methode)(this)
(aber ohne Prototype, wenn Methode statisch ist). Allerdings wird KlasseName.Prototype
beim Laden des Skripts aufgelöst.Nach dem Schlüsselwort super
muss eines der folgenden Symbole folgen: .[(
super()
ist äquivalent zu super.call()
.
Eine Eigenschaftsdefinition erzeugt eine dynamische Eigenschaft, die eine Methode aufruft, anstatt nur einen Wert zu speichern oder zurückzugeben.
Eigenschaft[Parameter] { get { return Eigenschaftswert } set { Speichere oder verarbeite value } }
Eigenschaft ist der Name der Eigenschaft, der für ihren indirekten Aufruf verwendet wird. Zum Beispiel bewirkt obj.Eigenschaft
, dass get aufgerufen wird, und obj.Eigenschaft := Wert
, dass set aufgerufen wird. Innerhalb von get oder set bezieht sich this
auf das Objekt, das gerade indirekt aufgerufen wird. Innerhalb von set enthält value
den Wert, der gerade zugewiesen wird.
Die Definition und Übergabe von Parametern erfolgt rechts vom Eigenschaftsnamen in eckigen Klammern - diese sollten jedoch weggelassen werden, wenn keine Parameter vorhanden sind (siehe unten). Abgesehen von der Verwendung eckiger Klammern werden die Parameter von Eigenschaften genauso definiert wie die Parameter von Methoden - optionale, variadische und ByRef-Parameter werden unterstützt.
Wenn eine Eigenschaft mit Parametern indirekt aufgerufen wird, aber keine definiert sind, werden Parameter automatisch an die __Item-Eigenschaft des von get zurückgegebenen Objekts weitergeleitet. Zum Beispiel hätte this.Eigenschaft[x]
denselben Effekt wie (this.Eigenschaft)[x]
oder y := this.Eigenschaft, y[x]
. Leere Klammern (this.Eigenschaft[]
) führen dazu, dass die __Item-Eigenschaft des Wertes von Eigenschaft immer indirekt aufgerufen wird, aber ein variadischer Aufruf wie this.Eigenschaft[Args*]
hat diesen Effekt nur, wenn die Anzahl der Parameter ungleich Null ist.
Um statische Eigenschaften zu definieren, stellen Sie dem Eigenschaftsnamen das separate Schlüsselwort static
voran. In diesem Fall verweist this
auf die Klasse selbst oder auf eine Unterklasse.
Der Rückgabewert von set wird ignoriert. Zum Beispiel wird Wert := obj.Eigenschaft := 42
immer Wert := 42
zuweisen, egal was die Eigenschaft macht, es sei denn, sie löst eine Ausnahme aus oder beendet den Thread.
Jede Klasse kann eine oder beide Hälften einer Eigenschaft definieren. Wenn eine Klasse eine Eigenschaft überschreibt, kann sie super.Eigenschaft
verwenden, um auf die von ihrer Basisklasse definierte Eigenschaft zuzugreifen. Wenn Get oder Set nicht definiert ist, kann es von einem Basisobjekt geerbt werden. Wenn Get undefiniert ist, kann die Eigenschaft einen Wert zurückgeben, der von einer Basis geerbt wurde. Wenn Set in diesem und allen Basisobjekten undefiniert ist (oder von einer geerbten Werteigenschaft verschleiert wird), löst der Versuch, die Eigenschaft zu setzen, eine Ausnahme aus.
Eine Eigenschaftsdefinition mit sowohl Get als auch Set erstellt faktisch zwei separate Funktionen, die weder lokale oder statische Variablen noch verschachtelte Funktionen gemeinsam nutzen. Analog zu den Methoden hat jede Funktion einen versteckten Parameter namens this
, und Set hat einen zweiten versteckten Parameter namens value
. Explizit definierte Parameter erfolgen danach.
Während eine Eigenschaftsdefinition die Get- und Set-Akzessorfunktionen für eine Eigenschaft auf die gleiche Weise wie DefineProp definiert, definiert eine Methodendefinition die Call-Akzessorfunktion. Jede Klasse kann eine Eigenschaftsdefinition und eine Methodendefinition gleichen Namens enthalten. Wenn eine Eigenschaft ohne Call-Akzessorfunktion (eine Methode) aufgerufen wird, wird Get ohne Parameter indirekt aufgerufen und das Ergebnis anschließend als Methode aufgerufen.
Mit der Fat-Arrow-Syntax kann ein Eigenschaft-Getter oder -Setter definiert werden, der ein Ausdruck zurückgibt:
KurzeEigenschaft[Parameter] { get => Ausdruck zum Ermitteln des Eigenschaftswertes set => Ausdruck zum Speichern oder Verarbeiten von value }
Wenn nur ein Getter definiert wird, können die geschweiften Klammern und get
weggelassen werden:
KürzereEigenschaft[Parameter] => Ausdruck zum Ermitteln des Eigenschaftswertes
In beiden Fällen müssen die eckigen Klammern weggelassen werden, es sei denn, Parameter sind definiert.
__Enum(AnzahlVars)
Die __Enum-Methode wird aufgerufen, wenn das Objekt an eine For-Schleife übergeben wird. Diese Methode sollte einen Enumerator zurückgeben, der Elemente im Objekt zurückgibt, wie z.B. Array-Elemente. Wenn es undefiniert bleibt, kann das Objekt nicht direkt an eine For-Schleife übergeben werden, es sei denn, es hat eine Enumerator-kompatible Call-Methode.
AnzahlVars enthält die Anzahl der Variablen, die an die For-Schleife übergeben wurden. Wenn AnzahlVars 2 ist, wird erwartet, dass der Enumerator dem ersten Parameter den Schlüssel oder Index eines Elements und dem zweiten Parameter den Wert zuweist. Jeder Schlüssel oder Index sollte als Parameter der __Item-Eigenschaft akzeptiert werden. Dies befähigt DBGp-basierte Debugger, ein bestimmtes Element abzurufen oder zu setzen, nachdem sie durch indirekten Aufruf des Enumerators aufgelistet wurden.
Die __Item-Eigenschaft wird indirekt aufgerufen, wenn der Indexierungsoperator (Arraysyntax) in Verbindung mit dem Objekt verwendet wird. Im folgenden Beispiel wird die Eigenschaft als statisch deklariert, so dass der Indexierungsoperator auf die Env-Klasse selbst angewendet werden kann. Ein weiteres Beispiel finden Sie unter Array2D.
class Env { static __Item[name] { get => EnvGet(name) set => EnvSet(name, value) } } Env["PATH"] .= ";" A_ScriptDir ; Betrifft nur dieses Skript und Unterprozesse. MsgBox Env["PATH"]
__Item
ist quasi ein Standardeigenschaftsname (falls eine solche Eigenschaft definiert wurde):
object[params]
ist äquivalent zu object.__Item[params]
, wenn Parameter vorhanden sind.object[]
ist äquivalent zu object.__Item
.Zum Beispiel:
obj := {} obj[] := Map() ; Äquivalent zu obj.__Item := Map() obj["base"] := 10 MsgBox obj.base = Object.prototype ; True MsgBox obj["base"] ; 10
Hinweis: Wenn ein expliziter Eigenschaftsname mit leeren eckigen Klammern kombiniert wird, wie in obj.prop[]
, wird dies als zwei separate Operationen behandelt: Zuerst obj.prop
abrufen und dann die Standardeigenschaft des Ergebnisses indirekt aufrufen. Dies ist Teil der Sprachsyntax und somit objektunabhängig.
Jedes Mal, wenn ein Objekt mit der Standardimplementierung von KlasseName() erstellt wird, wird die __New
-Methode des neuen Objekts aufgerufen, um eine benutzerdefinierte Initialisierung zu ermöglichen. Alle Parameter, die an KlasseName()
übergeben werden, werden an __New
weitergeleitet, so dass sie den ursprünglichen Inhalt des Objekts oder die Art und Weise, wie es konstruiert wird, beeinflussen können. Bei Zerstörung des Objekts wird __Delete
aufgerufen. Zum Beispiel:
m1 := GMem(0, 10) m2 := {base: GMem.Prototype}, m2.__New(0, 30) ; Hinweis: Für allgemeine Speicherreservierungen stattdessen Buffer() verwenden. class GMem { __New(aFlags, aGröße) { this.ptr := DllCall("GlobalAlloc", "UInt", aFlags, "Ptr", aGröße, "Ptr") if !this.ptr throw MemoryError() MsgBox "Neues GMem von " aGröße " Bytes auf Adresse " this.ptr "." } __Delete() { MsgBox "GMem auf Adresse " this.ptr " löschen." DllCall("GlobalFree", "Ptr", this.ptr) } }
__Delete wird nicht für Objekte aufgerufen, die eine Eigenschaft namens "__Class" haben. Prototypobjekte haben diese Eigenschaft standardmäßig.
Wenn ein Ausnahme- oder Laufzeitfehler bei der Ausführung von __Delete ausgelöst und nicht innerhalb von __Delete behandelt wird, verhält es sich so, als wäre __Delete von einem neuen Thread aufgerufen worden. Das heißt, dass ein Fehlerdialogfenster angezeigt wird und __Delete zurückkehrt, aber der Thread nicht beendet wird (es sei denn, er wurde bereits beendet).
Wenn das Skript auf irgendeine Weise direkt terminiert wird, z.B. über das Tray-Menü oder ExitApp, bekommen alle Funktionen, die noch nicht zu ihrem Aufrufer zurückgekehrt sind, keine Chance, dies jemals zu tun. Daher werden alle Objekte, die von lokalen Variablen dieser Funktionen referenziert werden, nicht freigegeben, was dazu führt, dass __Delete nicht aufgerufen wird. Unter diesen Umständen werden auch temporäre Referenzen auf dem Stapel der Ausdrucksauswertung nicht freigegeben.
Bei Beendigung des Skripts werden Objekte, die in globalen und statischen Variablen enthalten sind, automatisch in einer willkürlichen, von der Implementierung festgelegten Reihenfolge freigegeben. Wenn __Delete während dieses Vorgangs aufgerufen wird, können einige globale oder statische Variablen bereits freigegeben worden sein, aber alle Referenzen, die das Objekt selbst enthält, sind noch gültig. Daher ist es am besten, wenn __Delete völlig eigenständig ist und nicht auf globale oder statische Variablen angewiesen ist.
Jede Klasse wird automatisch initialisiert, wenn eine Referenz zur Klasse erstmals ausgewertet wird. Wenn z.B. MeineKlasse noch nicht initialisiert wurde, würde MeineKlasse.MeineEigenschaft
bewirken, dass die Klasse initialisiert wird, bevor die Eigenschaft abgerufen wird. Die Initialisierung umfasst den Aufruf von zwei statischen Methoden: __Init und __New.
static __Init
wird automatisch für jede Klasse definiert und beginnt immer mit einer Referenz zur Basisklasse, falls angegeben, um sicherzustellen, dass sie initialisiert ist. Statische bzw. Klassenvariablen und verschachtelte Klassen werden in der Reihenfolge initialisiert, in der sie definiert wurden, außer wenn eine verschachtelte Klasse während der Initialisierung einer vorherigen Variable oder Klasse referenziert wird.
Wenn die Klasse eine static __New
-Methode definiert oder erbt, wird diese unmittelbar nach __Init aufgerufen. Es ist wichtig zu beachten, dass __New einmal für die Klasse, in der es definiert ist, und einmal für jede Unterklasse, die keine eigene definiert (oder die super.__New()
aufruft), aufgerufen werden kann. Dies kann verwendet werden, um allgemeine Initialisierungsaufgaben für jede Unterklasse durchzuführen oder um Unterklassen vor ihrer Verwendung in irgendeiner Weise zu modifizieren.
Wenn static __New
nicht mit abgeleiteten Klassen arbeiten soll, kann dies durch die Überprüfung des Wertes von this
vermieden werden. In einigen Fällen kann es ausreichend sein, dass die Methode sich selbst löscht, wie z.B. bei this.DeleteProp('__New')
; allerdings könnte die erste Ausführung von __New für eine Unterklasse sein, wenn diese in der Basisklasse verschachtelt ist oder bei der Initialisierung einer statischen bzw. Klassenvariable referenziert wird.
Eine Klassendefinition bewirkt auch, dass die Klasse referenziert wird. Mit anderen Worten, wenn die Ausführung während der Startphase des Skripts eine Klassendefinition erreicht, werden __Init und __New automatisch aufgerufen, es sei denn, die Klasse wurde bereits vom Skript referenziert. Wenn die Ausführung jedoch daran gehindert wird, die Klassendefinition zu erreichen, z.B. durch return
oder eine Endlosschleife, wird die Klasse erst initialisiert, wenn sie referenziert wird.
Wenn die automatische Initialisierung einmal begonnen hat, wird sie für dieselbe Klasse nicht erneut durchgeführt. Dies ist grundsätzlich kein Problem, es sei denn, mehrere Klassen referenzieren sich gegenseitig. Nehmen wir zum Beispiel die folgenden zwei Klassen. Wenn A
zuerst initialisiert wird, bewirkt die Auswertung von B.SharedArray
(A1), dass B
initialisiert wird, bevor der Wert abgerufen und zurückgegeben wird, aber A.SharedValue
(A3) ist undefiniert und bewirkt keine Initialisierung von A
, da diese bereits im Gange ist. Mit anderen Worten, wenn A
erstmals verwendet oder initialisiert wird, ist die Reihenfolge A1 bis A3, sonst B1 bis B4:
MsgBox A.SharedArray.Length MsgBox B.SharedValue class A { static SharedArray := B.SharedArray ; A1 ; B3 static SharedValue := 42 ; B4 } class B { static SharedArray := StrSplit("XYZ") ; A2 ; B1 static SharedValue := A.SharedValue ; A3 (Error) ; B2 }
class KlasseName { __Get(Name, Params) __Set(Name, Params, Wert) __Call(Name, Params) }
Der Name der Eigenschaft oder Methode.
Ein Array von Parametern. Dies umfasst nur die Parameter zwischen ()
oder []
, kann also leer sein. Die Metafunktion soll Fälle wie x.y[z]
behandeln, in denen x.y
undefiniert ist.
Der Wert, der zugewiesen wird.
Metafunktionen definieren, was passieren soll, wenn eine undefinierte Eigenschaft oder Methode indirekt aufgerufen wird. Wenn z.B. obj.unk
keinen Wert zugewiesen bekommen hat, ruft es indirekt die __Get-Metafunktion auf. obj.unk := Wert
bewirkt dagegen den indirekten Aufruf von __Set und obj.unk()
den indirekten Aufruf von __Call.
Eigenschaften und Methoden können im Objekt selbst oder in einem seiner Basisobjekte definiert werden. Damit für jede Eigenschaft eine Metafunktion aufgerufen wird, sollte die Definition von Eigenschaften grundsätzlich vermieden werden. Interne Eigenschaften wie Base können mit einer Eigenschaftsdefinition oder DefineProp überschrieben werden.
Wenn eine Metafunktion definiert ist, muss sie die gewünschte Standardaktion ausführen. Zum Beispiel kann Folgendes erwartet werden:
Jedes aufrufbare Objekt kann als Metafunktion verwendet werden, wenn es der entsprechenden Eigenschaft zugewiesen wird.
Metafunktionen werden in den folgenden Fällen nicht aufgerufen:
x[y]
: Die Verwendung von eckigen Klammern ohne Eigenschaftsnamen ruft indirekt nur die __Item-Eigenschaft auf.x()
: Der Aufruf des Objekts selbst ruft indirekt nur die Call
-Methode auf. Dies umfasst interne Aufrufe durch interne Funktionen wie SetTimer und Hotkey.__Call
nicht aus.Die Eigenschaftssyntax und DefineProp ermöglichen die Definition von Eigenschaften, die bei jeder Auswertung einen Wert ermitteln, allerdings muss jede Eigenschaft im Voraus definiert sein. Mit __Get und __Set können hingegen Eigenschaften implementiert werden, die erst zum Zeitpunkt ihres indirekten Aufrufs bekannt sind.
Zum Beispiel kann ein "Proxy-Objekt" mit Eigenschaften erstellt werden, die den aktuellen Wert über das Netzwerk (oder über einen anderen Kanal) abfragen. Ein Remote-Server sendet eine Antwort zurück, die den Wert der Eigenschaft enthält, und das Proxy-Objekt übergibt den Wert an seinen Aufrufer. Selbst wenn der Name jeder Eigenschaft im Voraus bekannt wäre, würde es keinen Sinn machen, jede Eigenschaft einzeln in der Proxy-Klasse zu definieren, da jede Eigenschaft dasselbe bewirkt (eine Netzwerkanfrage senden). Metafunktionen empfangen den Namen der Eigenschaft als Parameter und sind daher eine gute Lösung für dieses Problem.
Primitive Werte wie Zeichenketten und Zahlen können keine eigenen Eigenschaften und Methoden haben. Allerdings unterstützen primitive Werte die gleiche Art der Delegation wie Objekte. Das heißt, dass jeder Eigenschafts- oder Methodenaufruf bei einem primitiven Wert an ein vordefiniertes Prototypobjekt delegiert wird, auf das auch über die Prototype-Eigenschaft der entsprechenden Klasse zugegriffen werden kann. Die folgenden Klassen beziehen sich auf primitive Werte:
Obwohl die Überprüfung der Type-Zeichenkette in der Regel schneller ist, kann der Typ eines Wertes auch getestet werden, indem geprüft wird, ob der Wert eine Basis hat. Zum Beispiel ist n.HasBase(Number.Prototype)
oder n is Number
True, wenn n ein reiner Integer oder eine reine Floating-Point-Zahl ist, aber False, wenn n eine numerische Zeichenkette ist, da String nicht von Number ableitet. IsNumber(n)
hingegen ist True, wenn n eine Zahl oder numerische Zeichenkette ist.
ObjGetBase und die Base-Eigenschaft geben ggf. eines der vordefinierten Prototypobjekte zurück.
Beachten Sie, dass x is Any
normalerweise True für jeden Wert innerhalb der Typenhierarchie von AutoHotkey ist, aber False für COM-Objekte ist.
Eigenschaften und Methoden können für alle Werte eines bestimmten Typs hinzugefügt werden, indem das Prototypobjekt dieses Typs geändert wird. Da jedoch ein primitiver Wert kein Object ist und keine eigenen Eigenschaften oder Methoden haben kann, werden die primitiven Prototypobjekte nicht von Object.Prototype
ableiten. Mit anderen Worten, es ist standardmäßig nicht möglich, auf Methoden wie DefineProp und HasOwnProp zuzugreifen. Diese können indirekt aufgerufen werden. Zum Beispiel:
DefProp := {}.DefineProp DefProp( "".base, "Length", { get: StrLen } ) MsgBox A_AhkPath.length " == " StrLen(A_AhkPath)
Obwohl primitive Werte Werteigenschaften von ihrem Prototyp erben können, wird eine Ausnahme ausgelöst, wenn das Skript versucht, eine Werteigenschaft bei einem primitiven Wert zu setzen. Zum Beispiel:
"".base.test := 1 ; Bitte nicht nachmachen. MsgBox "".test ; 1 "".test := 2 ; Fehler: Eigenschaft ist schreibgeschützt.
Obwohl __Set und Eigenschafts-Setter verwendet werden können, sind sie nicht nützlich, da primitive Werte als unveränderlich zu betrachten sind.
AutoHotkey verwendet einen einfachen Referenzzählmechanismus, um belegte Ressourcen eines Objekts automatisch freizugeben, wenn es nicht länger im Skript referenziert wird. Es ist wichtig, diesen Mechanismus zu verstehen, um die Lebensdauer eines Objekts richtig zu verwalten, was die Löschung des Objekts ermöglicht, wenn es nicht mehr benötigt wird, und nicht vorher.
Die Referenzanzahl eines Objekts wird jedes Mal erhöht, wenn eine Referenz gespeichert wird. Bei der Freigabe einer Referenz wird die Anzahl verwendet, um festzustellen, ob diese Referenz die letzte ist. Ist dies der Fall, wird das Objekt gelöscht, andernfalls wird die Anzahl dekrementiert. Die folgenden einfachen Beispiele zeigen, wie Referenzen gezählt werden:
a := {Name: "Bob"} ; Bobs Referenzanzahl ist anfangs 1 b := [a] ; Bobs Referenzanzahl erhöht sich auf 2 a := "" ; Bobs Referenzanzahl verringert sich auf 1 c := b.Pop() ; Bob transferiert, Referenzanzahl ist immer noch 1 c := "" ; Bob wurde gelöscht...
Temporäre Referenzen, die von Funktionen, Methoden oder Operatoren innerhalb eines Ausdrucks zurückgegeben werden, werden erst freigegeben, wenn die Auswertung dieses Ausdrucks abgeschlossen oder abgebrochen wurde. Im folgenden Beispiel wird das neue GMem-Objekt erst nach Abschluss von MsgBox freigegeben:
MsgBox DllCall("GlobalSize", "ptr", GMem(0, 20).ptr, "ptr") ; 20
Hinweis: In diesem Beispiel kann .ptr
weggelassen werden, da der Ptr-Argumenttyp Objekte mit einer Ptr
-Eigenschaft zulässt. Allerdings funktioniert das oben gezeigte Muster auch mit anderen Eigenschaftsnamen.
Um Code beim Freigeben der letzten Referenz zu einem Objekt auszuführen, implementieren Sie die __Delete-Metafunktion.
Wenn man sich ausschließlich auf die Referenzzählung verlässt, gerät man manchmal in eine Zwickmühle: Ein Objekt ist so konzipiert, dass es seine Ressourcen bei seiner Löschung freigibt, aber es wird erst gelöscht, wenn seine Ressourcen zuvor freigegeben wurden. Dies ist insbesondere dann der Fall, wenn es sich bei diesen Ressourcen um andere Objekte oder Funktionen handelt, die - oft indirekt - eine Referenz auf das Objekt aufrechterhalten.
Ein Zirkelbezug oder Referenzzyklus liegt vor, wenn ein Objekt direkt oder indirekt auf sich selbst verweist. Wenn jede Referenz, die Teil des Zyklus ist, in der Zählung enthalten ist, kann das Objekt erst gelöscht werden, wenn der Zyklus manuell unterbrochen wird. Zum Beispiel erzeugt Folgendes einen Referenzzyklus:
parent := {} ; parent: 1 (Referenzanzahl) child := {parent: parent} ; parent: 2, child: 1 parent.child := child ; parent: 2, child: 2
Wenn die Variablen parent
und child
neu zugewiesen werden, wird die Referenzanzahl für jedes Objekt auf 1 verringert. Beide Objekte wären für das Skript unzugänglich, würden aber nicht gelöscht werden, da die letzten Referenzen nicht freigegeben sind.
Ein Zyklus ist häufig weniger offensichtlich und kann mehrere Objekte umfassen. RefZyklusGuiZeigen zeigt zum Beispiel einen Zyklus, der Gui, MenuBar, Menu und Closures beinhaltet. Die Verwendung eines separaten Objekts zur Behandlung von GUI-Ereignissen ist ebenfalls anfällig für Zyklen, wenn das Handler-Objekt eine Referenz auf die GUI hat.
Nichtzyklische Referenzen auf ein Objekt können ebenfalls Probleme verursachen. Zum Beispiel bewirken Objekte, die von internen Funktionen wie SetTimer oder OnMessage abhängig sind, in der Regel, dass das Programm eine indirekte Referenz auf das Objekt hält. Dies würde verhindern, dass das Objekt gelöscht wird, was bedeutet, dass es __New und __Delete nicht verwenden kann, um den Timer oder die Meldungsüberwachung zu verwalten.
Im Folgenden werden einige Strategien vorgestellt, die zur Lösung der oben beschriebenen Probleme beitragen können.
Zyklen vermeiden: Wenn Referenzzyklen ein Problem darstellen, sollten sie nicht erstellt werden. Zum Beispiel würde entweder parent.child
oder child.parent
nicht gesetzt werden. Dies ist oft nicht praktikabel, da verwandte Objekte in irgendeiner Weise aufeinander verweisen müssen.
Vermeiden Sie bei der Definition von Ereignishandlern für OnEvent (Gui), die Quell-Gui in einer Closure- oder Bound-Funktion zu erfassen, und verwenden Sie stattdessen den Gui- oder Gui.Control-Parameter. Dasselbe gilt für Add (Menu) und den Menü-Parameter des Callbacks, aber natürlich kann ein Menüpunkt, der auf eine Gui verweisen muss, diesen Ansatz nicht verwenden.
In einigen Fällen kann das andere Objekt durch eine indirekte Methode abgerufen werden, die nicht auf einer gezählten Referenz beruht. Zum Beispiel können Sie eine HWND-Nummer speichern und GuiFromHwnd(hwnd)
verwenden, um ein Gui-Objekt abzurufen. Es ist nicht notwendig, eine Referenz aufrechtzuerhalten, um das Löschen zu verhindern, während das Fenster sichtbar ist, da die Gui dies selbst regelt.
Zyklen unterbrechen: Wenn das Skript die Referenzzählung vermeiden kann und stattdessen die Lebensdauer des Objekts direkt verwaltet, muss es den Zyklus nur unterbrechen, wenn die Objekte gelöscht werden sollen:
child.parent := unset ; parent: 1, child: 2 child := unset ; parent: 1, child: 1 parent := unset ; beide gelöscht
Dispose (Entsorgen): __Delete wird genau dann aufgerufen, wenn die letzte Referenz freigegeben wird, so dass man sich eine einfache Zuweisung wie meineGui := ""
als einen Bereinigungsschritt vorstellen kann, der das Löschen des Objekts auslöst. Manchmal wird dies explizit gemacht, wenn das Objekt nicht mehr benötigt wird, aber dies ist weder zuverlässig noch zeigt es wirklich die Absicht des Codes. Ein alternatives Muster besteht darin, eine Dispose- oder Destroy-Methode zu definieren, die die Ressourcen des Objekts freigibt, und sie so zu konzipieren, dass sie nichts tut, wenn sie ein zweites Mal aufgerufen wird. Sie kann dann zur Sicherheit auch von __Delete aus aufgerufen werden.
Ein Objekt, das diesem Muster folgt, muss dennoch alle Referenzzyklen unterbrechen, wenn es entsorgt wird, da sonst ein Teil des Speichers nicht freigegeben würde und __Delete für andere Objekte, die vom Objekt referenziert werden, nicht aufgerufen würde.
Zyklen, die durch die Ereignishandler eines Gui-Objekts, durch MenuBar oder durch ein Event-Sink-Objekt verursacht werden, werden automatisch "unterbrochen", wenn Destroy aufgerufen wird, da es diese Objekte freigibt. (Dies wird im RefZyklusGuiZeigen-Beispiel gezeigt.) Allerdings würde dies keine Zyklen unterbrechen, die durch neu hinzugefügte Eigenschaften verursacht werden, da Destroy diese nicht löscht.
Ähnlich wie das Dispose-Muster hat InputHook eine Stop-Methode, die explizit aufgerufen werden muss und deshalb nicht auf __Delete angewiesen ist, um zu signalisieren, wann die Operation beendet werden soll. Während der Operation hält das Programm effektiv eine Referenz auf das Objekt, wodurch dessen Löschung verhindert wird, aber dies ist eher eine Stärke als eine Schwäche: Ereignisrückrufe können weiterhin aufgerufen werden und erhalten den InputHook als Parameter. Wenn die Operation beendet ist, wird die interne Referenz freigegeben und der InputHook gelöscht, wenn das Skript keine Referenz darauf hat.
Pointer: Das Speichern von beliebig vielen Pointerwerten hat keinen Einfluss auf die Referenzanzahl des Objekts, da ein Pointer nur ein Integer ist. Ein mit ObjPtr abgerufener Pointer kann verwendet werden, um eine Referenz zu erzeugen, indem er an ObjFromPtrAddRef übergeben wird. Die AddRef-Version der Funktion muss verwendet werden, da die Referenzanzahl dekrementiert wird, wenn die temporäre Referenz automatisch freigegeben wird.
Nehmen wir zum Beispiel an, dass ein Objekt einige Eigenschaften jede Sekunde aktualisieren muss. Ein Timer hält eine Referenz auf die Rückruffunktion, die das Objekt als gebundenen Parameter hat. Normalerweise würde dies verhindern, dass das Objekt gelöscht wird, bevor der Timer gelöscht wird. Das Speichern eines Pointers anstelle einer Referenz ermöglicht das Löschen des Objekts unabhängig vom Timer, so dass es automatisch von __New und __Delete verwaltet werden kann.
a := EineKlasse() Sleep 5500 ; Den Timer 5 Mal laufen lassen. a := "" Sleep 3500 ; Exit temporär verhindern, um den Stop des Timers zu zeigen. class EineKlasse { __New() { ; Das Closure muss gespeichert werden, damit der Timer später ; gelöscht werden kann. Jedes Mal, wenn die Methode aufgerufen ; werden muss, wird eine gezählte Referenz synthetisiert. this.Timer := (p => ObjFromPtrAddRef(p).Update()).Bind(ObjPtr(this)) SetTimer this.Timer, 1000 } __Delete() { SetTimer this.Timer, 0 ; Wenn dieses Objekt wirklich gelöscht wird, werden alle Eigen- ; schaften gelöscht und die folgende __Delete-Methode aufgerufen. ; Dient nur zur Bestätigung und wird normalerweise nicht verwendet. this.Test := {__Delete: test => ToolTip("Objekt gelöscht")} } ; Dies soll nur zeigen, dass der Timer läuft. ; Theoretisch könnte diese Klasse auch einen anderen Zweck haben. count := 0 Update() => ToolTip(++this.count) }
Ein Nachteil dieses Ansatzes ist, dass der Pointer nicht direkt als Objekt verwendbar ist und weder von Type noch vom Debugger als solches erkannt wird. Das Skript muss absolut sicher sein, dass der Pointer nach dem Löschen des Objekts nicht mehr verwendet wird, da dies ungültig ist und das Ergebnis undefiniert wäre.
Wenn die Pointerreferenz an mehreren Stellen benötigt wird, kann es sinnvoll sein, sie zu kapseln. Zum Beispiel würde b := ObjFromPtrAddRef.Bind(ObjPtr(this))
eine BoundFunc erzeugen, die aufgerufen werden kann (b()
), um die Referenz abzurufen, während ((this, p) => ObjFromPtrAddRef(p)).Bind(ObjPtr(this))
als Eigenschafts-Getter verwendet werden kann (die Eigenschaft würde eine Referenz zurückgeben).
Ungezählte Referenzen: Wenn sich die Referenzanzahl des Objekts auf eine Referenz bezieht, sprechen wir von einer gezählten Referenz, andernfalls von einer ungezählten Referenz. Letzteres soll es dem Skript ermöglichen, eine Referenz zu speichern, die das Löschen des Objekts nicht verhindert.
Hinweis: Hier geht es darum, wie sich die Referenzanzahl des Objekts gemäß Skriptlogik zu einer bestimmten Referenz verhält, nicht um die Art der Referenz selbst. Das Programm wird weiterhin versuchen, die Referenz automatisch zu einem bestimmten Zeitpunkt freizugeben, daher sind die Begriffe schwache Referenz und starke Referenz unpassend.
Eine gezählte Referenz kann in eine ungezählte Referenz umgewandelt werden, indem die Referenzanzahl des Objekts einfach dekrementiert wird. Dies muss rückgängig gemacht werden, bevor die Referenz freigegeben wird, was wiederum geschehen muss, bevor das Objekt gelöscht wird. Da der Zweck einer ungezählten Referenz darin besteht, die Löschung des Objekts zu ermöglichen, ohne zuerst die Referenz manuell ungesetzt zu machen, muss die Anzahl in der Regel innerhalb der eigenen __Delete-Methode des Objekts korrigiert werden.
Zum Beispiel können __New und __Delete aus dem vorherigen Beispiel durch die folgenden ersetzt werden.
__New() { ; Die BoundFunc muss gespeichert werden, damit der Timer später ; gelöscht werden kann. SetTimer this.Timer := this.Update.Bind(this), 1000 ; Referenzanzahl dekrementieren, um das von Bind erfolgte AddRef ; zu kompensieren. ObjRelease(ObjPtr(this)) } __Delete() { ; Referenzanzahl inkrementieren, damit die Referenz innerhalb der ; BoundFunc sicher freigegeben werden kann. ObjPtrAddRef(this) ; Timer löschen, um die Referenz auf die BoundFunc freizugeben. SetTimer this.Timer, 0 ; BoundFunc freigeben. Dies geschieht evtl. nicht automatisch ; aufgrund des Referenzzyklus, der jetzt besteht, ; da die Referenz in der BoundFunc erneut gezählt wurde. this.Timer := unset ; Wenn dieses Objekt wirklich gelöscht wird, werden alle Eigen- ; schaften gelöscht und die folgende __Delete-Methode aufgerufen. ; Dient nur zur Bestätigung und wird normalerweise nicht verwendet. this.Test := {__Delete: test => ToolTip("Objekt gelöscht")} }
Dies kann generell angewendet werden, unabhängig davon, wo die ungezählte Referenz gespeichert ist und wofür sie verwendet wird. Die wichtigsten Punkte sind:
Die Referenzanzahl muss so oft inkrementiert und dekrementiert werden, wie es Referenzen gibt, die als ungezählt gelten sollen. Dies kann unpraktisch sein, wenn das Skript nicht genau vorhersagen kann, wie viele Referenzen von einer Funktion gespeichert werden.
Bei der Erstellung eines Objekts wird ein Teil des Speichers reserviert, um die Grundstruktur des Objekts zu speichern. Diese Struktur ist im Wesentlichen das Objekt selbst, weshalb wir ihre Adresse als Objektpointer bezeichnen. Eine Adresse ist ein Integerwert, der einer Stelle im virtuellen Speicher des aktuellen Prozesses entspricht und nur solange gültig ist, bis das Objekt gelöscht wird.
In seltenen Fällen kann es erforderlich sein, dass ein Objekt via DllCall an einen externen Code übergeben werden muss oder dass ein Objekt in eine binäre Datenstruktur für späteren Gebrauch gespeichert werden muss. Die Adresse eines Objekts kann via Adresse := ObjPtr(meinObjekt)
abgerufen werden, allerdings werden dadurch effektiv zwei Referenzen zum Objekt erzeugt, während das Programm selbst nur die eine Referenz in meinObjekt kennt. Das Objekt wird gelöscht, sobald die letzte bekannte Referenz zum Objekt freigegeben wird. Folglich muss das Skript dem Objekt mitteilen, dass es eine Referenz erhalten hat. Dies kann wie folgt erreicht werden (die folgenden zwei Zeilen sind äquivalent):
ObjAddRef(Adresse := ObjPtr(meinObjekt)) Adresse := ObjPtrAddRef(meinObjekt)
Außerdem muss das Objekt informiert werden, wenn das Skript mit dieser Referenz fertig ist:
ObjRelease(Adresse)
Generell sollte jede neue Kopie einer Objektadresse als separate Referenz zum Objekt behandelt werden, d.h. das Skript sollte ObjAddRef aufrufen, wenn es eine Kopie erhält, und sofort ObjRelease aufrufen, bevor es eine Kopie verliert. Zum Beispiel sollte immer ObjAddRef aufgerufen werden, wenn eine Adresse mit so etwas wie x := Adresse
kopiert wird. Ebenso sollte das Skript ObjRelease aufrufen, wenn es mit x fertig ist (oder dabei ist, den Wert von x zu überschreiben).
Mit der ObjFromPtr-Funktion kann eine Adresse in eine geeignete Referenz umgewandelt werden:
meinObjekt := ObjFromPtr(Adresse)
ObjFromPtr vermutet, dass Adresse eine gezählte Referenz ist, und nimmt sie in Besitz. Mit anderen Worten, meinObjekt := ""
bewirkt, dass die Referenz, ursprünglich durch Adresse repräsentiert, freigegeben wird. Danach muss Adresse als ungültig betrachtet werden. Um stattdessen eine neue Referenz zu erstellen, verwenden Sie eine der folgenden Möglichkeiten:
ObjAddRef(Adresse), meinObjekt := ObjFromPtr(Adresse) meinObjekt := ObjFromPtrAddRef(Adresse)