- PR -

昔の VB で要素数 0 の配列を使いたい

投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2005-12-26 14:58
いまだにちょっとしたプログラムには VB(バージョン 5 や 6 あたり)を使うことが多いです。はやく VB からおさらばしたいと思っているのですが、まだまだ逃れることができません。Excel がマクロに VBA を使っていることも一因かもしれません(Excel も良く利用するので)。
VB や VBA でも、一応、クラスもあるし、オブジェクトの扱いも古い言語の割には、非常に今風に使えてあなどれません。多くのことは Java や C# と似たようなロジックで書けます。しかし、配列がどうしてもうまく扱えません。

前置きが長くなりましたが、
とくに、要素数 0 の配列ができないのが非常に苦しいのです。
(昔の C で malloc(0) が保証されていなかったのと同じような雰囲気です。)
解決法としては、つぎのものが思いつきます。

(1) Array を使う( x = Array() みたいに)
  これだと型が使えない。

(2) Collection を使う( Set x = new Collection みたいに)
  これも型が使えない。インデックスが1から始まるのもなんか使いにくい。

(3) 普通の配列を使うが、常にインデックス 0 を使わない。
  うっかりインデックス0にアクセスしてハマりそう。

(4) すっごい裏ワザ
  なにかないですか?

みなさんはどう対処されているのでしょうか?
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2005-12-26 15:10
引用:

unibonさんの書き込み (2005-12-26 14:58) より:

みなさんはどう対処されているのでしょうか?


VB で困るのが、あるメソッドの戻り値が配列だった場合に、
Null (Nothing) で返すことができないことっすね。

要するに、

コード:

Dim stHoges() As String
stHoges = GetHogeArrays()  '配列を返す

If stHoges Is Nothing Then
    Exit Function
End If


といった評価ができない。(実行時エラーになる)
UBound を使っても 0 という値で返らないですし... (実行時エラーになる)

ですので、Is Nothing の代わりに、SafeArrayGetDim 関数と、
GetMem4 関数を使ったメソッドを書いてます。

これのためにわざわざ、クラス モジュールを書くのも...

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
でにす
会議室デビュー日: 2005/12/15
投稿数: 4
投稿日時: 2005-12-26 15:15
えっ?
関数の引数をVariantにするんじゃダメなの?
Nothingも戻せると思うけど?
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2005-12-26 15:25
引用:

でにすさんの書き込み (2005-12-26 15:15) より:

関数の引数をVariantにするんじゃダメなの?
Nothingも戻せると思うけど?


たとえば、String() だと型が一致しないという実行時エラーになると思いますが...

# もしかして、最初の質問を読み違えてますか? > わたし

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
未記入
ぬし
会議室デビュー日: 2004/09/17
投稿数: 667
投稿日時: 2005-12-26 15:34
Split 関数が要素数 0 の配列を返すらしい。

コード:
  a = Split("", "/")
  Debug.Print "aの要素数 " & UBound(a) - LBound(a) + 1


aの要素数 0 となります。LBound(a) = 0, UBound(a) = -1 となるので、LBound から UBound までの For ループで使ってもループ内に制御が移らない。使い方によっては役に立つとか立たないとか。
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2005-12-26 15:57
コード:

'/* MyStrings モジュール */
Option Explicit

Private Declare Sub GetMem4 Lib "MSVBVM60.DLL" ( _
    ByRef aParam() As Any, _
    ByRef lCount   As Long _
)

'/ Is Nothing の代わり
Public Function IsNothing(ByRef stArgs() As String) As Boolean
    Dim lCount As Long

    Call GetMem4(stArgs, lCount)

    If lCount = 0 Then
        IsNothing = True
    Else
        IsNothing = False
    End If
End Function

'/ 未割り当ての状態を取得 (メソッド内は空でもいい)
Public Function GetNothing() As String()
    Dim stNullable() As String

    GetNothing = stNullable
End Function

'/ 配列 0 の状態を取得
Public Function GetEmpty() As String()
    GetEmpty = Split(vbNullString, vbNullChar)
End Function


とりあえず、実験結果。

コード:

'/* 実験用の Form */
Option Explicit

Private Sub Command1_Click()
    '/ 今回のターゲット
    Dim stHoges() As String

    '/ 未割り当てなので Null
    If MyStrings.IsNothing(stHoges()) = True Then
        Call MsgBox("Null")
    Else
        Call MsgBox("Not Null")
    End If

    '/ 要素を 1 つ割り当てる
    ReDim stHoges(0)

    '/ 割り当ててるので Not Null
    If MyStrings.IsNothing(stHoges()) = True Then
        Call MsgBox("Null")
    Else
        Call MsgBox("Not Null")
    End If

    '/ 未割り当てにする
    stHoges = MyStrings.GetNothing()

    '/ 未割り当てなので Null
    If MyStrings.IsNothing(stHoges()) = True Then
        Call MsgBox("Null!")
    Else
        Call MsgBox("Not Null!")
    End If

    '/ 実行時エラーになる
    'Call MsgBox(CStr(UBound(stHoges)))

    '/ 要素数 0 の配列にしてみる
    stHoges = MyStrings.GetEmpty()

    '/ 0 となる
    Call MsgBox(CStr(LBound(stHoges)))
    '/ -1 となる
    Call MsgBox(CStr(UBound(stHoges)))
End Sub


うーん、厳密には Null (Nothing) という文言じゃダメなんでしょうけど。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2005-12-26 23:12
みなさまありがとうございます。
質問を補足すると、たとえば Java でいうと、こんな、
コード:
public class Hoge {
    
    public static int[] foo(int x) {
        if (x < 0) {
            return null;
        } else if (x == 0) {
            return new int[0];
        } else if (x > 0) {
            return new int[x];
        } else {
            throw new IllegalStateException();
        }
    }

    public static void bar(int[] x) {
        for (int i = 0; i < x.length; i++) {
            System.out.println("i = " + i + ", value = " + x[i]);
        }
    }
    
    public static void main(String[] args) {
        int[] a = Hoge.foo(-1); // null
        if (a != null) {
            bar(a);
        }
        
        int[] b = Hoge.foo(0); // int[0]
        if (b != null) {
            bar(b);
        }

        int[] c = Hoge.foo(1); // int[1]
        if (c != null) {
            bar(c);
        }
    }
}

感じのことを、VB でもやりたいのです。

すなわち、上記でいうところの変数 b の状態を実現したいです。
要素数 0 を特別扱いせずに使いたいのです。
b と c を区別せずに扱いたいのです。

また、ついでに a の状態もやれるものなら実現したいです。
こっちは VB でも変数の型を Variant にして Null を使えばできますが、型がなくなってしまうので...

引用:

未記入さんの書き込み (2005-12-26 15:34) より:
Split 関数が要素数 0 の配列を返すらしい。


Split を試そうとしたのですが、これは VB6 以上から備わっているみたいですね。あと、これは String 専用になってしまうという捉え方であっているでしょうか。

引用:

未記入さんの書き込み (2005-12-26 15:34) より:
コード:
  a = Split("", "/")
  Debug.Print "aの要素数 " & UBound(a) - LBound(a) + 1


aの要素数 0 となります。LBound(a) = 0, UBound(a) = -1 となるので、LBound から UBound までの For ループで使ってもループ内に制御が移らない。使い方によっては役に立つとか立たないとか。

そうです。もくろみはこれです。要素数 0 でも LBound や UBound をシームレスに使いたいわけです。

引用:

じゃんぬねっとさんの書き込み (2005-12-26 15:10) より:
ですので、Is Nothing の代わりに、SafeArrayGetDim 関数と、
GetMem4 関数を使ったメソッドを書いてます。


SafeArrayGetDim や GetMem4 などの API を使うとできるのでしょうか。このあたりをちょっと調べてみます。
#しかし GetMem4 に至っては Google で検索しても日本語だと1件しかヒットしない。
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2005-12-26 23:25
引用:

unibonさんの書き込み (2005-12-26 23:12) より:

SafeArrayGetDim や GetMem4 などの API を使うとできるのでしょうか。このあたりをちょっと調べてみます。


一応使用例のコードは提示していますよ。
ただ、見て頂くと察しが付くと思いますが、これも、どの VB でも使えるというわけでは...

# 基本的には 0 だけ (要素数:1) の場合を null と扱ったりするしか逃げ道がないです。
# あと、エラー処理という方法もありますが、これは生理的に受け付けません。。。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌

スキルアップ/キャリアアップ(JOB@IT)