VBAにおいてセル(=Rangeオブジェクト)を扱う際、
「.Value」を書いても書かなくても同じ動きになることが多いです。
Rangeオブジェクトを様々な処理で扱うときに、
- 「.Value」はどんな時に書く必要があるのか
- 書く必要がなかったとして省略しても良いのか
について解説します。
- 「セル」と「セル.Value」は同じもの?
- セル範囲の場合は.Valueの有無を明確に
- .Valueを付けてはいけない場合
- .Valueを付けないといけない場合
- .Valueを省略しても同じ動きになる場合
- .Valueを省略しても良いか
「セル」と「セル.Value」は同じもの?
.Valueを省略してよいかを考える上で重要になるのが、
そもそも「.Value」を書いた時と書かない時の仕様上の違いです。
実はセルとセル.Valueは全く違うもので、
- .Valueあり : セルの値
- .Valueなし : セルそのもの
という違いになっています。
ただここでややこしいのが、VBAの仕様として、
「値を使う処理にセルが渡された場合は自動でセル値(=セル.Value)をみてくれる」
という親切設計になっている点です。
つまり、
セルとセル.Valueが同じものとして扱われているわけではなく、
セルをセル.Valueと自動で読み替えてくれる機能がVBAにある
という方がより正しい理解ですので、まずはそこを抑えておいてください。
セル範囲の場合は.Valueの有無を明確に
本記事は.Valueの有無による「セルとセル値の違い」を考えていきますが、
その前に「Rangeオブジェクトがセルではなくセル範囲の場合」を整理しておきます。
「Rangeオブジェクトがセル範囲の場合」は、.Valueを付けるかどうかで、
- .Valueあり : セル範囲の値から作った二次元配列
- .Valueなし : セル範囲そのもの
という区分けになります。
セル範囲(Rangeオブジェクト)と配列(Array)ということで、
セルとセル値に比べると、全然違うものな気がしますよね?
なので「配列なら.Valueを書く」「Rangeなら書かない」と、
明確にどちらなのかを示さなければいけないことが多いです。
このValueを省略するとエラーになってしまう最たる例が、
↓の「Valueプロパティを使った値貼り付け」になります。
ws1.Range("A1:C3").Value = ws2.Range("A1:C3").Value
この処理は厳密には「右辺で配列を生成し、それを左辺に代入」しているため、
右辺の.Valueは省略することができません。
他の例として、
Sub 配列を受け取って動く処理(Arr As Variant)
↑こんな風に配列を処理するプロシージャを作ったときに、
引数をVariantで受け取ることはよくあるのですが、これをCallする際、
Call 配列を受け取って動く処理(セル範囲)
としてしまうとRangeオブジェクトが渡って不具合となることがあります。
上記のようにセル範囲(Range)なのか配列(Array)なのかは全く違うものですし、
エラー云々以前に、どちらなのかを明示しないと非常に読みづらいです。
Rangeオブジェクトがセル範囲の場合は、
.Valueをつける/つけないを明確に使い分けてください。
.Valueを付けてはいけない場合
さてここからセル.Valueのそれぞれのパターンの考察に入ります。
まずはValueプロパティを付けてはいけない場面ですが、
これは「処理がセル(オブジェクト)を必要としているとき」です。
例えばCopyメソッドで行先を指定する「Destination」は、
Range("A1").Copy Destination:=Range("B1").Value
と書くとエラーになります。
まあこれは当然と言えば当然で、どこにコピーするか聞かれているのに、
コピー先の値をもらってもCopyメソッドさんは困りますよね。
同じようなものとして、
- 並び替えでどの列をキーとするかを示す「Sortメソッドの引数Key」
- Hyperlinkを設置する場所を示す「Addメソッドの引数Anchor」
など、「どのセルを処理するか?」を指定する際.Valueを付けるとエラーになります。
この「Valueを付けてはいけない場面」はイメージしやすいですね。
.Valueを付けないといけない場合
次は逆に「Valueプロパティを付けないといけない場合」ですが、
「値とオブジェクトをどちらも受け取る処理に値を渡したいとき」が該当します。
身近な例としては、「セル値からシート名を取得する」ようなときに、
Valueプロパティを省略すると「型が一致しません」エラーになります。
Worksheets(Range("A1")) ' ← 型が一致しません。 Worksheets(Range("A1").Value) ' ← OK
これはWorksheets(x)がオブジェクトも受け取る仕様になっているからで、
同様にWorkbooks(セル.Value)の.Valueも省略できません。
数値か文字列しか使えないのにオブジェクトも受け取るのは不親切な気がしますが、
そういう仕様なのは仕方ありませんので注意してください。
同じような仕様で、CollectionやDictionaryのKey、Itemも、
Dictionary変数.Add Cells(R, 1), Cells(R, 2)
このようにValueを省略してしまうとKeyやItemにセルそのものが入ってしまい、
エラーにはならないのですが、連想配列としてはうまく機能しなくなります。
※ 上記の例では「key:みかん、Item:100円と登録した」つもりが、
「key:A1セル、Item:B1セルと登録した」ことになってしまい、
次に「みかん」が別セルで登場しても、同一Keyだと認識しなくなります。
このように「値でもオブジェクトでも動く処理」においては、
ただRangeオブジェクトを渡すとセルそのものが渡ってしまうので、
「値」を使いたい場合は.Valueが省略できません。
ちなみに「値でもオブジェクトでも動く処理」の面白い例として、
値もオブジェクトもいれていい「Variant変数」があります。
このVariant変数でも.Valueの省略問題は発生するのですが、
↓のコードがどんな処理になるか、ちょっと考えてみてください。
Dim A1セル Set A1セル = Range("A1") A1セル = 1
最後の「A1セル = 1」は一見セル値を1にしているように見えますが、
実は「A1セルをRange型からLong型に変更して1を代入する」処理になっています。
Variantはどんなものも代入できるわけですので、
「普通に1が入ってしまった」ということですね。
この時も、「A1セル.Value = 1」と書かないといけません。
といいつつも、この例の場合は.Valueが悪いというよりは、
「Dim A1セル As Range」をちゃんと書け
というのが正しい解釈かもしれませんが。
このVariantの仕様はレアケースなのでさておき、
値とオブジェクトをどちらも受け取る処理に値を渡したいときには、
Valueプロパティが省略できません。
エラーが出てくれるならまだしも、エラーも出てくれないことも多いため、
この仕様には十分注意しておきましょう。
.Valueを省略しても同じ動きになる場合
さて最後に.Valueを書いても省略しても同じ動きになる場合ですが、
これは「処理が値しか受け取らない」場合になります。
この時はセルそのものを渡しても、VBAさんが自動でセル値として読んでくれるので、
.Valueを書いても書かなくても同じ動きになります。
この「処理が値しか受け取らない」例で、最も頻出なのはただの演算ですかね。
Cells(R, 4) = Cells(R, 2) * Cells(R, 3)
この例のように、「掛け算」などの四則演算をした場合は、
値しか使えませんのでValueを省略してもセル値が使われます。
また上記コードで行っている「セル値の書き換え=Valueプロパティへの代入」も、
Valueを省略しても問題がありません。
※ 左辺に「Set」と書かない限りオブジェクトが使われることはないため
また四則演算以外の演算として頻出の「比較」でも、.Valueは省略できます。
If Cells(R, 2) = Cells(R - 1, 2) Then
※ 「=」は値の比較演算子であり、オブジェクトの比較演算子は「Is」のため
このように、値しか使えない処理においては、
.Valueを省略しても自動で.Value(=値)が使われます。
.Valueを省略しても良いか
ここまでをまとめると、
- セル範囲と配列(Array)は区別すべきなので.Valueの有無を明確に
- 行先や設置場所などセルそのものが必要なら.Valueは書かない
- 値とオブジェクトどちらでも動く処理に値を渡すなら.Valueは必須
- 値しか使えない処理なら.Valueはあってもなくても同じ処理
ということでした。
さて最後に「省略しても同じ処理だったとして、省略しても良いのか」ですが、
こちらは「安全性」や「コードの読みやすさ」で決める部分になります。
なので最終的には「書き手が好きにしてよい」部分かなと思いますが、
ここにきていきなり丸投げもどうかと思いますので、
参考までに私の考え方を記載しておきます。
まず「何かの引数」にセル値を渡す場合はValueを省略しません。
これは「値しか受け取らなそうな引数でも意外とオブジェクトを受け取る」からです。
上の例で出した「Worksheets(セル.Value)」がまさにそうなのですが、
これが「実はオブジェクトも受け取る」ってイメージしづらいですよね?
実際は数値(シート番号)か、文字列(シート名)でしか動かせないのに。
※ これは「Worksheets(x)が実はWorksheets.Item(x)の省略」で、
「WorksheetsプロパティはWorksheetsコレクションを取得しているだけ」
ということに起因しています。
ということで、こっちは「値しか使えない場所」と思っているのに、
「実はオブジェクトも行ける」とセルごと持っていかれても困るので、
メソッドやプロパティの引数では.Valueを書くことが多いです。
逆に「演算」をしているときはValueを省略することが多いです。
SetやIsがないならぱっと見でもオブジェクトには見えないからですね。
↓のコードくらいであれば、上の方が読みやすく書くのも楽と思うため上。
Cells(R, 4) = Cells(R, 2) * Cells(R, 3) Cells(R, 4).Value = Cells(R, 2).Value * Cells(R, 3).Value
さらにEnumを使って↓にしたときなどは、
横スクロール量も可読性の重要なポイントだと思っているため省略します。
ws売上データ.Cells(R, 売上データの列.金額) = ws売上データ.Cells(R, 売上データの列.単価) * ws売上データ.Cells(R, 売上データの列.個数) ws売上データ.Cells(R, 売上データの列.金額).Value = ws売上データ.Cells(R, 売上データの列.単価).Value * ws売上データ.Cells(R, 売上データの列.個数).Value
同様にIfステート内の比較も横スクロールを減らすために省略することが多いです。
If ws売上データ.Cells(R, 売上データの列.金額) >= 1000000 Then
しかし同じ処理をSelect Caseで書くときは、
Valueがあった方が読みやすい気がして書いています。
Select Case ws売上データ.Cells(R, 売上データの列.金額).Value Case 0 To 1000000
上記の2例は「Range、Cellsをその場で取得して使っている場合」で、
コード量が多いと可読性を損なうのでValueを省略していますが、
セルを変数に入れて使う場合は、.Valueを書くことが多いです。
これは「変数への代入」に見えてしまわないようにというのが大きいですね。
金額セル = 単価セル * 個数セル 金額セル.Value = 単価セル.Value * 個数セル.Value ' ↑こうなると下の方が読みやすい
加えて変数に入れていると、
- コード量が減っているのでそこまで横スクロールを気にしなくていい
- Cells使用時と違って入力候補から.Valueが打てるので書くのが面倒ではない
あたりもあるため、ちゃんと.Valueを書いていることが多い気がします。
以上、私はこんなルールでやっている気がしますが、
正直気分でコロコロ変わる部分です。
使い捨てマクロを「めんどくせっ」て思いながら書いてるときは、
ひとつもValue書かなかったりしてるでしょうし。
何が読みやすいかも人それぞれですし、
どこまでしっかり作るマクロなのかにもよる部分です。
結論、好きなように書いたらいいと思いますので、
自分なりのやり方を考えてみてください。