Setが必要かどうか見分ける方法
メソッドやプロパティがあるかどうかで判定する
変数への代入の際に、Setステートメントを付けるかどうかを判定する方法には2通りあります。
1つは、代入しようとしている変数にメソッドやプロパティがあるかないかです。
VBAでよく利用するオブジェクトといえばセル範囲を表すRangeオブジェクトですが、Valueプロパティなどがありますので、Rangeオブジェクト変数の代入はSetが必要、と判断できます。
このようにメソッドやプロパティがあることをわかっていれば、Setを付けて、なければ付けません。
対象の変数がメソッドやプロパティを持っているかどうかわからない場合は、具体的には、=の右側の変数にドット(.)を付けてメソッドやプロパティが候補として表示されるならSetを付けて、表示されなければSetは付けない、という判定方法です。
例えばActiveCellプロパティを代入する場合ですが、ActiveCellプロパティはRangeオブジェクトを返すため、Rangeオブジェクトが持つValueプロパティなどを参照することが可能です。
そのため、以下のようにSetステートメントを付けて代入します。
1 2 |
Dim r As Range Set r = ActiveCell '// ActiveCell.ValueなどのプロパティがあるのでSetを付ける |
それとは逆に、以下のように=の右側の変数がInteger型やDate型のようにプロパティやメソッドを持たない型を代入する場合は、Setは不要です。
1 2 |
Dim d As Date d = Date '// システム日付を代入 |
IsObject関数で判定する
もう1つの判定方法はIsObject関数を使う方法です。
IsObject関数の引数として対象の変数を渡すと、オブジェクトの場合はTrueを返し、そうでないIntergerやLong型などの場合はFalseを返します。
以下のようにIsObject関数に調べたい変数を渡して、オブジェクト変数と判定されたらSetを付けて、そうでなければ付けない、という書き方も可能です。
1 2 3 4 5 6 7 8 9 10 |
Dim x Dim v Set x = Range("A1") If IsObject(x) = True Then Set v = x '// xはRangeオブジェクトのためこちらが処理される Else v = x End If |
なぜSetを付けるかどうかが分かりにくいのか
Setステートメントの説明では「オブジェクトの代入にはSetを使う」というような説明が書いてあったりしますが、その説明で分かる人はオブジェクト指向に対する理解がある人に限定されます。
オブジェクト指向、というかオブジェクト指向プログラミングが理解できる人はJavaやC++などのプログラミング言語でそれなりの経験がある本職プログラマーがほとんどでしょう。
そのため、それ以外の方は「オブジェクト」という言葉で説明されてもなかなか伝わらないと思います。
なので「オブジェクト」なんて言い方ではなく、「クラス」と言った方がいいと感じています。
クラスには必ずではありませんが通常はプロパティやメソッドが実装されます。
なので、「クラスを代入する場合はSetが必要」、と思っておけばOKです。
こう書くと本職の方から「クラスとインスタンスは違うでしょ」などの突っ込みがありそうですが、そのあたりの知識は別途学習していただくとして、あくまでも「Setっていつ付けるの?」ということを知りたい方を対象としていますのでご了承ください。
Setは参照渡し
上で「代入」と書きましたが、実際にはSetステートメントでは代入は代入ですが値渡しではなく参照渡しになります。
値渡しの場合は代入後に代入元の値が変わっても代入された時点の値が保持されますが、参照渡しの場合は代入後に代入元の値が変わると、代入された変数の値も変わります。
以下は、A1セルに”abc”と入力したあとにRange型の変数にSetステートメントを使って代入しています。
その後、A1セルに”EFG”と再入力して書き換えています。
そうすると、”abc”のときに代入したRange型の変数の値が”EFG”に変わります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Sub SetRangeTest() Dim r As Range '// A1セルに"abc"と入力 Range("A1").Value = "abc" '// A1セルのRangeオブジェクトをRange型変数rに代入 Set r = Range("A1") '// 変数rでセルの値を出力 Debug.Print r.Value '// abc '// 再度、A1セルに"EFG"と入力 Range("A1").Value = "EFG" '// 変数rでセルの値を出力 Debug.Print r.Value '// abcではなくEFG End Sub |
参照渡しであるため、「セルの変更前の状態を保持しておこう」と思ってSetステートメントを使ってセルのRangeオブジェクトを保持しておいても、残念ながら保持されません。
保持する方法やいくつかありますが、それらの詳細については「VBAのオブジェクトのSetコピーとディープコピー」をご参照ください。