VB.NETでReDim Preserveを使うくらいなら配列を使うのをやめたら?
はじめに
弊社のブログで現在一番アクセス数が多いのは(個人的には意外ですが)ReDim
の記事です。
この記事でもReDim Preserve
には否定的な感じで書いていますが、現在でもこの意見は変わりません。
今北産業
- 添え字操作がバグの温床になる
- 配列の使用意図を読み取るのに文脈に読み解かないといけないから可読性がマッハで低下する
- マジで気を付けないとパフォーマンス的にデメリットになるし、気を付けられる人はそもそもVBを
つkにゃーん
⇒定数として複数の値を定義したり、配列しか受け入れないメソッドへの引数で使う以外でVBで配列とReDim Preserve
を使う価値はない
How about ReDim?
ReDim
ってどういう仕組みで配列の拡張または縮小を行っているのか確認してみましょう。
Dim a As String() = New String() {"Hello", "World"} ReDim Preserve a(2)
のコードは以下のコードと等価です。
Dim a As String() = New String() {"Hello", "World"} Dim b(2) As String For i As Integer = 0 To a.GetLength(0) b(i) = a(i) Next a = b
単純に言ってしまうと、新しいサイズの配列を生成して値をコピーしているだけです。
ReDim Preserve
に否定的な理由
そんじゃ、なんで弊社がReDim Preserve
(というか配列そのもの)に否定的な意見*1なのかと言いますと
配列の添え字の制御が面倒(かつバグの温床になりやすい)
ReDim Preserve
を使う場面って最初からデータの数が正確に予想できない場合がほとんどだと思います。
例えば以下のコードのように入力をバッファする場合、正確な数が予想できないため必要に応じて配列を拡張する必要があります。
Dim currentIndex = 0 Dim buffer(4) As String While True Dim input = Console.ReadLine() If input = "q" Then Exit While End If If currentIndex > buffer.GetLength(0) Then ReDim Preserve buffer(buffer.GetLength(0) + 5) End If buffer(currentIndex) = input currentIndex += 1 End While For Each it In buffer If it <> Nothing Then Console.WriteLine(it) End If Next
入力された値をバッファし、最後に一気に出力しています。
それで、こちらがQueue(Of T)
を使ったバージョン。
Dim buffer As New Queue(Of String) While True Dim input = Console.ReadLine() If input = "q" Then Exit While End If buffer.Enqueue(input) End While For Each it In buffer Console.WriteLine(it) Next
余計な変数が1つ消え、めんどくさい添え字計算も省略できています。*2
VBの主戦場たる生産性アプリケーションでは基本的にそこまでクリティカルな性能が求められることはなく*3、むしろコードでどこまでドメインを表現できるか・保守性の高いコードであるかが求められると考えています。
後述しますが、そのコードでのドメインの表現・保守性を考えると、配列の添え字を操作するよりも適切なコレクションクラスを用いてドメインのセマンティクスをコード上で直接表現したほうが保守性が高くなるのではないでしょうか。
使用意図が分かりずらい
添え字計算を駆使すれば、配列はキュー*4・スタック・リングバッファー等々のセマンティクスを表現できます。
表現できるのはいいのですが、その駆使された添え字計算について後から読まなくてはならないあなたの後任の事も考えてあげましょう。
つまるところ、ぐちゃぐちゃな添え字計算を読み取って使用意図をくみ取るのと、専用のコレクションが使われているのを読み取るのでは、将来あなたの後任にとってどちらが楽ですか?という事です。
多用するとパフォーマンス的に不利になりやすい
上でも述べましたが、ReDim Preserve
は新しい領域を確保して値をコピーした後に古い領域をガベージコレクタにブン投げます。
特に配列のサイズが大きい*5ケースでReDim Preserve
を多用するとメモリアロケーションとガベージコレクションが多発してパフォーマンスに明らかな悪影響を及ぼします。
代替手段
ほとんどのケースでは
の中から適したコレクションクラスを選択するだけで要求を満足するはずです。
(古い記事では特に)配列の代替としてArrayList
を推しているケースが多いですが、パフォーマンス・型安全の点からみてもジェネリックコレクションと比べたメリットが無いので2秒でゴミ箱にぶち込みましょう。