JAG配列がおかしい!?

この章では、従来版VBVBAなどとの違いで懸案事項に揚がったことなどを説明します。
JAG配列」って何?   私も近年までこの名前は知らなかったのですが、要は「配列の中に配列を入れて2次元配列として利用する」やりかたのことです。
そもそもは2次元配列で1次側の要素数を収容内容に影響せずに任意に増減できればこのようなことはやらないのですが、多次元配列では末端の次元しか収容内容に影響せずに任意に増減できません。 このため、縦軸方向となる1次元配列を用意しておき、さらに横軸方向となる1次元配列を要素数を決めて内容を収容させてから縦軸方向の配列の1要素の中に収容させるといったことを行なうと、 縦軸方向も見かけは1次元配列なので収容された内容に影響させずに要素数を増やすことが可能になるわけです。
JAG」というのは「ギザギザ」のことだそうで、これは縦軸方向となる1次元配列に収容させる横軸方向となる1次元配列の要素数が「行」ごとにことなっても構わないことから 「ギザギザ」な2次元配列が作成できるというようなことからくる名前らしいです。
なお、「配列の中に配列を入れて」という表現になっていますが、実際には「1次元配列の中の1要素を配列化させる」ということでも可能なのです。

従来版VBVBAでは、このように動作します。
今回は、コード記述上での問題なので、まずはソースを見てみます。

'******************************************************************************
' 配列内に配列を納める(JAG配列)サンプル
'******************************************************************************
Option Explicit
Private Const g_cnsTitle As String = "JAG配列のテスト"
Private Type typCOL
    COL As Variant
End Type

'******************************************************************************
' 配列内に配列を納める(JAG配列)サンプル(VB6,VBAでは正常な動作)
'******************************************************************************
Sub JAG_ArrayTEST1()
    Dim tblCOL As Variant           ' カラム方向の配列
    Dim tblROW() As typCOL          ' 行方向の配列
    Dim strMSG As String

    '--------------------------------------------------------------------------
    ' ■行方向の第1要素
    '--------------------------------------------------------------------------
    ' カラム方向の配列要素数の決定と値のセット
    tblCOL = Array(1, 2, 3)
    ' 行方向の配列にカラム方向の配列を格納
    ReDim tblROW(0)
    'ReDim tblROW(0).COL(2)         ' この記述は省略
    tblROW(0).COL = tblCOL
    ' 状態を検証
    With tblROW(0)
        strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
    End With
    MsgBox strMSG,, g_cnsTitle

    '--------------------------------------------------------------------------
    ' ■行方向の第2要素
    '--------------------------------------------------------------------------
    ' カラム方法の配列要素の値を変更
    tblCOL = Array(4, 5, 6)
    ' 行方向の配列にカラム方向の配列を格納
    ReDim Preserve tblROW(1)
    'ReDim tblROW(1).COL(2)         ' この記述は省略
    tblROW(1).COL = tblCOL
    ' 状態を検証
    With tblROW(0)
        strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
    End With
    With tblROW(1)
        strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
    End With
    MsgBox strMSG,, g_cnsTitle

    '--------------------------------------------------------------------------
    ' ■行方向の第3要素
    '--------------------------------------------------------------------------
    ' カラム方法の配列要素の値を変更
    tblCOL = Array(7, 8, 9)
    ' 行方向の配列にカラム方向の配列を格納
    ReDim Preserve tblROW(2)
    'ReDim tblROW(2).COL(2)         ' この記述は省略
    tblROW(2).COL = tblCOL
    ' 状態を検証
    With tblROW(0)
        strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
    End With
    With tblROW(1)
        strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
    End With
    With tblROW(2)
        strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
    End With
    MsgBox strMSG,, g_cnsTitle

    '--------------------------------------------------------------------------
    ' ■行方向の第3要素を変えみる
    '--------------------------------------------------------------------------
    ' ここでは配列を押し込まずにJAG配列内の値を直接変更してみる
    tblROW(2).COL(0) = 10
    tblROW(2).COL(1) = 11
    tblROW(2).COL(2) = 12
    ' 状態を検証
    With tblROW(0)
        strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
    End With
    With tblROW(1)
        strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
    End With
    With tblROW(2)
        strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
    End With
    MsgBox strMSG,, g_cnsTitle
End Sub
横方向の配列「tblCOL」は要素数3で固定して値をセットします。これを縦方向の配列「tblROW」の要素数を1つずつ増やしながら増やした要素位置にセットするという単純なものです。 なお、縦方向は、単なる「Variant型」ではなく、「Withステートメント」が使えるようにユーザー定義型を介しています。
このような配列を内部で作成することは少ないかも知れませんが、ADO.NETのデータテーブルで「dbTbl.Rows(IX)(COL)」などという記述を扱うのは、作る記述はないものの、まさしくこの「JAG配列」を扱っていることになります。

さて、ここでメッセージボックスが4回表示され、縦方向の要素数が変異していく中で、過去にセットした次元の値を含めて内容を表示させるようにしているので、動作結果を見てみましょう。ここではExcelVBAで動かしています。
JAG配列のテスト1
左から順にこのように表示されました。
ここで、見ていただきたいのは、「Preserve」付きの「ReDimステートメント」ですから、当然、以前に配列に格納された値には影響せずに配列要素数が変更されるという「当たり前」のことです。

では、このコードを「Visual Basic.NET(2005以降)」で動かすと...
では、今回は「Visual Basic 2008」で、新しいWindowsフォームのプロジェクトを用意し、ボタンのClickイベントのプロシージャを用意してから、上記のコードを貼り付けて利用します。 「Variant」は「Object」に勝手に変更されますが、「Array関数」のところだけエラーになるので、下記のように変更しておきます。

'************************************************************************************
' 配列内に配列を納める(JAG配列)サンプル
'************************************************************************************
Public Class Form1
    Private Const g_cnsTitle As String = "JAG配列のテスト"
    Private Structure typCOL
        Dim COL As Object
    End Structure

    '********************************************************************************
    ' 配列内に配列を納める(JAG配列)サンプル(VB2005/2008ではおかしい)
    '********************************************************************************
    Private Sub BTN_TEST1_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BTN_TEST1.Click
        Dim tblCOL() As Integer         ' カラム方向の配列
        Dim tblROW() As typCOL          ' 行方向の配列
        Dim strMSG As String

        '----------------------------------------------------------------------------
        ' ■行方向の第1要素
        '----------------------------------------------------------------------------
        ' カラム方向の配列要素数の決定と値のセット
        ReDim tblCOL(2)
        tblCOL(0) = 1
        tblCOL(1) = 2
        tblCOL(2) = 3
        ' 行方向の配列にカラム方向の配列を格納
        ReDim tblROW(0)
        'ReDim tblROW(0).COL(2)         ' この記述は省略
        tblROW(0).COL = tblCOL
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第2要素
        '----------------------------------------------------------------------------
        ' カラム方法の配列要素の値を変更
        tblCOL(0) = 4
        tblCOL(1) = 5
        tblCOL(2) = 6
        ' 行方向の配列にカラム方向の配列を格納
        ReDim Preserve tblROW(1)
        'ReDim tblROW(1).COL(2)         ' この記述は省略
        tblROW(1).COL = tblCOL
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第3要素
        '----------------------------------------------------------------------------
        ' カラム方法の配列要素の値を変更
        tblCOL(0) = 7
        tblCOL(1) = 8
        tblCOL(2) = 9
        ' 行方向の配列にカラム方向の配列を格納
        ReDim Preserve tblROW(2)
        'ReDim tblROW(2).COL(2)         ' この記述は省略
        tblROW(2).COL = tblCOL
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(2)
            strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第3要素を変えみる
        '----------------------------------------------------------------------------
        ' ここでは配列を押し込まずにJAG配列内の値を直接変更してみる
        tblROW(2).COL(0) = 10
        tblROW(2).COL(1) = 11
        tblROW(2).COL(2) = 12
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(2)
            strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)
    End Sub
End Class
このようなコードになります。

では、これを実行すると、
JAG配列のテスト2
こんな結果になってしまいました。
きちんと「Preserve」付きの「ReDimステートメント」で要素数を変更してから、追加した要素だけを更新したはずなのに、以前の要素まで書き換わってしまいました。 実は、この現象は、セットする横軸方向の配列のデータ型がInteger型などで明示された配列であれば問題を起こさないとか、Express EditionProfessional Editionで動作が違うようだとかの未検証部分はありますが、このコードはどのEditionでも同じ結果になるはずです。

これはバグなのでしょうか。
従来版VBVBAから見れば「バグ」だという判断になってしまうのですが、これらは新しいメソッドを使うと解決できます。

'************************************************************************************
' 配列内に配列を納める(JAG配列)サンプル
'************************************************************************************
Public Class Form1
    Private Const g_cnsTitle As String = "JAG配列のテスト"
    Private Structure typCOL
        Dim COL As Object
    End Structure

    '********************************************************************************
    ' 配列内に配列を納める(JAG配列)サンプル(これならOK(CopyTo)!?)
    '********************************************************************************
    Private Sub BTN_TEST2_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BTN_TEST2.Click
        Dim tblCOL() As Integer         ' カラム方向の配列
        Dim tblROW() As typCOL          ' 行方向の配列
        Dim strMSG As String

        '----------------------------------------------------------------------------
        ' ■行方向の第1要素
        '----------------------------------------------------------------------------
        ' カラム方向の配列要素数の決定と値のセット
        ReDim tblCOL(2)
        tblCOL(0) = 1
        tblCOL(1) = 2
        tblCOL(2) = 3
        ' 行方向の配列にカラム方向の配列を格納
        ReDim tblROW(0)
        ReDim tblROW(0).COL(2)
        tblCOL.CopyTo(tblROW(0).COL, 0)  ' CopyToメソッドで転記
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第2要素
        '----------------------------------------------------------------------------
        ' カラム方法の配列要素の値を変更
        tblCOL(0) = 4
        tblCOL(1) = 5
        tblCOL(2) = 6
        ' 行方向の配列にカラム方向の配列を格納
        ReDim Preserve tblROW(1)
        ReDim tblROW(1).COL(2)
        tblCOL.CopyTo(tblROW(1).COL, 0)  ' CopyToメソッドで転記
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第3要素
        '----------------------------------------------------------------------------
        ' カラム方法の配列要素の値を変更
        tblCOL(0) = 7
        tblCOL(1) = 8
        tblCOL(2) = 9
        ' 行方向の配列にカラム方向の配列を格納
        ReDim Preserve tblROW(2)
        ReDim tblROW(2).COL(2)
        tblCOL.CopyTo(tblROW(2).COL, 0)  ' CopyToメソッドで転記
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(2)
            strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第3要素を変えみる
        '----------------------------------------------------------------------------
        ' ここでは配列を押し込まずにJAG配列内の値を直接変更してみる
        tblROW(2).COL(0) = 10
        tblROW(2).COL(1) = 11
        tblROW(2).COL(2) = 12
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(2)
            strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)
    End Sub
End Class
このコードを動作させると、
JAG配列のテスト3
所望の通りの結果になるのです。

コード上での変更箇所は、

        'ReDim tblROW(0).COL(2)         ' この記述は省略
        tblROW(0).COL = tblCOL
と記述していた部分(配列要素ごとに3カ所)を

        ReDim tblROW(0).COL(2)
        tblCOL.CopyTo(tblROW(0).COL, 0)  ' CopyToメソッドで転記
このように変更するだけです。ですから計6行が異なるだけです。

ちなみに、

'************************************************************************************
' 配列内に配列を納める(JAG配列)サンプル
'************************************************************************************
Public Class Form1
    Private Const g_cnsTitle As String = "JAG配列のテスト"
    Private Structure typCOL
        Dim COL As Object
    End Structure

    '********************************************************************************
    ' 配列内に配列を納める(JAG配列)サンプル(配列ごと押し込むのはNG!?)
    '********************************************************************************
    Private Sub BTN_TEST3_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BTN_TEST3.Click
        Dim tblROW() As typCOL          ' 行方向の配列
        Dim strMSG As String

        '----------------------------------------------------------------------------
        ' ■行方向の第1要素
        '----------------------------------------------------------------------------
        ' 行方向の配列にカラム方向の配列を格納
        ReDim tblROW(0)
        With tblROW(0)
            ReDim .COL(2)
            .COL(0) = 1
            .COL(1) = 2
            .COL(2) = 3
            ' 状態を検証
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第2要素
        '----------------------------------------------------------------------------
        ' 行方向の配列にカラム方向の配列を格納
        ReDim Preserve tblROW(1)
        With tblROW(1)
            ReDim .COL(2)
            .COL(0) = 4
            .COL(1) = 5
            .COL(2) = 6
        End With
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第3要素
        '----------------------------------------------------------------------------
        ' 行方向の配列にカラム方向の配列を格納
        ReDim Preserve tblROW(2)
        With tblROW(2)
            ReDim .COL(2)
            .COL(0) = 7
            .COL(1) = 8
            .COL(2) = 9
        End With
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(2)
            strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第3要素を変えみる
        '----------------------------------------------------------------------------
        ' ここでは配列を押し込まずにJAG配列内の値を直接変更してみる
        With tblROW(2)
            .COL(0) = 10
            .COL(1) = 11
            .COL(2) = 12
        End With
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(2)
            strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)
    End Sub
End Class
このように配列を配列に「押し込む」方法ではなく、横軸方向の配列要素を1つずつセットしていく方法であれば「CopyToメソッド」を使わなくても問題はありません。
さらに、試していると、

'************************************************************************************
' 配列内に配列を納める(JAG配列)サンプル
'************************************************************************************
Public Class Form1
    Private Const g_cnsTitle As String = "JAG配列のテスト"
    Private Structure typCOL
        Dim COL As Object
    End Structure

    '********************************************************************************
    ' 配列内に配列を納める(横軸方向の配列を毎回初期化すると!?)
    '********************************************************************************
    Private Sub BTN_TEST5_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BTN_TEST5.Click
        Dim tblCOL As Object            ' カラム方向の配列
        Dim tblROW() As typCOL          ' 行方向の配列
        Dim strMSG As String

        '----------------------------------------------------------------------------
        ' ■行方向の第1要素
        '----------------------------------------------------------------------------
        ' カラム方向の配列要素数の初期化と値のセット
        tblCOL = New Integer() {1, 2, 3}
        ' 行方向の配列にカラム方向の配列を格納
        ReDim tblROW(0)
        'ReDim tblROW(0).COL(2)         ' この記述は省略
        tblROW(0).COL = tblCOL
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第2要素
        '----------------------------------------------------------------------------
        ' カラム方向の配列要素数の初期化と値のセット
        tblCOL = New Integer() {4, 5, 6}
        ' 行方向の配列にカラム方向の配列を格納
        ReDim Preserve tblROW(1)
        'ReDim tblROW(1).COL(2)         ' この記述は省略
        tblROW(1).COL = tblCOL
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第3要素
        '----------------------------------------------------------------------------
        ' カラム方向の配列要素数の初期化と値のセット
        tblCOL = New Integer() {7, 8, 9}
        ' 行方向の配列にカラム方向の配列を格納
        ReDim Preserve tblROW(2)
        'ReDim tblROW(2).COL(2)         ' この記述は省略
        tblROW(2).COL = tblCOL
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(2)
            strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)

        '----------------------------------------------------------------------------
        ' ■行方向の第3要素を変えみる
        '----------------------------------------------------------------------------
        ' ここでは配列を押し込まずにJAG配列内の値を直接変更してみる
        tblROW(2).COL(0) = 10
        tblROW(2).COL(1) = 11
        tblROW(2).COL(2) = 12
        ' 状態を検証
        With tblROW(0)
            strMSG = "(0)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(1)
            strMSG = strMSG & vbCr & "(1)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        With tblROW(2)
            strMSG = strMSG & vbCr & "(2)=" & .COL(0) & "-" & .COL(1) & "-" & .COL(2)
        End With
        MsgBox(strMSG,, g_cnsTitle)
    End Sub
End Class
このコードも問題ないことがわかりました。
横軸方向の配列を縦軸方向の配列に押し込む場合に、このサンプルは「毎回初期化(Newキーワードで)」しているのです。 つまり、上記の不具合と思われる事象は、初期化し直さずに同じ縦軸方向の配列を押し込んだ場合、同じ縦軸方向の配列を用いた全ての行が同時に書き換わってしまうということのようです。 これは、横軸配列を縦軸方向の配列に押し込んだ当座だけでなく、それ以降も連動して動作してしまうということだという現象になっています。

ですが、私がこのような一時的な配列を用意するのは、Excelのシートへの転記動作をできるだけ速く行なう必要性から転記回数を減らすため、「値」「計算式」「空要素」を含めて100列以上を一時的に配列に納めることを考えたわけで、 しかも明細項目レベルの合計を「上」に表現しなければならなかったため、明細を先に横軸方向の1次配列を通して縦軸方向の配列に確保しながら「伝票計」を1番上の要素に再計算させた上で収容された要素数分をExcelシート上に行単位にセットするという方法を採ったからです。
横軸方向には、年間各月度が配置され、「予算」「実績」「差異」「達成率」が各月度に繰り返され、「予算」「実績」は値、「差異」「達成率」は計算式です。当然、計算式は縦軸方向の配列には事前にセットしておき、行毎にセットし直すことはありません。
このことから、縦軸方向の配列の1要素に対して、横軸方向の項目を1つずつセットするとか、毎行ごとに初期化するようなことではこの方法を採る意味がなくなってしまいます。 従って、「CopyToメソッド」を使うことが「従来互換」だということを熟知しておく必要があると思います。