totoのExcel備忘録

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

CDec関数で取得したDecimal型の値が「Variant型以外の型」の変数に「代入」できてしまうという話

今まで、CDec関数の戻り値として得られる十進数型(Decimal)のデータは、VBAの標準データ型に代入できないと思っていました。

Microsoft Docsをはじめ、「Decimalデータ型はバリアント型 ( Variant) のみで使用できます 」と各所で説明されているためです。

ところが、他の記事を書いている最中、CDec関数の戻り値が他のVBAの標準データ変数に何のエラーもなく「代入」できてしまうことに気づきました。

VBAの型システムの仕様を考えたら当たり前なのですが、ちょっとだけショックを受けましたし、Microsoft のレファレンスにすら書かれていなかったので、記事にしようと思います。


この記事の内容


VBAの型システムの問題

Variant型以外のデータ型の変数は暗黙的にデータを改変して受け取る

VBAプログラミングを安全に行うためには変数のデータ型を宣言するべきだ、という意見をよく見かけます。

しかしVariant型以外のVBAのデータ型(以下「基本データ型」と書きます)は、ただデータ型のチェックを行うだけではなく、「型に合致する値として解釈できる値は暗黙的に変換して受け入れる」という性質も持っています。

例えばこんな例が該当します。

【String型変数にLong型変数の格納値を代入する場合】

Sub assign_long_2_string()

    Dim i As Long
    Dim s As String

    i = 100
    s = i   '本当はここで「13 : 型が一致しません。」 と実行時エラーを出してほしいが、受け入れてしまう。
    Debug.Print s

End Sub

出力結果:
100

String型で宣言したsに、Long型のiの値を「代入」すると、暗黙的に文字列への変換が行われ、sに値が設定されます。

他の例として、数字の文字列を数値型の変数に「代入」すると、その型の値に合った数値として解釈される、といったことが起きます。

個人的には、こういう処理が暗黙且つ簡単に行われてしまうVBAの基本データ型というのは、あまり信頼できないと感じています。

VBAにおけるDecimal型とはどのようなデータ型なのか

Single型やDouble型の十進数小数の表現力不足を補うのがDecimal型

本題に入る前に、Decimal型とはどのようなデータ型なのかについて、簡単に触れておきます。

Decimal型とは、二進数表現の浮動小数点数型(Single型やDouble型)で正確に表現できない十進数の小数を正確に表現するための数値データ型です。

二進数浮動小数点型による十進数の小数点表現は不正確です。
十進数の「0.1」を2進数に変換すると「0.0001100110011…」となり、「0011」の以下が永遠に循環します。
しかし、計算機の仕組み上、数値の表現を有効桁数内で納めなくてはならないので、近接する数字へと丸められます。
結果として「0.1」はDouble型では二進数で「0.0001100110011001100110011001100110011001100110011001101」となりますが、これは十進数にすると「0.1000000000000000055511151231257827021181583404541015625」となり、0.1そのものとは全く異なる数値になります。

試しに以下のような小数の同値比較をイミディエイトウィンドウで実行してみると、Falseが返ります。

?0.1  + 0.2 = 0.3
False

このような丸め誤差は、処理する数字の桁数が多く、丸め誤差が許容できない財務処理などでは致命的になります。

Decimal型は、この誤差をなるべく小さくするために考案されたものです。
正負の符号なしの96bit(12Byte)の仮数部(整数値)と、小数部の桁数を表す指数部(スケーリングファクター:0~28で指定)、符号部からなり、最大 29 桁の有効桁数をサポートしています。

表現できる値の範囲は、小数点ありの場合、+/-7.9228162514264337593543950335、小数点なしの場合で+/-79,228,162,514,264,337,593,543,950,335です。 0でない最小の絶対値を持つ数値は+/-0.0000000000000000000000000001です。

VBAでは、標準関数ライブラリのCDec関数を使って、Decimal型の値を取得することができます。 CDec関数で取得したDecimal型の値だけで先ほどの同値評価を行うと、数学的に正しい判定結果を得ることができます。

?CDec(0.1)  + CDec(0.2) =CDec(0.3)
True

Decimal型の値はVariant型でしか扱うことができない

Microsoft DocsのDecimal型の説明では、こんなことが書かれています。

Decimalデータ型はバリアント型 ( Variant) のみで使用できます。つまり、変数をDecimal型に宣言することはできません。 ただし、 CDec関数を使用して、サブタイプがDecimalであるバリアントを作成することはできます。

VB6、および現在のVBAでは、Variant型でしかDecimal型を扱えないようです*1。 小数の扱いが他の数値型と異なるためでしょう。 VBAの世界では、他のシステムからパラメータとして受け取るVariant型の値やCDec関数の戻り値の中身として取得するする以外、Decimal型を扱う方法がないようです。

この点、後継言語のVB.NETではDecimal型が独立した型としてサポートされているようで、やはりVBAは古い言語ということなのでしょう。

ただしこの「Variant型でしか使えない」という点、他のデータ型にそもそも代入が出来ないと思っていたのですが、それは間違いだったようです。

Decimal型の値がVariant型以外の変数に「代入」出来てしまう

実際のコードでDecimal型の値をあらゆる型の変数に代入してみる

今回の本題は、CDec関数によって作成したDecimal型のデータが、Variant型以外のVBAの基本データ型の変数に「代入」が出来てしまうよというお話です。
つまり、他の値型同様、Decimal型の値も受け手の変数にとって都合の良い型で解釈されて変数に値が格納されてしまうということです。

具体的なコード例で示してみることにします。 あらゆるデータ型の変数を用意し、CDec関数の値を「代入」して、どのようなデータ型の値として格納されているかを出力します。 型チェックによるエラーが起きた場合、エラー番号とDescriptionを出力します。

【全てVBAのデータ型に十進数型の値を代入するコード】

'それぞれの型の変数を用意し、CDec(1/3)の結果を代入して出力する
'エラー情報も取りたいので、変数への代入前にErr.Clearを毎回コールする

Sub cdec_assign_test()
    On Error Resume Next
    Debug.Print "CDec(2/3)" & "の代入結果:": Debug.Print
    
    Err.Clear: Dim byteVar As Byte: byteVar = CDec(2 / 3): Debug.Print "Byte型" & vbTab & ": [value]: " & vbTab & byteVar & vbTab & "[type name]: " & TypeName(byteVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim boolVar As Boolean: boolVar = CDec(2 / 3): Debug.Print "Boolean型" & vbTab & ": [value]: " & vbTab & boolVar & vbTab & "[type name]: " & TypeName(boolVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim curVar As Currency: curVar = CDec(2 / 3): Debug.Print "Currency型" & vbTab & ": [value]: " & vbTab & curVar & vbTab & "[type name]: " & TypeName(curVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim dateVar As Date: dateVar = CDec(2 / 3): Debug.Print "Date型" & vbTab & ": [value]: " & vbTab & dateVar & vbTab & "[type name]: " & TypeName(dateVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim dblVar As Double: dblVar = CDec(2 / 3): Debug.Print "Double型" & vbTab & ": [value]: " & vbTab & dblVar & vbTab & "[type name]: " & TypeName(dblVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim intVar As Integer: intVar = CDec(2 / 3): Debug.Print "Integer型" & vbTab & ": [value]: " & vbTab & intVar & vbTab & "[type name]: " & TypeName(intVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim lngVar As Long: lngVar = CDec(2 / 3): Debug.Print "Long型" & vbTab & ": [value]: " & vbTab & lngVar & vbTab & "[type name]: " & TypeName(lngVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim lnglngVar As LongLong: lnglngVar = CDec(2 / 3): Debug.Print "LongLong型" & vbTab & ": [value]: " & vbTab & lnglngVar & vbTab & "[type name]: " & TypeName(lnglngVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim objVar As Object: Let objVar = CDec(2 / 3): Debug.Print "Object型(Let)" & vbTab & ": [value]: " & vbTab & objVar & vbTab & "[type name]: " & TypeName(objVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
                                                        Err.Clear: Set objVar = CDec(2 / 3): Debug.Print "Object型(Set)" & vbTab & ": [value]: " & vbTab & objVar & vbTab & "[type name]: " & TypeName(objVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim sngVar As Single: sngVar = CDec(2 / 3): Debug.Print "Single型" & vbTab & ": [value]: " & vbTab & sngVar & vbTab & "[type name]: " & TypeName(sngVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim strVar As String: strVar = CDec(2 / 3): Debug.Print "String型" & vbTab & ": [value]: " & vbTab & strVar & vbTab & "[type name]: " & TypeName(strVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Err.Clear: Dim varVar As Variant: varVar = CDec(2 / 3): Debug.Print "Variant型" & vbTab & ": [value]: " & vbTab & varVar & vbTab & "[type name]: " & TypeName(varVar): If Err.Number <> 0 Then Debug.Print "[error number]: " & Err.Number & vbTab & Err.Description
    Debug.Print
End Sub

===========
CDec(2/3)の代入結果:

Byte型:
[value]:    1   [type name]: Byte

Boolean型:
[value]:    True    [type name]: Boolean

Currency型:
[value]:    0.6667  [type name]: Currency

Date型:
[value]:    16:00:00    [type name]: Date

Double型:
[value]:    0.666666666666667   [type name]: Double

Integer型:
[value]:    1   [type name]: Integer

Long型:
[value]:    1   [type name]: Long

LongLong型:
[value]:    1   [type name]: LongLong

Object型(Let):
[error number]: 91  オブジェクト変数または With ブロック変数が設定されていません。

Object型(Set):
[error number]: 91  オブジェクト変数または With ブロック変数が設定されていません。

Single型:
[value]:    0.6666667   [type name]: Single

String型:
[value]:    0.666666666666667   [type name]: String

Variant型:
[value]:    0.666666666666667   [type name]: Decimal

なんと、Variant型以外の型であっても、値型を受け付けないObject型以外は全て、CDec関数の戻り値を受け入れてしまいます。
しかもTypeName関数で取得する型情報は、受け入れ側の型です。

何が起きているのか

どうやら、CDec関数によって生成したDecimal型データであっても、それぞれの型が持つ変換ルールに従って数値または文字列表現として評価され、改変された値が変数に格納されるようです。 数値型やBoolean型への「代入」の場合、四捨五入による丸めと評価が行われているようですが、どのようなコンテクストで変換されているのか、正確には分かりません。

しかし、Variant型しか適合する型のない特殊な値型であるDecimal型の値まで暗黙的に変換されてしまうとは、実に恐ろしいです。
VBAにおける基本データ型は本当に要注意です。

「Decimal型はVariant型でしか使用できない」とは

もう一度Microsoft Docsを読んでみる

では、そもそも「Decimal型はVariant型でしか使用できない」とはどういうことだったのでしょう。

Microsoft DocsのDecimal型の説明をもう一度よく読んでみます。

Decimalデータはバリアント型 ( Variant) のみで使用できます。つまり、変数をDecimal型に宣言することはできません。 ただし、 CDec関数を使用して、サブタイプがDecimalであるバリアントを作成することはできます。

この「バリアント型のみで使用できます」という部分の目的語は、Decimal型の型定義そのものであるということを理解しないといけないですね。

つまり、As Decimalなどといった形で変数宣言時に型の定義を利用することができないし、DimでDecimal型に対応したサイズや構造のメモリを確保することもないということですね。

Decimal型はAs句による型指定のサポート外なので、インテリセンスで型名が表示されない
Decimal型はAs句による型指定のサポート外なので、インテリセンスで型名が表示されない

このことは、Decimal型という型情報が、バリアント型のインスタンスが内部で保持している型情報(つまりサブタイプ)としてしか存在できないという意味にもなります。 確かに先ほどのコードサンプルでも、”Decimal”という型情報を保持できていたのはVariant型変数だけでした。

ただし、CDec関数などで得られたバリアント型のDecimal型の値を他のデータ型の変数に渡そうとするとどうなるのか、ということについては明示的に触れられてはいません。 「Decimal型のデータを他の型の変数で受け取ることが出来ない」とは言っていないわけです。

Decimal型に適合する変数タイプが存在しないということなので、私はてっきり型チェックで弾かれる仕様なのかなと思っていたわけですが。。

そこはやはりVBA、CDec関数で作成したDecimal型であっても、値の受け取り手の基本データ型変数の都合に合わせて値とデータ型が改変されてしまうということのようです。


最後に

以上、Decimal型データを他の基本データ型変数で受け取った場合の暗黙の値変換についてまとめてみました。

今回の記事を通じて、VBAの奥深さを実感頂けたら嬉しいです。

本記事の内容に誤り等がありましたらご指摘いただけると助かります。

それでは、また!

本記事のまとめ

  • VBAの基本データ型の変数は、型が違うデータが代入されても、型変換して受け入れられる値であれば暗黙的に型変換して受け入れてしまう
  • Decimal型は十進数で数値を表現する型だが、VBAではVariant型の内部表現としてしか扱えない
  • VBAの基本データ型にDecimal型の値を「代入」すると、数値型の値として解釈され、変数側で受け入れ可能な型の値に変換して受け入れてしまう
  • 「Decimal型はVariant型でしか使えない」とは、Decimal型の型情報をVariant型でしか扱えないという意味であり、他の基本データ型への値の受け渡しは可能(ただし型情報は失われる)

*1:「サブタイプがDecimal」とは、Variant型が内部で認識している型の種類がDecimalということです。このVariant型の内部の型の扱いについては、別の機会にVariant型の記事を書く予定です。

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文字が指定可能と記載されていますが、事実上英字のみです

ブログはじめました

はじめまして。totoといいます。

 

私は東京のとある会社で品質管理の仕事をしています。なお職業プログラマではありませんが、業務ツールの内製プロジェクトにも開発担当者として関わっています。

 

いままで、Twitterでプログラミング全般(特にExcelVBAやAccess VBA)、モダンExcel、BIツール、データ分析に関することを呟いてきました。しかし最近、自分の書いたことを後からまとめて振り返ることのできる場所があればと良いなと思うようになったので、このたびブログを開設することにしました。

 

Twitterは大人の砂場と思って気軽に楽しむようにしていますので、ダジャレ、改行無しのトリッキーなコーディング等、ゆるい発言ばかりしていますが、ここではまた違った一面も皆さんにお見せできればと思っています。

 

ブログ初心者ですが、マイペースに更新していこうと思います。

よろしくお願いいたします。