Externally visible member takes a multidimensional array parameter

ID

vbnet.performance.no_multidim_array_parameter

Severity

critical

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Performance

Language

VB.NET

Description

Reports a parameter declared as a multidimensional (rectangular) array - a type whose array suffix contains a comma, such as Double(,) or Byte(,,) - on a member that forms part of a type’s externally visible surface: a Public or Protected method or function, a method declared on an Interface, and a MustOverride member. Jagged arrays (Double()()) and single-dimension arrays (Double()) are not reported, and neither are multidimensional arrays used only as locals, fields or property types - the rule is about what crosses the API boundary. Methods that are Private or Friend are not examined.

Rationale

A rectangular array is stored as one contiguous block addressed through a computed offset, and the runtime has no dedicated instruction for that computation: every element access multiplies the row index by the row length, adds the column index, and performs a bounds check against the dimension descriptors held in the array header. Single-dimension access, by contrast, is one of the operations the JIT optimizes hardest - it recognises the pattern, hoists the bounds check out of a counted loop, and emits a direct scaled index. A jagged array is a single-dimension array of single-dimension arrays, so it gets that treatment on both levels, which is why the same nested loop typically runs measurably faster over Double()() than over Double(,) once the loop is hot. The cost is paid by the caller as well as the implementation. Rectangular arrays cannot be built up a row at a time, so a caller assembling data from a query, a file or a service has to count the rows first and then copy them into a preallocated block, and a caller that already holds jagged data - the normal shape when rows arrive one at a time or have different lengths - must flatten it into a rectangle purely to satisfy the signature. Because the shape is fixed at allocation, resizing means allocating a new block and copying everything. Putting the rectangular form in a signature also propagates it: it is the one place the decision cannot be revisited later without a breaking change, since every caller and every override is written against it. Rectangular arrays are additionally awkward for consumers outside Visual Basic and C# - several .NET languages and most interop and serialization layers handle only single-dimension arrays.

The following code illustrates the pattern detected by this rule:

Public Class MatrixOperations

    ' FLAGGED: Externally visible member takes a multidimensional array parameter
    Public Sub Normalize(samples As Double(,))
        Rescale(samples, 1.0)
    End Sub

Remediation

Declare the parameter as a jagged array - Double()() instead of Double(,) - and index it in two steps, grid(row)(column). Where the data is genuinely rectangular and that invariant matters, prefer a small type that owns a single-dimension backing array and exposes row and column accessors, which keeps the fast indexing and lets the type enforce the shape. Keep any rectangular array that is required by an external contract inside the implementation and convert at the boundary.

' Before: rectangular array in the public signature
Public Sub Normalize(samples As Double(,))
    For row = 0 To samples.GetUpperBound(0)
        For column = 0 To samples.GetUpperBound(1)
            samples(row, column) /= Scale
        Next
    Next
End Sub

' After
Public Sub Normalize(samples As Double()())
    For row = 0 To samples.Length - 1
        Dim values = samples(row)
        For column = 0 To values.Length - 1
            values(column) /= Scale
        Next
    Next
End Sub

Configuration

This detector does not need any configuration.