totoのExcel備忘録

モダンExcel、VBA、BIツール等について調べたことをメモしていきます。

Deftypeステートメントについて

VBAにはBASICやVBから引き継がれている古い機能や仕様が沢山あり、独特の味わいがあるなと感じます。 そういった味わい深い機能の使いどころを探すのも面白いです。

今日はその中でも特に自分が気に入っているDeftypeステートメントについて書いてみようと思います。


この記事の内容


Deftypeステートメントとは

概要

Deftypeステートメントは、モジュール内で定義されている変数・引数・関数のうち、 特定の文字から始まる名前を持つものについて、As 型名でデータ型を指定することなしに既定のデータ型を設定する機能を提供します。

通常、As句を使わずに宣言した変数などは既定でVariant型になりますが、 Deftypeステートメントを使うと、String型等のデータ型を既定の型として利用できるようになります。

利点としては、変数や関数の戻り値のデータ型の宣言を簡略化できる、変数や関数の名前とデータ型に一定の規則性や関連性を持たせることができる、という点でしょうか。

構文

Deftypeというのは複数のステートメントの総称で、Deftypeというキーワードは存在しません。 BASICの古い歴史を反映し、標準のデータ型一つ一つに別々のステートメントが存在するというハードな仕様になっているためです。

各データ型に対応したキーワード(ステートメント)は、以下の通りです。

【Deftypeステートメントの種類】

ステートメント 型名 構文
DefBool ブーリアン型 (Boolean) DefBool letterrange, [ letterrange ] . . .
DefByte バイト型 (Byte) DefByte letterrange, [ letterrange ] . . .
DefInt 整数型 (Integer) DefInt letterrange, [ letterrange ] . . .
DefLng 長整数型 (Long) DefLng letterrange, [ letterrange ] . . .
DefLngLng 64bit長整数型 (LongLong) DefLngLng letterrange, [ letterrange ] . . .
DefLngPtr 長ポインタ整数型 (LongPtr) DefLngPtr letterrange, [ letterrange ] . . .
DefCur 通貨型 (Currency) DefCur letterrange, [ letterrange ] . . .
DefSng 単精度浮動小数点数型 (Single) DefSng letterrange, [ letterrange ] . . .
DefDbl 倍精度浮動小数点数型 (Double) DefDbl letterrange, [ letterrange ] . . .
DefDec 10進整数型 (Decimal) DefDec letterrange, [ letterrange ] . . .
DefDate 日付型 (Date) DefDate letterrange, [ letterrange ] . . .
DefStr 文字列型 (String) DefStr letterrange, [ letterrange ] . . .
DefObj オブジェクト型 (Object) DefObj letterrange, [ letterrange ] . . .
DefVar バリアント型 (Variant) DefVar letterrange, [ letterrange ] . . .

引数のletterrangeには、既定データ型を設定したい変数や関数の名前のイニシャルを示す文字を指定します。
書き方は何通りかあり、複数の文字を指定したり、一定範囲のアルファベットをまとめて指定したりすることが出来ます。

【引数の書き方】

パターン 構文 記述例 記述例で指定した文字
一つの文字を指定する Deftype 英字1文字 DefDate D D, d
一定範囲の英文字をまとめて指定する Deftype 範囲の最初の1文字 - 範囲の終わりの1文字 DefStr A-C A, B, C, a, b, c
複数の文字や文字範囲をまとめて指定する Deftype 英字1文字または範囲1,英字一文字または範囲2,.. DefLng A-C, H, X-Z A, B, C, H, X, Y, Z, a, b, c, h, x, y, z

構文以外の仕様

引数letterrangeに指定する英文字は、強制的に大文字になります。
ただし大文字・小文字の区別なく、変数や関数、引数の名前が判定されます。 (DefStr Sと指定されていたら、strValueStrValueもString型になる)

またOption Explicitなどと同様、モジュール単位で適用されるステートメントなので、モジュールの先頭に記載する必要があります。

なお、コードの中でAs句を使用することで、Deftypeステートメントによって指定された型とは別のデータ型を任意に設定することが出来ます。

この他、詳しい説明はMSの公式リファレンスに載っているので、興味のある方はそちらを参照してみてください。

コード例

以下のように書くと、ローマ字の' A 'で始まる変数や関数すべてに対し、As Stringによる型指定なしに、String型を既定のデータ型として設定することができます。

Option Explicit
DefStr A

Sub sample1()
        Dim a, ABC
        Dim AAA As Long
        Debug.Print "a: " & vbTab & a)
        Debug.Print "ABC: " & vbTab & TypeName(ABC)
        Debug.Print "AAA: " & vbTab & TypeName(AAA)
End Sub

'TypeName関数による各変数の出力結果。

'a:     String
'ABC:   String
'AAA:   Long

'変数aと変数ABCはAs句でデータ型を指定していないが、頭文字がA/aのため既定でString型が設定されている
'大文字・小文字ば区別されない
'変数AAAのようにコード内で別の型が指定されていれば別の型が適用される

使いどころはあるのか

システムハンガリアン記法との併用

VBAの世界では、データ型を表す文字列を変数名の先頭につけるシステムハンガリアン記法が一部で定着しています。
strValue As StringとかrngTarget As Rangeみたいな表記のことです)

ハンガリアン記法を前提とするならば、
Deftypeステートメントで頭文字とデータ型をモジュール全体で紐づけてしまうことで、いちいちAs句でデータ型を指定しなくてもよくなるというメリットはあるかもしれません。

ということで、DefTypeステートメントを使っていないバージョンと使っているバージョンのコードを作成して、比較してみました。

'DefTypeステートメントを使わないバージョン
Option Explicit
Sub sample3()
        Dim wsInput As Excel.Worksheet, wsOutput As Excel.Worksheet
        Dim lngRow1 As Long, lngRow2 As Long
        Dim lngCol1 As Long, lngCol2 As Long
        Dim strValue1 As String, strValue2 As String
        Dim dStartDate As Date, dEndDate As Date, dDueDate As Date
'・・・
end sub

これが、こうなります。

'DefTypeステートメントを使っているバージョン
Option Explicit
DefDate D
DefLng L
DefStr S
Sub sample4()
        Dim wsInput As Excel.Worksheet, wsOutput As Excel.Worksheet
        Dim lngRow1, lngRow2
        Dim lngCol1, lngCol2
        Dim strValue1, strValue2
        Dim dStartDate, dEndDate, dDueDate
'・・・
end sub

見た目のコード量が結構違います。
これくらい見た目が違ってくるならば、それなりに利用価値を感じる人がいるかもしれませんね。

日本語変数との使い分け

後述するように、Deftype名前のステートメントの引数には半角英字しか指定できません。
これは逆に言えば、日本語名等の非半角英字の文字種で定義された変数や関数はDeftypeステートメントの影響を受けないということです。

この仕様を利用して、Variant型やObject型といった万能型は日本語名の変数や関数で定義して、
英字名の関数や変数だけDeftypeステートメントで既定の型を細かく制御する、という使い方も出来るかもしれません。


古い仕様にはクセが付き物

古い機能・古い仕様にはそれなりのクセがあるものです。
そういう面を見ていくのも一興かと思います(特にVBAは)。

Optionalキーワードとの相性がとても悪い

以下のようなコードは、コンパイルが通りません。

Option Explicit
DefLng L
DefStr S
DefDate D

Function strConcat(strA, Optional strB)
strConcat = strA & strB
End Function

Deftypeステートメントの影響を受ける仮引数はOptional指定できないらしい
Deftypeステートメントの影響を受ける仮引数は既定値未設定の状態でOptional指定ができないらしいです

むしろ、既定値の固有のデータ型を指定するためにこそDeftypeステートメントを使っているのですが、、、

確かめたわけではないので確実なことは言えないのですが、
コンパイル時にVariant型でなければ既定値があるわけでもない、という状態として評価されてしまうのでしょう。

ちなみにOptional strB As StringとAs句を使用したり、
Optional strB =""などと既定値を指定したりすることで、
Deftypeステートメントが書かれているモジュールであっても普通にコンパイルが通ります。
でもそれじゃあまり意味がないんですよね。

Option Explicit
DefLng L
DefStr S
DefDate D

 'Optional strB = ""` などと既定値を設定するのもOK 
Function strConcat(strA, Optional strB As String)
strConcat = strA & strB
End Function

アプリケーションAPIのオブジェクト型やユーザー定義型はサポート外

VBAでは、As句によるデータ型指定の際、
As Excel.RangeAs Word.Documentという具合に、
個別のアプリケーションのVBAオブジェクトモデルで定義されているオブジェクト型を指定することが出来ます。

またクラスモジュールや構造体、列挙体をユーザー定義型として作成している場合も、As ユーザー定義型の名前でそれらの型を指定することができます。

Deftypeステートメントの場合、データ型によって違う命令語を用意するという仕様にしているので、
当然、VBA本体の組み込みのデータ型だけがサポートされている格好になります。

Deftype 型名 指定文字 という感じのシンプルな書式で、あらゆる型をサポートできる仕組みになっていたら便利だったかなと思いますけど、そこは仕方がない所ですね。

データ型の制約はモジュールにしか及ばない

他のモジュールで定義されている変数や関数等には影響しません。
特定のプロシージャ内限定とか、特定のモジュール間でのみ設定共有とか、VBProject全体とか、
細やかに適用範囲を指定する機能は無いようです。

VBA≒VB6が「孤立したモジュールファイルの世界」だということをよく表している仕様で、実に味わい深いです。

指定可能な文字種は(事実上)半角英字だけ

漢字や平仮名などの日本語の文字種を自由に指定出来たら結構便利だと思うのですが、
指定できるのは半角英字のみです*1

名前の先頭1バイトを、ごく基本的な英字の文字セットと照合して、既定のデータ型を決定する仕様なのでしょう。

文字指定が重複している場合、コンパイルエラーになる

「コードに書かれた内容の矛盾を許さない」という意味では優秀なかつ当然の制約なのですが、
コードのメンテナンスのしやすさとか再利用性を考えると、
一番最後に定義されている設定の方が強制的に適用されるといったような柔軟な仕組みが欲しいなという感じがします。

Option Explicit
DefLng A-G
DefStr H-Z
DefDate D 'この行でコンパイルエラーが発生する。先行するどの宣言と矛盾しているかまでは教えてくれない。

判定されるのは名前のイニシャル一文字のみ

名前のチェックでは先頭の一文字しかチェックできません。 DefStr STR というように、複数文字からなる文字列を判定条件として指定することは不可能です。 構文チェックを単純化するためにこのような仕様にしているのだと思います。

システムハンガリアン記法のように、変数の名前とデータ型を関連させている場合、この制約はちょっとネックになります。 String型とSingle型、Boolean型とByte型、Date型とDouble型とDecimal型など、 データ型名の頭文字が重複している場合があるためです。

例えばDefStr S と、SをString型の指定文字として使ってしまうと、Single型の指定文字にはSを使用できなくなります。 となると、指定文字に重複が出ないよう、DefStr STR DefSng SNGなどと複数の文字からなる文字列を型判定の条件に設定したいところですが、残念ながらそれが出来ない。

sngParam As Single と、As句でSingle型を指定すれば良いだけと言えばそれまでですが。。。


最後に

以上、Deftypeステートメントについてまとめてみました。
あまり業務のコードやネット等で見かける機会の多くないステートメントだと思うので、
これをきっかけに多くの方に知ってもらい、VBAの奥深さや面白さを実感いただけたらと思います。
また本記事に関して、記載の誤り等がありましたら、ご指摘いただけると助かります。

それでは、また!

本記事のまとめ

  • Deftypeステートメントにより、モジュールで定義された変数や関数や引数に対して、Variant型以外の既定のデータ型を指定できる

  • 各標準データ型に対応する命令語が用意されている

  • システムハンガリアン記法と組み合わせて使うなど、工夫次第でそれなりに活用することは出来そう

  • Optionalキーワードとの相性、複数文字からなる文字列をletterrangeに指定できないなど、様々な仕様上の制約があり、利用には注意が必要

*1:MSの公式リファレンスでは文字セットの最初の128文字が指定可能と記載されていますが、事実上英字のみです