VBAのVariantを型と実体の違いから理解する

VBAのVariantを型と実体の違いから理解する

型を書かないためではなく、型と値の関係を明示的に読むためにVariantを使う

Copyright © 2026 LWP 山中 一弘

本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。

記事要約

VBAでVariantをどう扱うかは、昔から意見が分かれやすいテーマです。

「Variantは遅い」「型が曖昧で危ない」「初心者向けではない」「きちんと型を書くべきだ」と言われることがあります。一方で、VBAの配列、Rangeの値、関数の戻り値、ParamArray、複数種類の値を受けるライブラリ関数を真面目に扱い始めると、Variantを避け続けるほうがむしろ苦しくなります。

この記事では、Variantを「型を書かないための逃げ」ではなく、「型情報と値をセットで持つVBAの重要なデータコンテナ」として整理します。特に次の観点を中心に見ます。

  • Variantには値だけでなく型情報も入る

  • Variantへ代入すると、代入元の型がVariant側にバインドされる

  • 文字列、配列、オブジェクトは、変数に実体そのものではなく参照値を持つ

  • Variantには単純型、参照型、配列型、配列参照型という見方がある

  • 配列と関数設計では、VariantがVBAの不足をかなり補う

  • ただし、Rangeオブジェクトや深い再帰、大量データでは注意点がある

結論から言うと、VBAでは「全部の変数を機械的に具象型で書く」よりも、「基本はVariantで受け、必要なところだけ具象型にする」ほうが、実務マクロや小さなライブラリでは自然に書ける場面が多くあります。

この記事では、2020年3月21日の発表資料「バリアントの真実と活用事例のご紹介」と、Togetter/Posfieに残っている発表後の補足投稿をもとに、VBAのVariantを内部構造、参照、配列、関数設計、実務上の罠から整理します。

本記事の対象とゴール

想定読者

  • VBAでVariantを使ってよいのか迷っている人

  • Dim x と Dim x As Long の違いを、単なる省略記法以上の意味で理解したい人

  • 配列、Rangeの値、ParamArray、多値戻り値を扱う関数を作りたい人

  • ByRef引数、配列コピー、オブジェクト参照の挙動で混乱したことがある人

  • Variantを禁止するのではなく、使う場所と避ける場所を判断したい人

本記事で得られること

  1. Variantが「型のない変数」ではなく、型情報と値または参照値を持つ容器であることが分かります。

  2. 具象型からVariant、Variantから具象型、Variant同士の代入で何が変わるかを整理できます。

  3. BSTR、SAFEARRAY、オブジェクト参照を、VBAから扱うときの実務的な見方が分かります。

  4. Variantバインド配列とByRef Variantを使う意味が分かります。

  5. Rangeオブジェクト、大量配列、深い再帰など、Variantを避けるべき場面も判断できます。

本記事で扱わないこと

  • COM AutomationのVARIANT/SAFEARRAY仕様をC言語レベルで完全に解説すること

  • VBAランタイム内部のメモリ配置を、すべての型で厳密に検証すること

  • すべての変数をVariantにするコーディング規約を無条件に推奨すること

  • 外部API呼び出しや高速数値計算での型指定最適化

先に結論

Variantは、型がないから便利なのではありません。型情報が変数の中に外出しされ、実行時に「今どの型として振る舞うか」を持てるから便利です。

VBAのVariantは、型情報、予約領域、値または参照値を持ちます。数値なら値そのものを持ち、文字列ならBSTRへの参照を持ち、配列ならSAFEARRAYへの参照を持ち、オブジェクトならオブジェクトへの参照を持ちます。つまりVariantを読むときは、「何型か」だけでなく、「値を持っているのか、参照を持っているのか」を見る必要があります。

実務では、Excel Rangeの値、2次元配列、Array(...)、ParamArray、多値戻り値、汎用ライブラリ関数ではVariantが自然です。一方、Range、Worksheet、Workbookなど明確なExcelオブジェクト、大量の数値配列、外部API呼び出しでは具象型を書いたほうが安全です。

Variantを避けるべきものとして扱うのではなく、Variantが何をバインドしているかを読む。この姿勢を持つと、VBAの配列処理、関数設計、Excelシート連携はかなり楽になります。

Variantは型のない変数ではない

Variantは「型がない変数」と説明されることがあります。しかし、この説明はかなり不正確です。

Variantは、値を入れたときに、その値の型情報も一緒に持つ変数です。つまり、Variantの中には「値」だけではなく「今この値を何型として扱うか」という情報も入っています。

たとえば、次の2つを比べます。

Dim s As String
s = 99
Dim v
v = 99

s = 99 では、代入先がString型なので、数値の99は文字列の "99" へ変換されます。代入先の型はStringのままです。

一方、v = 99 では、Variantである v に、数値の99がその型情報と一緒に入ります。v は「IntegerまたはLongなどの数値として振る舞うVariant」になります。

この違いが重要です。

具象型変数への代入では、代入先の型が先に決まっています。必要なら暗黙の型変換が行われます。Variantへの代入では、代入元の型情報がVariant側に設定されます。だから、Variantは「型がない」のではなく、「実行時に型を持つ」と考えたほうが正確です。

Variantは16バイトの容器である

WindowsのCOMで使われるVARIANT構造体は、基本的に16バイトの構造として扱われます。Microsoft LearnのVARIANT構造体の説明でも、先頭側に VARTYPE vt と予約領域があり、その後ろにLong、Double、BSTR、IUnknown、IDispatch、SAFEARRAY、ByRefポインタなどを入れる共用体があることが確認できます。

この構造をVBA記事向けに単純化すると、中には型を表すフィールドと、値または参照値を入れる領域があります。

概念的には、次のように見ると理解しやすくなります。

Variant
  型情報
  予約領域
  値または参照値

VariantにLongを入れれば、型情報はLongを示し、値領域にはLongの値が入ります。文字列を入れれば、型情報はStringを示し、値領域には文字列実体への参照が入ります。配列を入れれば、型情報には配列であることを示すフラグが入り、値領域には配列を管理する構造体への参照が入ります。

つまり、Variantを理解するには、単に「何型を入れられるか」だけでなく、「その値は実体なのか、参照値なのか」を見る必要があります。

代入で何が起きているか

Variantの挙動は、代入の方向で分けると整理できます。

具象型から具象型への代入

Dim a As String
Dim b As Long
b = 99
a = b

この場合、a はString型の変数です。b の値を a へ入れると、必要に応じて型変換されます。a の型は変わりません。

具象型からVariantへの代入

Dim b As Long
Dim v
b = 99
v = b

この場合、Variantである v は、b の値だけでなく、b の型情報も受け取ります。v はLongとして振る舞います。

ここでは、Stringへ入れるときのような文字列化は起きません。代入元の型をVariant側に設定できるからです。

Variantから具象型への代入

Dim v
Dim s As String
v = 99
s = v

この場合、代入先の s はString型です。v は数値として振る舞うので、Stringに入れる時点で文字列へ変換されます。

Variantは、評価されるときには「今バインドされている型の値」として振る舞います。だから、Variantから具象型へ入れるときは、具象型同士の代入と同じように暗黙の型変換が起こります。

VariantからVariantへの代入

Dim v1
Dim v2
v1 = 99
v2 = v1

Variant同士の代入では、結果として v2 には v1 が持っていた型と値が設定されます。

このとき、単に16バイトをそのままコピーしていると考えることもできますが、実用上は「コピー元Variantを、そのVariantにバインドされている型の値として評価し、その結果をコピー先Variantへ設定する」と考えると整理しやすくなります。

文字列は値型のように見える参照である

VBAの文字列は、見た目には値型のように使えます。

Dim a As String
Dim b As String
a = "abc"
b = a
b = "xyz"

b を変更しても a は変わりません。だから、Stringは値のように見えます。

しかし内部的には、String型変数には文字列データそのものが直接入っているわけではありません。BSTRという文字列領域への参照値が入っています。BSTR側には文字列長や文字データが格納されています。

この「参照を持っているが、代入では値型のように扱える」という性質は、VBAの挙動を読むうえで大切です。

Variantに文字列を入れると、Variantの型情報はStringを示し、値領域にはBSTRへの参照値が入ります。つまり、Variantは「String型として振る舞う参照値」を持つことになります。

配列はSAFEARRAYへの参照である

配列も同じように、変数の中に全要素が直接入っているわけではありません。

VBAの配列は、SAFEARRAYという構造体で管理されています。Microsoft LearnのSAFEARRAY構造体では、次元数、ロック数、要素サイズ、データ領域へのポインタ、各次元の境界情報などを持つ構造として説明されています。

VBAから見た実務上の要点は、配列変数が全要素を直接持つのではなく、SAFEARRAYを通じて実データ領域を参照していることです。

概念的には次の関係です。

配列変数
  ↓
SAFEARRAY構造体
  ↓
実データ領域

Variantに配列を入れると、Variantの値領域にはSAFEARRAYへの参照が入ります。さらに、配列要素をVariantにしておけば、要素ごとに型を持てます。

これが、VBAでVariantバインド配列が非常に便利な理由です。

Dim arr
arr = Array(1, "A", Date)

この arr はVariantです。中には配列がバインドされています。配列の各要素もVariantなので、数値、文字列、日付を同じ配列に入れられます。

もちろん、常に混在配列が良いという意味ではありません。ただし、Excelのセル範囲や業務データを扱うときには、最初から値の型が揃わないことがよくあります。そのとき、Variant配列は自然な受け皿になります。

Variantの4つの見方

Variantは、値そのものの型だけでなく、「そのVariantがどのような状態で値を持っているか」で見ると理解しやすくなります。

ここでは次の4つに分けます。

  • 単純型

  • 参照型

  • 配列型

  • 配列参照型

単純型は、数値や日付などをVariantに直接バインドしている状態です。

参照型は、ByRef引数などを通じて、別の変数を参照しているVariantです。これは関数設計で特に重要です。

配列型は、Variantに配列をバインドしている状態です。

配列参照型は、配列を参照として受けているVariantです。Variant配列をVariant引数で受けるときに、コピーを避けるうえで重要になります。

この区別を持っておくと、「Variantは何でも入るから曖昧」ではなく、「今のVariantは何を、どの形で持っているのか」と読めるようになります。

ByRef VariantはVBAの強力な道具である

VBAの関数設計では、仮引数をVariantにすることで柔軟性が大きく上がります。

Sub SetValue(ByRef x)
    x = x + 1
End Sub

ByRef x は、明示的に As Variant と書かなくてもVariantです。VBAでは型指定を省略した変数や引数はVariantになります。

VariantをByRefで受けると、呼び出し元の変数を書き換えられます。これを危険と見ることもできますが、関数の目的が「渡された変数を更新すること」であれば、これは正しい使い方です。

大切なのは、書き換える関数か、値を返す関数かを明確にすることです。ByRefが危険なのではなく、意図が読めない関数が危険です。

本当の参照という考え方

発表資料では、実引数もVariant、仮引数もVariantのときに「ほんとうの参照」という表現が使われています。

これは、呼び出し元と呼び出し先の別々の変数名が、実質的に同じ変数実体へバインドされる、という意味です。

スライドの図では、具象型をVariant仮引数で受けた場合と、VariantをVariant仮引数で受けた場合が分けて描かれていました。具象型実引数を受けるときは、仮引数側のVariantが元の値、文字列、配列、オブジェクトを間接参照する関係になります。実引数がVariantで、仮引数もVariantの場合は、実引数と仮引数のアドレスが同じになり、同じ実体に別名が付いている状態として扱えます。

この違いは、配列を関数の奥へ渡すときに効きます。配列を値として何度もコピーしているのではなく、同じ配列実体を別名で扱えるため、VBAのライブラリ関数を小さく分けても、配列コピーの負担を抑えられます。

Sub Main()
    Dim arr
    arr = Array(1, 2, 3)
    UpdateFirst arr
End Sub
Sub UpdateFirst(ByRef xs)
    xs(0) = 99
End Sub

このようなコードでは、xs を通じて arr の中身を更新できます。配列全体をコピーしてから処理しているのではなく、同じ配列を別名で扱っていると考えると分かりやすくなります。

ライブラリの奥深くまで配列を渡していくとき、ここで毎回コピーが発生すると大きな負担になります。Variant配列をVariant引数で受ける設計は、VBAで配列処理を組むうえで重要な選択肢です。

また、発表資料では、具象型引数のByRefは型が厳密に一致する必要があり、Integer と Long の違いも許容されないことが指摘されています。Variantで受ける設計は、この型一致の厳しさを緩められます。ただし、意図しない型まで受けてしまう可能性もあるため、公開境界では IsArray、IsObject、VarType などで必要な検査を入れます。

Excelシートとの相互運用はVariantが自然である

Excel VBAでVariantを避けにくい最大の理由の一つが、Rangeの値です。

Dim mtx
mtx = Range("A1:C3").Value

複数セル範囲の値を取得すると、通常は2次元配列が返ります。受け皿はVariantにするのが自然です。

逆に、2次元配列をRangeへ戻すこともできます。

Dim mtx
mtx = Range("A1:C3").Value
mtx(1, 1) = "変更後"
Range("A1:C3").Value = mtx

このやり方は、セルを1つずつ読むよりも速く、コードも短くなります。

そもそもExcelのセルには、数値、文字列、日付、空白、エラー値など、さまざまな値が入ります。セルの値を受ける段階ではVariantで受け、必要に応じて後から検査するほうが自然です。

戻り値にもVariantを使う

Variantを戻り値に使うと、関数の表現力が上がります。

たとえば、最小値とその位置を返したい場合、戻り値を配列にできます。

Function FindMin(ByRef xs)
    Dim i
    Dim minValue
    Dim minIndex
    minValue = xs(LBound(xs))
    minIndex = LBound(xs)
    For i = LBound(xs) + 1 To UBound(xs)
        If xs(i) < minValue Then
            minValue = xs(i)
            minIndex = i
        End If
    Next
    FindMin = Array(minValue, minIndex)
End Function

この関数は、戻り値の型を明示していません。つまりVariantを返します。戻り値には配列が入ります。

呼び出し側では次のように受けられます。

Dim result
result = FindMin(Array(8, 3, 5))
Debug.Print result(0)  ' 最小値
Debug.Print result(1)  ' インデックス

VBAにはタプルや多値戻り値が標準でありません。その不足を、Variant配列でかなり補えます。

ParamArrayもVariant配列として扱う

ParamArray は、可変長引数を受けるときに使います。

Sub PrintAll(ParamArray args())
    Dim x
    For Each x In args
        Debug.Print x
    Next
End Sub

ParamArray の中身もVariant配列です。複数種類の値をまとめて受ける仕組みなので、Variantと相性が良いのは当然です。

自作ライブラリで「値、配列、オブジェクトのどれでも受ける」関数を作る場合、Variantで受けて IsArray、IsObject、IsEmpty などで振り分ける設計が使えます。

Emptyは欠点ではなく、未初期化を表す値として使える

Variantの初期値は Empty です。

これを分かりにくいと感じることもありますが、実務上はむしろ便利です。Empty は、Stringの空文字 "" とも、数値の0とも、Nothing とも違います。

Dim x
If IsEmpty(x) Then
    Debug.Print "まだ値が入っていない"
End If

Static変数の初期化判定にも使えます。

Function NextId()
    Static current
    If IsEmpty(current) Then
        current = 0
    End If
    current = current + 1
    NextId = current
End Function

具象型の初期値では、未初期化なのか、たまたま0や空文字を入れたのかが区別しにくいことがあります。Variantの Empty は、この区別を持てる点で便利です。

Variantは短く書くためだけのものではない

Variantを使うと、型指定を省略できるのでコードは短くなります。

Dim i
Dim xs
Dim result

これは単なる省エネ記法にも見えます。しかし本質は、VBAの実行時データを自然に受けるための器を使っているということです。

Excelのセル、配列、戻り値、可変長引数、Collection、ArrayListなどを組み合わせると、具象型だけで設計するほうが不自然になる場面があります。

省略できるところを省略し、必要なところだけ型を書く。これがVariantスタイルの基本です。

オブジェクトは具象型でよい場面も多い

Variantが便利だからといって、すべてのオブジェクト変数をVariantにする必要はありません。

特にローカル変数としてRangeやWorksheetを扱う場合は、具象型を書いたほうが読みやすく、VBEの入力補完も効きます。

Dim ws As Worksheet
Dim r As Range
Set ws = ActiveSheet
Set r = ws.Range("A1")
r.Value = 1

オブジェクトは参照として扱われます。Variantにオブジェクトを入れても使えますが、ローカルで明確にRangeを扱うなら As Range と書くほうがよいことが多いです。

一方で、コレクションにオブジェクトを入れる、複数種類のオブジェクトをまとめて扱う、ダックタイピング的に同じメソッドを呼びたい、という場合にはVariantで受ける設計が使えます。

Rangeオブジェクトで起きやすい罠

Variantを使うときに、特に注意したいのがRangeオブジェクトです。

次のコードは、A1セルを書き換えたい意図に見えます。

Dim r
Set r = Range("A1")
r = 1

しかし、これは誤解を生みやすいコードです。

r はVariantです。Set r = Range("A1") により、r にはRangeオブジェクトが入っています。しかし、r = 1 と書いたときに何を変更しているのかが読み取りにくくなります。

このような場面では、次のように書くほうが明確です。

Dim r As Range
Set r = Range("A1")
r.Value = 1

または、Variantで受けるとしても、セルの値を変えることを明示します。

Dim r
Set r = Range("A1")
r.Value = 1

可読性と堅牢性を考えると、Rangeのような具体的なExcelオブジェクトは具象型で宣言し、プロパティも明示するのが安全です。

Variantを使うと静的エラーが実行時エラーになる

Variantを多用すると、型の不一致をコンパイル時に拾えず、実行時エラーとして出る場面があります。これは事実です。

ただし、実務マクロで本当に重要なのは、「実行時エラーになること自体」ではなく、「問題が起きたときに原因を特定できること」です。

部門内や個人用の業務マクロでは、予想外の状態で無理に動き続けるより、止まってくれたほうが安全なことがあります。エラー処理を過剰に書いて握りつぶすと、原因が見えにくくなります。

チェックすべきなのは、次のような場面です。

  • エラーが出ても原因が分かりにくい

  • 間違った型でも処理が進んでしまう

  • データを壊す可能性がある

  • 外部ファイルやシートを書き換える前

一方で、明確な実行時エラーで止まるだけなら、過剰な事前チェックは不要なこともあります。

チェックは必要なところにだけ入れる

Variantを使うなら、IsArray、IsObject、IsEmpty、IsNumeric、TypeName、VarType などを必要なところで使います。

Sub PrintItems(ByRef xs)
    Dim x
    If Not IsArray(xs) Then
        Err.Raise 5, , "配列を指定してください。"
    End If
    For Each x In xs
        Debug.Print x
    Next
End Sub

このように、関数の前提が重要な場合は、入り口で検査して分かりやすいエラーに変換します。

反対に、内部の小さな関数まで毎回すべての型を検査すると、コードが膨らみます。ライブラリの公開境界、シート書き込み前、外部入力を受けた直後など、意味のある境界でチェックするのが現実的です。

巨大配列と深い再帰ではVariantのコストを見る

Variantは便利ですが、万能ではありません。

Variantは1要素が大きくなります。巨大な配列で、要素がすべてLongやDoubleに決まっている場合、Variant配列より具象型配列のほうがメモリ効率は良くなります。

Dim xs() As Long

大量の数値だけを扱う処理、深い再帰、メモリ上限が問題になる処理では、具象型配列を使う価値があります。

また、再帰が深い処理では、ローカル変数や仮引数のサイズも効いてきます。Variantは16バイトなので、深い再帰で大量に積むなら注意が必要です。

ただし、実引数も仮引数もVariantで、参照として渡せている場合は、配列本体を毎回コピーしているわけではありません。ここでも、何がコピーされ、何が参照されているかを区別することが重要です。

Variantを使う判断基準

実務では、次のように考えると扱いやすくなります。

Variantが向いているもの。

  • Excel Rangeから取得した値

  • 複数セル範囲を受ける2次元配列

  • Array(...) で作る小さな配列

  • ParamArray

  • 複数種類の値を受けるライブラリ関数

  • 多値戻り値の簡易表現

  • 未初期化状態を Empty で持ちたい変数

具象型が向いているもの。

  • Range、Worksheet、Workbookなど明確なExcelオブジェクト

  • 大量の数値だけを扱う配列

  • 外部APIやCOM呼び出しで型が明確に必要な引数

  • 型の誤りをコンパイル時に止めたい公開手続き

  • ループカウンタなど、意味が明確で型変換の余地がない変数

「Variantか具象型か」を思想で決める必要はありません。データの性質、関数の境界、メモリ量、読みやすさで決めれば十分です。

Variantスタイルの要点

Variantを主力にするなら、次の要点を押さえると安定します。

  • Variantは型のない変数ではなく、型情報を持つ変数である

  • 具象型からVariantへ代入すると、代入元の型がVariantにバインドされる

  • Variantから具象型へ代入すると、代入先に合わせた暗黙変換が起こり得る

  • 文字列、配列、オブジェクトは参照値を含むので、実体との関係を見る

  • 配列処理ではVariantバインド配列を基本にすると書きやすい

  • RangeなどのExcelオブジェクトは具象型と .Value 明示が安全なことが多い

  • チェックは公開境界や危険な操作の前に入れる

  • 巨大データと深い再帰ではメモリコストを見る

Variantを理解すると、VBAの見え方が変わります。単に型宣言を省略するための仕組みではなく、VBAで配列、セル範囲、可変長引数、多値戻り値、ライブラリ関数を扱うための中心的な仕組みとして見えるようになります。

Variantを避けるのではなく、Variantが何を持っているのかを読む。これが、VBAを実務で楽に書くための一つの近道です。

出典メモ

この記事は、次の素材を確認して作成しました。

  • バリアントの真実(発表後改定)_005.pptx

  • Togetter/Posfie「バリアントの真実」 https://togetter.com/li/1692817

  • Posfie正規URL https://posfie.com/@hoehoe1234/p/RIER57N

  • Dropbox Paper URL https://paper.dropbox.com/doc/2020-03-21--BaOAnO86znH3ZBcpog0p2OXjAg-yadAGgmPLHZHJHut56zn4

  • Microsoft Learn「VARIANT structure (oaidl.h)」 https://learn.microsoft.com/en-us/windows/win32/api/oaidl/ns-oaidl-variant

  • Microsoft Learn「SAFEARRAY structure (oaidl.h)」 https://learn.microsoft.com/en-us/windows/win32/api/oaidl/ns-oaidl-safearray

PPTXは24枚分のスライド本文を抽出し、さらにPowerPointからPNG化して、Variant構造、変数とVariant仮引数の関係図、関数設計、Range代入の罠を目視確認しました。TogetterはPosfieへ正規化されたページから本文を取得し、2ページ分の投稿本文を確認しました。Dropbox Paperは取得時にDropboxログインページへリダイレクトされたため、本文は確認できませんでした。

Togetter/Posfie内では、改善さんによるVBA導入説明、発表時の補足、BSTR、SAFEARRAY、Variantバインド配列、Range代入の罠などに関する投稿を参照しました。pic.twitter.com 画像つき投稿は複数ありましたが、現時点で画像本体を安定取得できたのはPPTX由来のスライド画像です。本文では、投稿本文で読めた範囲、PPTX本文、PPTXからPNG化して目視確認した範囲をもとに、説明用コードと概念図の内容を再構成しています。