VBA標準機能で作成できる要素数0の配列

# 前提

この記事における「要素数0の配列」は
『`UBound()`や`LBound()`に渡してもエラーが発生せず、`UBound() - LBound() + 1`を評価すると0となる一次元配列』とします。

# はじめに

VBAでは、変数宣言などの記述で要素数0の配列を作成できません。

```vb
'Option Base 0

Dim myArray() As Variant
ReDim myArray(0) ' 要素数0ではなく添え字が0~0の要素数1個の配列
```

しかし、一部の型の配列に関してはVBA標準機能・組み込み関数の組み合わせで作成できます。
この記事では、その作成方法を記載しています。

また、この記事の方法はWindows環境でしか動作を確認していません。

```:動作確認環境
Windows 7   Microsoft Excel 2010 32bit
Windows 8.1 Microsoft Excel 2013 32bit
Windows 10  Microsoft Excel 2016 64bit
```

## 備考

以下のページのようにSAFEARRAY操作用のWindows APIを使用すれば、汎用的に要素数0の配列を作成できます。

> [[Excel VBA] 空の配列を管理する - くろねこ研究所](http://www.blackcat.xyz/article.php/ProgFAQ-Xls_EmptyQueue "[Excel VBA] 空の配列を管理する - くろねこ研究所")

> [VBA ほぼタイプセーフなコレクション - Qiita](https://qiita.com/tomochan154/items/9dd43fa04a8073f7c6f3 "VBA ほぼタイプセーフなコレクション - Qiita")

> 使用されているWindows API
[SafeArrayAllocDescriptor function | Microsoft Docs](https://docs.microsoft.com/ja-jp/previous-versions/windows/desktop/api/oleauto/nf-oleauto-safearrayallocdescriptor "SafeArrayAllocDescriptor function | Microsoft Docs")

使用例

```vb
'引数1 ：作成したい配列の次元数（VBAなら基本1でいいはず）
'引数2 ：任意の型の配列
'返り値：成功・失敗（0なら成功）
Private Declare PtrSafe Function SafeArrayAllocDescriptor Lib "oleaut32" ( _
        ByVal cDims As Long, _
        ByRef ppsaOut() As Any _
    ) As Long

Property Get EmptyLongArray() As Long()
    Dim apiResult As Long
    apiResult = SafeArrayAllocDescriptor(1, EmptyLongArray)
    If apiResult <> 0 Then Err.Raise 5
End Property
```




# 要素数0の配列を取得する記述

VBAの標準機能では以下の型の要素数0の配列を作成できます。

- Variant
- String
- Byte
- オブジェクト

また記述の都合上、それぞれの配列を取得するプロシージャとして紹介します。

## Variant型

比較的有名な方法だと思われますが、[Array 関数](https://msdn.microsoft.com/ja-jp/vba/language-reference-vba/articles/array-function?f=255&MSPPError=-2147217396 "Array 関数")に引数を渡さず実行すると得られます。

ヘルプにも記載されていますが、`Option Base 1`が設定されているかつ`Array()`と記述すると`Variant(1 To 0)`の配列となるため注意が必要です。

```vb
Property Get EmptyVariantArray() As Variant()
    Let EmptyVariantArray = VBA.Array()
End Property
```

## String型

[Split 関数](https://msdn.microsoft.com/ja-jp/vba/language-reference-vba/articles/split-function "Split 関数")に長さ0の文字列を渡して実行すると得られます。

```vb
Property Get EmptyStringArray() As String()
    Let EmptyStringArray = VBA.Split(VBA.vbNullString)
End Property
```

## Byte型

VBAのString型は内部的にはByte型の配列として扱われており、相互に代入が可能です。
長さ0の文字列をByte型の配列に代入すると、要素数0の配列が得られます。

```vb
Property Get EmptyByteArray() As Byte()
    Let EmptyByteArray = VBA.vbNullString
End Property
```

## オブジェクト型

正直なぜこのような動作になるのかわかっていませんが、初期化していないObject型の動的配列を変数に代入すると、要素数0の配列になります。

また、返り値を`Object()`とすると`Object()`型の変数にしか代入できなくなるため`Variant`としています。

```vb
Property Get EmptyObjectArray() As Variant '= Object()
    Dim o() As Object '-> Object()
    Let EmptyObjectArray = o
End Property '-> Object(0 To -1)
```

# 動作確認用コード

上記プロシージャのTypeNameと`UBound`・`LBound`の結果を出力します。

```vb
Sub SampleEmptyArray()
    
    Dim v() As Variant
    v = EmptyVariantArray
    
    Debug.Assert (UBound(v) = -1) And (LBound(v) = 0)
    
    Debug.Print TypeName(v)
    Debug.Print "UBound="; UBound(v)
    Debug.Print "LBound="; LBound(v)
    
    
    Dim s() As String
    s = EmptyStringArray
    
    Debug.Assert (UBound(s) = -1) And (LBound(s) = 0)
    
    Debug.Print TypeName(s)
    Debug.Print "UBound="; UBound(s)
    Debug.Print "LBound="; LBound(s)
    
    Dim b() As Byte
    b = EmptyByteArray
    
    Debug.Assert (UBound(b) = -1) And (LBound(b) = 0)
    
    Debug.Print TypeName(b)
    Debug.Print "UBound="; UBound(b)
    Debug.Print "LBound="; LBound(b)
    
    
    Dim c() As VBA.Collection
    c = EmptyObjectArray
    
    Debug.Assert (UBound(c) = -1) And (LBound(c) = 0)
    
    Debug.Print TypeName(c)
    Debug.Print "UBound="; UBound(c)
    Debug.Print "LBound="; LBound(c)
    
End Sub
```