オブジェクト

オブジェクトは、いくつかのプロパティとメソッドを組み合わせたものである。 メソッド.

関連トピック

IsObjectは、値がオブジェクトであるかどうかを判定するために使用できます:

Result := IsObject(expression)

標準的なオブジェクト・タイプのリストについては、組み込みクラスを参照のこと。基本的には2つのタイプがある:

目次

基本的な使い方

Arrays

配列を作成する:

MyArray := [Item1, Item2, ..., ItemN]
MyArray := Array(Item1, Item2, ..., ItemN)

項目 (または配列要素) を取得します:

Value := MyArray[Index]

項目の値を変更する(Indexは1からLengthの間、または同等の逆インデックスでなければならない):

MyArray[Index] := Value

InsertAtメソッドを使用して、指定されたインデックスに1つ以上の項目を挿入します:

MyArray.InsertAt(Index, Value, Value2, ...)

Pushメソッドを使用して、1つまたは複数のアイテムを追加します:

MyArray.Push(Value, Value2, ...)

RemoveAtメソッドを使用して項目を削除します:

RemovedValue := MyArray.RemoveAt(Index)

Popメソッドを使って最後の項目を削除する:

RemovedValue := MyArray.Pop()

Lengthは、配列の項目数を返します。配列の中身をループするには、インデックスを使う方法と、Forループを使う方法がある。事例:

MyArray := ["one", "two", "three"]

; Iterate from 1 to the end of the array:
Loop MyArray.Length
    MsgBox MyArray[A_Index]

; Enumerate the array's contents:
For index, value in MyArray
    MsgBox "Item " index " is '" value "'"
    
; Same thing again:
For value in MyArray
    MsgBox "Item " A_Index " is '" value "'"

Maps (Associative Arrays)

Mapまたは連想配列は、一意のキーと値のコレクションを含むオブジェクトで、各キーは1つの値と関連付けられています。キーは文字列、整数、オブジェクトのいずれでもよく、値はどのような型でもよい。連想配列は次のようにして作ることができる:

MyMap := Map("KeyA", ValueA, "KeyB", ValueB, ..., "KeyZ", ValueZ)

項目を取得する。キー変数または

Value := MyMap[Key]

項目を割り当てます:

MyMap[Key] := Value

Deleteメソッドを使用して項目を削除します:

RemovedValue := MyMap.Delete(Key)

項目を列挙する:

MyMap := Map("ten", 10, "twenty", 20, "thirty", 30)
For key, value in MyMap
    MsgBox key ' = ' value

オブジェクト

オブジェクトはプロパティアイテム(配列要素など)を持つことができる。項目へのアクセスは、前のセクションで示したように[]を使用する。プロパティは通常、ドットの後に識別子(単なる名前)を書いてアクセスする。メソッドは、呼び出すことができるプロパティである。

例:

文字どおりPropertyという名前のプロパティを取得または設定します:

Value := MyObject.Property
MyObject.Property := Value

または変数の評価によって名前が決定されるプロパティを取得または設定します:

Value := MyObject.%Expression%
MyObject.%Expression% := Value

文字通りMethodという名前のプロパティ/メソッドを呼び出す:

ReturnValue := MyObject.Method(Parameters)

式または変数の評価によって名前が決定されるプロパティ/メソッドを呼び出す:

ReturnValue := MyObject.%Expression%(Parameters)

プロパティの取得や割り当ての際にパラメータを受け付けることがある:

Value := MyObject.Property[Parameters]
MyObject.Property[Parameters] := Value

オブジェクトはインデックスをサポートすることもある:MyArray[Index]は、実際にはMyArray__Itemプロパティを呼び出し、Indexをパラメータとして渡す。

オブジェクト・リテラル

オブジェクト・リテラルをの中で使うと、即席のオブジェクトを作ることができる。オブジェクト・リテラルは、カンマで区切られた名前と値のペアのリストを中括弧({})で囲んだものである。各ペアは、コロン(:)で区切られたリテラル(引用符で囲まれていない)プロパティ名と値(サブ式)で構成されます。事例:

Coord := {X:13, Y:240}

これは等価だ:

Coord := Object()
Coord.X := 13
Coord.Y := 240

各名前と値のペアは、値のプロパティを定義させるが、例外としてBaseを設定することができる(通常の代入と同じ制限がある)。

名前の置換は、または変数を評価することによってプロパティ名を決定することができます。事例:

parts := StrSplit("key = value", "=", " ")
pair := {%parts[1]%:parts[2]}
MsgBox pair.key

オブジェクトの解放

スクリプトは明示的にオブジェクトを解放しない。オブジェクトへの最後の参照が解放されると、オブジェクトは自動的に解放される。変数に格納された参照は、その変数に他の値が代入されると自動的に解放される。事例:

obj := {}  ; Creates an object.
obj := ""  ; Releases the last reference, and therefore frees the object.

同様に、プロパティや配列要素に格納された参照は、そのプロパティや配列要素に他の値が割り当てられたり、オブジェクトから削除されたりすると解放される。

arr := [{}]  ; オブジェクトを含んだ配列を作ります。
arr[1] := {}  ; Creates a second object, implicitly freeing the first object.
arr.RemoveAt(1)  ; Removes and frees the second object.

オブジェクトを解放する前に、オブジェクトへの参照をすべて解放しなければならないので、循環参照を含むオブジェクトは自動的に解放されない。例えば、x.childyを参照し、y.parentxを参照している場合、xyをクリアするだけでは不十分である。この状況を解決するには、循環参照を削除する。

x := {}, y := {}             ; Create two objects.
x.child := y, y.parent := x  ; Create a circular reference.

y.parent := ""               ; The circular reference must be removed before the objects can be freed.
x := "", y := ""             ; Without the above line, this would not free the objects.

より高度な使い方や詳細については、参照カウントを参照のこと。

延長使用

配列の配列

「多次元」配列はサポートされていないが、スクリプトは複数の配列やマップを組み合わせることができる。事例:

grid := [[1,2,3],
         [4,5,6],
         [7,8,9]]
MsgBox grid[1][3] ; 3
MsgBox grid[3][2] ; 8

カスタム・オブジェクトは、__Itemプロパティを定義することで、多次元サポートを実装することができます。事例:

class Array2D extends Array {
    __new(x, y) {
        this.Length := x * y
        this.Width := x
        this.Height := 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.Width * (y-1) + x
}

grid := Array2D(4, 3)
grid[4, 1] := "#"
grid[3, 2] := "#"
grid[2, 2] := "#"
grid[1, 3] := "#"
gridtext := ""
Loop grid.Height {
    y := A_Index
    Loop grid.Width {
        x := A_Index
        gridtext .= grid[x, y] || "-"
    }
    gridtext .= "`n"
}
MsgBox gridtext

本当のスクリプトは、エラーチェックを行い、列挙をサポートする__Enumのような他のメソッドをオーバーライドすべきである。

カスタムオブジェクト

カスタム・オブジェクトを作成するには、一般的に2つの方法がある:

メタファンクションは、オブジェクトの振る舞いをさらにコントロールするために使うことができる。

注:このセクションでは、オブジェクトとは Objectクラスのインスタンスを指します。このセクションはCOMオブジェクトには適用されない。

アドホック

プロパティとメソッド(呼び出し可能なプロパティ)は通常、いつでも新しいオブジェクトに追加できる。例えば、1つのプロパティと1つのメソッドを持つオブジェクトは次のように構築される:

; Create an object.
thing := {}
; Store a value.
thing.foo := "bar"
; Define a method.
thing.test := thing_test
; Call the method.
thing.test()

thing_test(this) {
    MsgBox this.foo
}

同様に、thing := {foo:"bar"}. {property:value}表記を使う場合、プロパティに引用符を使ってはならない。

thing.test()が呼ばれると、thingは自動的にパラメータ・リストの先頭に挿入される。慣習上、関数はオブジェクトの「タイプ」とメソッド名を組み合わせて命名されるが、これは必須条件ではない。

上記の例では、testが定義された後に他の関数や値が割り当てられる可能性があり、その場合、元の関数は失われ、このプロパティを通じて呼び出すことはできない。別の方法として、以下のように読み取り専用のメソッドを定義することもできる:

thing.DefineProp 'test', {call: thing_test}

こちらもご覧ください:DefineProp

代表

オブジェクトはプロトタイプベース。つまり、オブジェクト自体で定義されていないプロパティは、代わりにオブジェクトのベースで定義することができる。これは、委譲による継承または差分継承と呼ばれるもので、オブジェクトは、そのオブジェクトを異なるものにする部分のみを実装し、残りをベースに委譲することができるからである。

ベース・オブジェクトは一般にプロトタイプとも呼ばれるが、「クラスのプロトタイプ」はクラスのすべてのインスタンスがベースとするオブジェクトを意味し、「ベース」はインスタンスがベースとするオブジェクトを意味する。

AutoHotkeyのオブジェクトデザインは、主にJavaScriptとLuaの影響を受けており、C#も少し含まれています。JavaScriptのobj.__proto__の代わりにobj.base<//c0>を使い、JavaScriptのfunc.prototype<//c6>の代わりにcls.Prototype<//c4>を使います。(クラス・オブジェクトはコンストラクタ関数の代わりに使われる)。

オブジェクトのベースは、そのタイプやクラスを識別するためにも使われる。例えば、x := []Array.Prototype基づくオブジェクトを作成する。これは、xがArrayであり、x.HasBase(Array.Prototype)が真であり、type(x)が "Array"を返すことを意味する。各クラスのPrototypeはベース・クラスのPrototypeに基づいているので、x.HasBase(Object.Prototype)も真になる。

Objectのインスタンスや派生クラスはすべてベース・オブジェクトになることができるが、オブジェクトは同じネイティブ・タイプを持つオブジェクトのベースとしてのみ割り当てることができる。これは、組み込みメソッドが常にオブジェクトのネイティブ・タイプを識別し、正しいバイナリ構造を持つオブジェクトに対してのみ操作できるようにするためである。

ベース・オブジェクトは2つの異なる方法で定義できる:

ベース・オブジェクトは他のオブジェクトのベース・プロパティに割り当てることができますが、通常、オブジェクトのベースはオブジェクトが作成されるときに暗黙的に設定されます。

ベースオブジェクトの作成

どんなオブジェクトでも、同じネイティブ・タイプを持つ他のオブジェクトのベースとして使うことができる。次の例は、アドホック(Ad Hoc)の前の例を基にしたものである(実行する前にこの2つを組み合わせてください):

other := {}
other.base := thing
other.test()

この場合、otherthingからfootestを継承する。この継承は動的なので、thing.fooが変更されると、その変更はother.fooに反映される。スクリプトがother.fooに代入した場合、その値はotherに格納され、thing.fooをさらに変更してもother.fooには影響しません。other.test()が呼び出されると、thisパラメータにはthingの代わりにotherへの参照が含まれる。

クラス

オブジェクト指向プログラミングでは、クラスはオブジェクトを作成するための拡張可能なプログラム・コード・テンプレートであり、状態の初期値(メンバ変数)や動作の実装(メンバ関数またはメソッド)を提供する。ウィキペディア

より一般的な言い方をすれば、クラスとは、何らかの特性や属性を共通に持つ物事の集合やカテゴリーのことである。AutoHotkeyでは、クラスはそのクラスのインスタンスで共有されるプロパティ(および呼び出し可能なプロパティであるメソッド)を定義します。インスタンスとは、クラスからプロパティを継承したオブジェクトのことで、通常はそのクラスに属していることを識別することもできます(instanceisClassNameという表現など)。インスタンスは通常、ClassName()を呼び出して作成します。

オブジェクト動的プロトタイプベースなので、各クラスは2つの部分で構成される:

  • クラスにはPrototypeオブジェクトがあり、このオブジェクトを基にクラスのすべてのインスタンスが作られる。クラスのインスタンスに適用されるすべてのメソッドとダイナミック・プロパティは、プロトタイプ・オブジェクトに含まれている。これには、staticキーワードを持たないすべてのプロパティとメソッドが含まれる。
  • クラスそのものはオブジェクトで、静的メソッドとプロパティだけを含んでいる。これには、staticキーワードを持つすべてのプロパティとメソッド、およびすべてのネストされたクラスが含まれます。これらは特定のインスタンスには適用されず、クラス自体を名前で参照することで使用できる。

以下に、クラス定義のほとんどの要素を示す:

class ClassName extends BaseClassName
{
    InstanceVar := Expression
    
static ClassVar := Expression

class NestedClass
    {
        ...
    }

Method()
    {
        ...
    }
    
static Method()
    {
        ...
    }

Property[Parameters]  ; Use brackets only when parameters are present.
    {
        get {
            return value of property
        }
        set {
            Store or otherwise handle value
        }
    }
    
ShortProperty
    {
        get => Expression which calculates property value
        set => Expression which stores or otherwise handles value
    }
    
ShorterProperty => Expression which calculates property value
}

スクリプトがロードされると、Classオブジェクトが構築され、ClassNameという名前のグローバル定数(読み取り専用変数)に格納されます。extends BaseClassNameがある場合、BaseClassNameは他のクラスの完全な名前でなければならない。各クラスの完全な名前はClassName.Prototype.__Classに格納されます。

クラス自体は変数を通してアクセスされるため、クラス名を使用してクラスを参照し、同じコンテキストで別の変数(クラスのインスタンスを保持するなど)を作成することはできません。例えば、box := Box()は、boxBoxも同じものに解決されるため、機能しない。この方法でトップ・レベルの(入れ子になっていない)クラスを再割り当てしようとすると、ロード・タイム・エラーになります。

この文書では、「クラス」という単語は通常、classキーワードで構築されたクラス・オブジェクトを意味する。

クラス定義には、変数宣言、メソッド定義、ネストされたクラス定義を含めることができます。

インスタンス変数

インスタンス変数とは、クラスの各インスタンスがそれ自身のコピーを持つ変数である。これらは宣言され、通常の代入と同じように振る舞いますが、this.接頭辞は省略されます(クラス本体内でのみ):

InstanceVar := Expression

これらの宣言は、ClassName()でクラスの新しいインスタンスが生成されるたびに評価され、すべての基本クラス宣言が評価された後、__Newが呼ばれる前に評価されます。これは、super.__Init()への呼び出しを含む__Initという名前のメソッドを自動的に作成し、そこに各宣言を挿入することで実現される。したがって、1つのクラス定義に__Initメソッドとインスタンス変数宣言の両方を含めることはできません。

これを介して他のインスタンス変数やメソッドにアクセスできる。グローバル変数は読み込むことはできるが、代入することはできない。式の中で追加の代入(または参照演算子の使用)を行うと、一般的に__Initメソッドのローカル変数が作成される。例えば、x := y := 1とすると、this.xとローカル変数yが設定される(初期化子がすべて評価されると解放される)。

インスタンス変数にアクセスするには(メソッド内であっても)、例えば、this.InstanceVarのようにします。

x.y:=zのような宣言も、xが以前にこのクラスで定義されていればサポートされる。例えば、x := {}, x.y := 42xを宣言し、this.x.yも初期化する。

静的クラス変数

静的変数/クラス変数はクラスそのものに属しますが、その値はサブクラスに継承させることができます。インスタンス変数と同じように、staticキーワードを使って宣言する:

static ClassVar := Expression

これらの宣言は、クラスが初期化されるときに一度だけ評価される。この目的のために、__Initという静的メソッドが自動的に定義される。

各宣言は、クラス・オブジェクトをターゲットとして、通常のプロパティ割り当てとして動作します。は、thisがクラス自身を参照することを除いて、インスタンス変数と同じ解釈をする。

例えば、ClassName.ClassVar := Valueのようにします。サブクラスがその名前のプロパティを所有していない場合、Subclass.ClassVarを使用して値を取得することもできます。したがって、値がオブジェクトへの参照である場合、サブクラスはデフォルトでそのオブジェクトを共有します。ただし、Subclass.ClassVar := yは、ClassNameではなくSubclassに値を格納します。

static x.y := zのような宣言も、xが以前にこのクラスで定義されていればサポートされる。例えば、static x := {}, x.y := 42xを宣言し、ClassName.x.yも初期化します。Prototypeは各クラスで暗黙的に定義されているため、static Prototype.sharedValue := 1を使用すると、クラスのすべてのインスタンスに動的に継承される値を設定できます(インスタンス自体のプロパティによってシャドウされるまで)。

入れ子のクラス

入れ子になったクラス定義では、クラス・オブジェクトを別のグローバル変数ではなく、外側のクラスの静的/クラス変数に関連付けることができます。上記の例では、NestedClassクラスClassオブジェクトを構築し、ClassName.NestedClassに格納しています。Subclasses could inherit NestedClass or override it with their own nested class (in which case WhichClass.NestedClass() could be used to instantiate whichever class is appropriate).

class NestedClass
{
    ...
}

クラスを入れ子にすることは、外側のクラスとの特定の関係を意味するものではない。ネストされたクラスは自動的にインスタンス化されないし、ネストされたクラスのインスタンスは、スクリプトが明示的にその接続を行わない限り、アウター・クラスのインスタンスとの接続を持たない。

Each nested class definition produces a dynamic property with get and call accessor functions instead of a simple value property. This is to support the following behaviour (where class X contains the nested class Y):

  • X.Y() does not pass X to X.Y.Call and ultimately __New, which would otherwise happen because this is the normal behaviour for function objects called as methods (which is how the nested class is being used here).
  • X.Y := 1 is an error by default (the property is read-only).
  • 初めて参照または呼び出すと、クラスが初期化されます。

メソッド

メソッド定義は関数定義と同じように見える。各メソッド定義は、thisという名前の隠された第1パラメータを持つFuncを作成し、メソッドの呼び出しやその関数オブジェクトの取得に使用されるプロパティを定義する。

方法には2種類ある:

  • インスタンス・メソッドは以下のように定義され、クラスのプロトタイプにアタッチされます。メソッドが呼ばれると、これはクラスのインスタンスを参照する。
  • 静的メソッドは、メソッド名の前に個別のキーワードstaticを付けて定義します。これらはクラス・オブジェクト自体に付属していますが、サブクラスにも継承されます。

以下のメソッド定義では、target.DefineProp('Method', {call:funcObj}). デフォルトでは、target.MethodfuncObjを返し、target.Methodに代入しようとするとエラーがスローされます。これらのデフォルトは、プロパティを定義するかDefinePropを呼び出すことでオーバーライドできます。

Method()
{
    ...
}

ファット・アロー構文は、式を返す単一行のメソッドを定義するために使用できます:

Method() => Expression

Super

メソッドやプロパティのゲッター/セッターの内部では、派生クラスでオーバーライドされているメソッドやプロパティのスーパークラスのバージョンにアクセスするために、thisの代わりにsuperキーワードを使用することができます。例えば、上で定義したクラスのsuper.Method()は、通常、BaseClassNameの中で定義されたバージョンのMethodを呼び出します。注:

  • super.Method()は、たとえそれがそのクラスのサブクラスや他のクラスから派生したものであっても、現在のメソッドの元の定義に関連付けられたクラスやプロトタイプオブジェクトのベースを常に呼び出します。
  • super.Method()は、暗黙のうちにこれを最初の(隠し)パラメータとして渡す。
  • ClassNameがベース・オブジェクトの連鎖のどこに存在するか(あるいは存在するかどうか)は不明なので、ClassName自体が開始点として使用される。したがって、super.Method()は、(ClassName.Prototype.base.Method)(this)とほぼ等価である(ただし、Methodがstaticの場合はPrototypeを含まない)。しかし、ClassName.Prototypeはロード時に解決される。
  • そのプロパティがスーパークラスで定義されていないか、呼び出すことができない場合はエラーがスローされる。

キーワードsuperの後には、以下の記号のいずれかをつけなければならない:.[(

super()super.call()と等価である。

属性

プロパティ定義は、単に値を格納したり返したりするのではなく、メソッドを呼び出す動的プロパティを作成します。

Property[Parameters]
{
    get {
        return property value
    }
    set {
        Store or otherwise handle value
    }
}

Propertyは単にプロパティの名前であり、このプロパティを呼び出すために使用される。例えば、obj.Propertygetを呼び、obj.Property :=valuesetを呼びます。getまたはsetの中では、これは呼び出されるオブジェクトを指す。セット内では、valueに代入される値が含まれる。

パラメータは、プロパティ名の右側に角括弧で囲んで定義することができ、同じように渡されます - ただし、パラメータが存在しない場合は省略する必要があります(下記参照)。角括弧を使用することを除けば、プロパティのパラメータはメソッドのパラメータと同じように定義され、オプショナル、ByRef、バリアディックパラメータに対応しています。

プロパティがパラメータ付きで呼び出され、何も定義されていない場合、パラメータは自動的にgetによって返されるオブジェクトの__Itemプロパティに転送される。例えば、this.Property[x]は、(this.Property)[x]またはy := this.Property, y[x]と同じ効果を持つ。空括弧(this.Property[])は、常にPropertyの値の__Itemプロパティを呼び出しますが、this.Property[args*]のようなバリアディック呼び出しは、パラメータの数がゼロでない場合にのみ、この効果を持ちます。

スタティック・プロパティは、プロパティ名の前にstaticという個別のキーワードを付けて定義することができます。その場合、thisはクラスそのものかサブクラスを指す。

setの戻り値は無視される。例えば、val := obj.Property := 42は、例外をスローするかスレッドを終了しない限り、プロパティが何をするかに関係なく、常にval := 42を代入する。

各クラスはプロパティの片方または両方を定義できる。クラスがプロパティをオーバーライドする場合は super.Propertyを使用して、ベース・クラスで定義されたプロパティにアクセスできます。GetまたはSetが定義されていない場合は、ベース・オブジェクトから継承することができる。Getが未定義の場合、プロパティはベースから継承した値を返すことができます。Setがこのオブジェクトとすべてのベースオブジェクトで未定義である場合(または継承された値プロパティによって隠されている場合)、プロパティを設定しようとすると例外がスローされます。

getsetの両方を持つプロパティ定義は、実際には2つの別々の関数を作成し、ローカル変数や静的変数、入れ子関数を共有することはありません。メソッドと同様、各関数にはthisという隠しパラメータがあり、setには valueという2番目の隠しパラメータがある。明示的に定義されたパラメータはその後に来る。

プロパティ定義では、DefinePropと同じようにプロパティのgetsetのアクセサ関数を定義しますが、メソッド定義では呼び出しのアクセサ関数を定義します。どのクラスも、同じ名前のプロパティ定義とメソッド定義を含むことができる。呼び出しアクセッサ関数(メソッド)を持たないプロパティが呼び出された場合、getはパラメータなしで呼び出され、その結果はメソッドとして呼び出される。

ファット・アロー・プロパティーズ

ファット・アロー構文を使用すると、式を返すプロパティ・ゲッターまたはセッターを定義できます:

ShortProperty[Parameters]
{
    get => Expression which calculates property value
    set => Expression which stores or otherwise handles value
}

ゲッターだけを定義する場合、中括弧とgetは省略できる:

ShorterProperty[Parameters] => Expression which calculates property value

いずれの場合も、パラメータが定義されていない限り、角括弧は省略しなければならない。

__Enum Method

__Enum(NumberOfVars)

Enumメソッドは、オブジェクトがforループに渡されるときに呼び出される。このメソッドは、配列要素など、オブジェクトに含まれる項目を返す列挙子を返す必要があります。未定義のままだと、列挙子と互換性のあるCallメソッドを持たない限り、オブジェクトを直接forループに渡すことはできない。

NumberOfVarsには、forループに渡される変数の数が入る。NumberOfVarsが2の場合、列挙者は項目のキーまたはインデックスを最初のパラメータに、値を2番目のパラメータに代入することが期待される。各キーまたはインデックスは、__Itemプロパティのパラメータとして受け入れられるべきである。これにより、DBGpベースのデバッガは、列挙器を呼び出して項目をリストアップした後、特定の項目を取得または設定できるようになる。

__Item Property

Itemプロパティは、インデックス演算子(配列構文)がオブジェクトと共に使用されるときに呼び出される。以下の例では、Envクラス自体でインデックス演算子を使用できるように、プロパティはstaticとして宣言されています。他の例としては、Array2Dを参照。

class Env {
    static __Item[name] {
        get => EnvGet(name)
        set => EnvSet(name, value)
    }
}

Env["PATH"] .= ";" A_ScriptDir  ; Only affects this script and child processes.
MsgBox Env["PATH"]

__Item is effectively a default property name (if such a property has been defined):

  • パラメータがある場合、object[params]object.__Item[params]と等価である。
  • object[]object.__Itemと等価である。

事例:

obj := {}
obj[] := Map()     ; Equivalent to obj.__Item := Map()
obj["base"] := 10
MsgBox obj.base = Object.prototype  ; True
MsgBox obj["base"]                  ; 10

注意: obj.prop[]のように、明示的なプロパティ名が空括弧と組み合わされている場合、それは2つの別々の操作として扱われます:まずobj.propを取得し、次に結果のデフォルト・プロパティを呼び出します。これは言語構文の一部であり、オブジェクトに依存するものではない。

コンストラクタとデストラクタ

ClassName()のデフォルト実装によってオブジェクトが生成されるたびに、新しいオブジェクトの__Newメソッドが呼び出されます。ClassName()に渡されたパラメータはすべて__Newに転送されるため、オブジェクトの初期内容や構築方法に影響を与える可能性があります。オブジェクトが破棄されると、__Deleteが呼ばれる。事例:

m1 := GMem(0, 10)
m2 := {base:GMem.Prototype}, m2.__New(0, 30)

; Note: For general memory allocations, use Buffer() instead.
class GMem
{
    __New(aFlags, aSize)
    {
        this.ptr := DllCall("GlobalAlloc", "UInt", aFlags, "Ptr", aSize, "Ptr")
        if !this.ptr
            throw MemoryError()
        MsgBox "New GMem of " aSize " bytes at address " this.ptr "."
    }

__Delete()
    {
        MsgBox "Delete GMem at address " this.ptr "."
        DllCall("GlobalFree", "Ptr", this.ptr)
    }
}

__Deleteは、"__Class"というプロパティを所有するオブジェクトに対しては呼び出されません。プロトタイプ・オブジェクトはデフォルトでこのプロパティを持っている。

Deleteの実行中に例外または実行時エラーがスローされ、__Delete内で処理されない場合、__Deleteは新しいスレッドから呼び出されたかのように動作します。つまり、エラー・ダイアログが表示され、__Deleteが戻るが、スレッドは終了しない(すでに終了していた場合を除く)。

トレイメニューやExitAppなど、何らかの手段でスクリプトが直接終了した場合、まだ戻っていない関数はその機会を得られない。したがって、これらの関数のローカル変数によって参照されるオブジェクトは解放されないので、__Deleteは呼び出されません。Temporary references on the expression evaluation stack are also not released under such circumstances.

スクリプトが終了すると、グローバル変数と静的変数に含まれるオブジェクトは、実装で定義された任意の順序で自動的に解放されます。この処理中に__Deleteが呼ばれた場合、グローバル変数やスタティック変数はすでに解放されている可能性があるが、オブジェクト自身が含む参照はまだ有効である。したがって、__Deleteは完全に自己完結型とし、グローバル変数や静的変数に依存しないのがベストである。

クラスの初期化

各クラスは、そのクラスへの参照が初めて評価されるときに自動的に初期化される。例えば、MyClassがまだ初期化されていない場合、MyClass.MyPropはプロパティが取得される前にクラスを初期化します。初期化は、2つの静的メソッドを呼び出すことからなる:__Init and __New.

static __Initは、すべてのクラスに対して自動的に定義され、ベース・クラスが指定されていれば常にそのクラスへの参照から始まり、確実に初期化されます。静的/クラス変数ネストされたクラスは、定義された順番に初期化されます。ただし、ネストされたクラスが前の変数やクラスの初期化中に参照された場合は除きます。

クラスがstatic __Newメソッドを定義または継承している場合、 __Init の直後に呼び出されます。__Newは、それが定義されているクラスに対して一度だけ呼び出されることに注意することが重要でありそして、定義されていないサブクラス(またはsuper.__New()を呼び出すサブクラス)ではそれぞれ1回呼び出されるということです。これは、各サブクラスに対して共通の初期化タスクを実行したり、サブクラスを使用する前に何らかの方法で修正したりするために使用できます。

static __Newが派生クラスに対して作用することを意図していない場合は、thisの値をチェックすることで回避できる。場合によっては、this.DeleteProp('__New')のように、メソッド自体を削除すれば十分かもしれません。しかし、__Newの最初の実行は、サブクラスが基本クラスに入れ子になっている場合や、静的/クラス変数の初期化中に参照される場合があります。

クラス定義は、クラスを参照する効果もある。言い換えると、スクリプトの起動中に実行がクラス定義に達すると、__Initと__Newが自動的に呼び出されます。しかし、returnや無限ループなど、実行がクラス定義に到達できない場合は、クラスが参照されている場合のみ初期化される。

一度自動初期化が開始されると、同じクラスで再び初期化が行われることはありません。これは、複数のクラスが互いに参照し合わない限り、一般的には問題にはなりません。例えば、以下の2つのクラスを考えてみよう。Aが最初に初期化されるときは、B.SharedArray(A1)を評価すると、値を取得して返す前にBが初期化されますが、A.SharedValue(A3)は未定義であり、すでに進行中のためAの初期化は起こりません。言い換えれば、Aが最初にアクセスまたは初期化された場合、順番はA1からA3となり、そうでない場合はB1から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
}

Meta-Functions

class ClassName {
    __Get(Name, Params)
    __Set(Name, Params, Value)
    __Call(Name, Params)
}
名前

プロパティまたはメソッドの名前です。

Params

パラメータの配列。これは、()または[]の間のパラメータのみを含むので、空でも差し支えありません。メタファンクションは、x. yが未定義のx.y[z]のようなケースを処理することが期待されます。

割り当てられている値。

メタファンクションは、未定義のプロパティやメソッドが呼び出されたときに何が起こるかを定義します。例えば、obj.unkに値が割り当てられていない場合、__Getメタファンクションを呼び出します。同様に、obj.unk := value__Setを起動し、obj.unk()__Callを起動します。

プロパティとメソッドは、オブジェクト自身またはそのベース・オブジェクトのいずれかに定義することができます。一般的に、すべてのプロパティに対してメタファンクションを呼び出すには、プロパティを定義しないようにしなければなりません。Baseのような組み込みプロパティは、プロパティ定義またはDefinePropでオーバーライドできます。

メタファンクションが定義されているときは、そのメタファンクションは必要なデフォルトアクションを全て実行しなければなりません。例えば、次のようなことが予想される:

  • Call:MethodErrorをスローします。
  • パラメータが与えられたときは、例外をスローします(パラメータを転送するオブジェクトがありません)。
  • Get:PropertyErrorをスローします。
  • Set:DefinePropを呼び出すなどして、指定された値を持つ新しいプロパティを定義する。

呼び出し可能なオブジェクトは、関連するプロパティに代入することで、メタファンクションとして使用することができる。

メタファンクションは以下の場合には呼び出されない:

  • x[y]:プロパティ名なしで角括弧を使用すると、__Itemプロパティのみが呼び出されます。
  • x():オブジェクト自体を呼び出すと、Callメソッドが呼び出されるだけである。これには、SetTimerHotkeyなどの組込関数による内部呼び出しも含まれる。
  • 他のメタファンクションやダブルアンダースコアメソッドへの内部呼び出しは__Callをトリガーしない。

ダイナミック・プロパティ

Property構文DefinePropは、評価されるたびに値を計算するプロパティを定義するために使用できますが、各プロパティは事前に定義する必要があります。対照的に、__Get__Setは、呼び出された瞬間にしかわからないプロパティを実装するために使うことができる。

例えば、プロパティに対するリクエストをネットワーク経由で(あるいは他のチャネル経由で)送信する「プロキシ」オブジェクトを作成することができる。リモートサーバーはプロパティの値を含む応答を送り返し、プロキシはその値を呼び出し元に返す。各プロパティの名前が事前に分かっていたとしても、どのプロパティも同じこと(ネットワーク・リクエストを送信する)を行うので、プロキシ・クラスで各プロパティを個別に定義するのは論理的ではない。メタ・ファンクションはプロパティ名をパラメータとして受け取るので、この問題に対する良い解決策となる。

プリミティブ値

文字列や数値などのプリミティブ値は、独自のプロパティやメソッドを持つことはできない。しかし、プリミティブ値はオブジェクトと同じようなデリゲーションをサポートする。つまり、プリミティブ値に対するプロパティやメソッドの呼び出しは、定義済みのプロトタイプ・オブジェクトに委譲される。以下のクラスは、プリミティブ値に関するものです:

  • Primitive (extends Any)
    • Number
      • Float
      • Integer
    • String

Type文字列をチェックする方が一般的に速いが、値の型は、それが指定されたベースを持っているかどうかをチェックすることによってテストすることができる。例えば、n.HasBase(Number.Prototype)またはn is Numberは、nが純粋なIntegerまたはFloatの場合は真ですが、nが数値文字列の場合は真ではありません。対照的に、IsNumber(n)nが数値または数値文字列の場合に真となる。

ObjGetBaseBaseプロパティは、適切な場合、定義済みのプロトタイプ・オブジェクトの1つを返します。

通常、x is AnyはAutoHotkeyの型階層内のどの値に対してもtrueになりますが、COMオブジェクトに対してはfalseになることに注意してください。

プロパティとメソッドの追加

プロパティとメソッドは、その型のプロトタイプ・オブジェクトを修正することによって、与えられた型のすべての値に対して追加することができる。しかし、プリミティブ値はObjectではなく、独自のプロパティやメソッドを持つことができないため、プリミティブプロトタイプオブジェクトはObject.Prototypeから派生しません。つまり、DefinePropHasOwnPropといったメソッドは、デフォルトではアクセスできない。間接的に呼ばれることもある。事例:

DefProp := {}.DefineProp
DefProp( "".base, "Length", { get:StrLen } )
MsgBox A_AhkPath.length " == " StrLen(A_AhkPath)

プリミティブ値はそのプロトタイプから値プロパティを継承できますが、スクリプトがプリミティブ値に値プロパティを設定しようとすると例外がスローされます。事例:

"".base.test := 1  ; Don't try this at home.
MsgBox "".test  ; 1
"".test := 2  ; エラー: プロパティは読み取り専用です。

Setやプロパティ・セッターを使うこともできるが、プリミティブ値は不変であると考えるべきであるので、これらは有用ではない。

実施

Reference Counting

AutoHotkey uses a basic reference counting mechanism to automatically free the resources used by an object when it is no longer referenced by the script. Understanding this mechanism can be essential for properly managing the lifetime of an object, allowing it to be deleted when it is no longer needed, and not before then.

An object's reference count is incremented whenever a reference is stored. When a reference is released, the count is used to determine whether that reference is the last one. If it is, the object is deleted; otherwise, the count is decremented. The following example shows how references are counted in some simple cases:

a := {Name: "Bob"}  ; Bob's ref count is initially 1
b := [a]            ; Bob's ref count is incremented to 2
a := ""             ; Bob's ref count is decremented to 1
c := b.Pop()        ; Bob is transferred, ref count still 1
c := ""             ; Bob is deleted...

式内の関数、メソッド、または演算子によって返された一時参照は、その式の評価が完了または中止された後に解放されます。次の例では、新しいGMemオブジェクトはMsgBoxが返った後にのみ解放される:

MsgBox DllCall("GlobalSize", "ptr", GMem(0, 20).ptr, "ptr")  ; 20

注:この例では、Ptrarg型がPtrプロパティを持つオブジェクトを許可しているため、.ptrを省略することができました。しかし、上に示したパターンは、他のプロパティ名でも機能する。

オブジェクトへの最後の参照が解放されるときにコードを実行するには、__Deleteメタ関数を実装します。

Problems with Reference Counting

Relying solely on reference counting sometimes creates catch-22 situations: an object is designed to free its resources when deleted, but would only be deleted if its resources are first freed. Specifically, this occurs when those resources are other objects or functions which retain a reference to the object, often indirectly.

A circular reference or reference cycle is when an object directly or indirectly refers to itself. If each reference which is part of the cycle is included in the count, the object cannot be deleted until the cycle is manually broken. For example, the following creates a reference cycle:

parent := {}  ; parent: 1 (reference count)
child := {parent: parent}  ; parent: 2, child: 1
parent.child := child  ; parent: 2, child: 2

If the variables parent and child are reassigned, the reference count for each object is decremented to 1. Both objects would be inaccessible to the script, but would not be deleted because the last references are not released.

A cycle is often less obvious than this, and can involve several objects. For example, ShowRefCycleGui demonstrates a cycle involving a Gui, MenuBar, Menu and closures. The use of a separate object to handle GUI events is also prone to cycles, if the handler object has a reference to the GUI.

Non-cyclic references to an object can also cause issues. For instance, objects with a dependency on built-in functions like SetTimer or OnMessage generally cause the program to hold an indirect reference to the object. This would prevent the object from being deleted, which means that it cannot use __New and __Delete to manage the timer or message monitor.

Below are several strategies for solving issues like those described above.

Avoid cycles: If reference cycles are a problem, avoid creating them. For example, either parent.child or child.parent would not be set. This is often not practical, as related objects may need a way to refer to each other.

When defining event handlers for OnEvent (Gui), avoid capturing the source Gui in a closure or bound function and instead utilize the Gui or Gui.Control parameter. Likewise for Add (Menu) and the callback's Menu parameter, but of course, a menu item which needs to refer to a Gui cannot use this approach.

In some cases, the other object can be retrieved by an indirect method which doesn't rely on a counted reference. For example, retain a HWND and use GuiFromHwnd(hwnd) to retrieve a Gui object. Retaining a reference is not necessary to prevent deletion while the window is visible, as the Gui itself handles this.

Break cycles: If the script can avoid relying on reference counting and instead manage the lifetime of the object directly, it need only break the cycle when the objects are to be deleted:

child.parent := unset  ; parent: 1, child: 2
child := unset  ; parent: 1, child: 1
parent := unset  ; both deleted

Dispose: __Delete is called precisely when the last reference is released, so one might come to think of a simple assignment like myGui := "" as a cleanup step which triggers deletion of the object. Sometimes this is done explicitly when the object is no longer needed, but it is neither reliable nor truly showing the intent of the code. An alternative pattern is to define a Dispose or Destroy method which frees the objects resources, and design it to do nothing if called a second time. It can then also be called from __Delete, as a safeguard.

An object following this pattern would still need to break any reference cycles when it is disposed, otherwise some memory would not be reclaimed, and __Delete would not be called for other objects referenced by the object.

Cycles caused by a Gui object's event handlers, MenuBar or event sink object are automatically "broken" when Destroy is called, as it releases those objects. (This is demonstrated in the ShowRefCycleGui example.) However, this would not break cycles caused by new properties which the script has added, as Destroy does not delete them.

Similar to the Dispose pattern, InputHook has a Stop method which must be called explicitly, so it does not rely on __Delete to signal when its operation should end. While operating, the program effectively holds a reference to the object which prevents it from being deleted, but this becomes a strength rather than a flaw: event callbacks can still be called and will receive the InputHook as a parameter. When operation ends, the internal reference is released and the InputHook is deleted if the script has no reference to it.

Pointers: Storing any number of pointer values does not affect the reference count of the object, since a pointer is just an integer. A pointer retrieved with ObjPtr can be used to produce a reference by passing it to ObjFromPtrAddRef. The AddRef version of the function must be used because the reference count will be decremented when the temporary reference is automatically released.

For example, suppose that an object needs to update some properties each second. A timer holds a reference to the callback function, which has the object bound as a parameter. Normally this would prevent the object from being deleted before the timer is deleted. Storing a pointer instead of a reference allows the object to be deleted regardless of the timer, so it can be managed automatically by __New and __Delete.

a := SomeClass()
Sleep 5500  ; Let the timer run 5 times.
a := ""
Sleep 3500  ; Prevent exit temporarily to show that the timer has stopped.

class SomeClass {
	__New() {
        ; The closure must be stored so that the timer can be deleted later.
; Synthesize a counted reference each time the method needs to be called.
        this.Timer := (p => ObjFromPtrAddRef(p).Update()).Bind(ObjPtr(this))
        SetTimer this.Timer, 1000
    }
	__Delete() {
        SetTimer this.Timer, 0
        ; If this object is truly deleted, all properties will be
        ; deleted and the following __Delete method will be called.
; This is just for confirmation and wouldn't normally be used.
        this.Test := {__Delete:test => ToolTip("object deleted")}
    }
    ; This is just to demonstrate that the timer is running.
; Hypothetically, this class has some other purpose.
    count := 0
	Update() => ToolTip(++this.count)
}

A drawback of this approach is that the pointer is not directly usable as an object, and is not recognized as such by Type or the debugger. The script must be absolutely certain not to use the pointer after the object is deleted, as doing so is invalid and the result would be indeterminate.

If the pointer-reference is needed in multiple places, encapsulating it might make sense. For instance, b := ObjFromPtrAddRef.Bind(ObjPtr(this)) would produce a BoundFunc which can be called (b()) to retrieve the reference, while ((this, p) => ObjFromPtrAddRef(p)).Bind(ObjPtr(this)) can be used as a property getter (the property would return a reference).

Uncounted references: If the object's reference count accounts for a reference, we call it a counted reference, otherwise we call it an uncounted reference. The idea of the latter is to allow the script to store a reference which does not prevent the object from being deleted.

Note: This is about how the object's reference count relates to a given reference as per the script's logic, and doesn't affect the nature of the reference itself. The program will still attempt to release the reference automatically at whatever time it would normally, so the terms weak reference and strong reference are unsuitable.

A counted reference can be turned into an uncounted reference by simply decrementing the object's reference count. This must be reversed before the reference is released, which must occur before the object is deleted. Since the point of an uncounted reference is to allow the object to be deleted without first manually unsetting the reference, generally the count must be corrected within that object's own __Delete method.

For example, __New and __Delete from the previous example can be replaced with the following.

	__New() {
        ; The BoundFunc must be stored so that the timer can be deleted later.
        SetTimer this.Timer := this.Update.Bind(this), 1000
        ; Decrement ref count to compensate for the AddRef done by Bind.
        ObjRelease(ObjPtr(this))
    }
	__Delete() {
        ; Increment ref count so that the ref within the BoundFunc
        ; can be safely released.
        ObjPtrAddRef(this)
        ; Delete the timer to release its reference to the BoundFunc.
        SetTimer this.Timer, 0
        ; Release the BoundFunc. This may not happen automatically
        ; due to the reference cycle which exists now that the ref
        ; in the BoundFunc is counted again.
        this.Timer := unset
        ; If this object is truly deleted, all properties will be
        ; deleted and the following __Delete method will be called.
; This is just for confirmation and wouldn't normally be used.
        this.Test := {__Delete:test => ToolTip("object deleted")}
    }

This can generally be applied regardless of where the uncounted reference is stored and what it is used for. The key points are:

  • Decrement the reference count (Release) after storing the reference.
  • Increment the reference count (AddRef) before unsetting the reference.
  • Explicitly unset the reference before __Delete returns (doing so before it is called is fine).

The reference count must be incremented and decremented as many times as there are references which are intended to be uncounted. This may not be practical if the script cannot accurately predict how many references will be stored by some function.

オブジェクトへのポインタ

As part of creating an object, some memory is allocated to hold the basic structure of the object. This structure is essentially the object itself, so we call its address a pointer to the object. An address is an integer value which corresponds to a location within the virtual memory of the current process, and is valid only until the object is deleted.

まれに、DllCallを介して外部コードにオブジェクトを渡したり、後で取り出すためにバイナリ・データ構造に格納したりする必要がある場合がある。オブジェクトのアドレスは、address := ObjPtr(myObject)で取得することができる。しかし、これは事実上オブジェクトへの参照を2つ作ることになり、プログラムはmyObjectの中の1つしか知らない。オブジェクトへの最後の既知の参照が解放された場合、オブジェクトは削除される。したがって、スクリプトはオブジェクトに参照を得たことを通知しなければならない。これは以下のようにできる(以下の2行は同等):

ObjAddRef(address := ObjPtr(myObject))
address := ObjPtrAddRef(myObject)

スクリプトはまた、その参照を終了するときにオブジェクトに通知しなければならない:

ObjRelease(アドレス)

一般に、オブジェクトのアドレスの新しいコピーはそれぞれ、そのオブジェクトへの別の参照として扱われるべきです。したがって、スクリプトは、コピーを得たときにObjAddRefを呼び出し、コピーを失う直前にObjReleaseを呼び出す必要があります。例えば、x := addressのようにアドレスがコピーされるときは、必ずObjAddRefが呼ばれなければならない。同様に、スクリプトがxの処理を終えたら(あるいはxの値を上書きしようとしたら)、ObjReleaseを呼び出すべきである。

アドレスを適切な参照に変換するには、ObjFromPtr関数を使用します:

myObject := ObjFromPtr(address)

ObjFromPtrは、アドレスがカウントされた参照であると仮定し、その所有権を主張する。つまり、myObject := ""とすると、もともとaddressで表されていた参照が解放される。それ以降のアドレスは無効とみなされる。その代わりに新しいリファレンスを作成するには、以下のいずれかを使用する:

ObjAddRef(address), myObject := ObjFromPtr(address)
myObject := ObjFromPtrAddRef(address)