No Multidim Array Parameter

ID

csharp.no_multidim_array_parameter

Severity

critical

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

CSharp

Tags

api-design, arrays, code_smell, readability

Description

Reports a jagged (int[][]) or multidimensional (int[,]) array in the parameter list of an externally visible method or constructor. The signature tells the caller how many indices to supply and nothing else, so the shape the method actually requires stays undocumented and unchecked.

Rationale

Everything a caller needs to know about a rectangular or nested array lives outside the type. Must the two dimensions match? May the inner arrays of a jagged array have different lengths, or be null? Is an empty outer array acceptable? A parameter typed double[,] answers none of that, so the contract moves into prose the caller does not read and the compiler cannot enforce. The method is then forced to re-validate the shape of every argument on entry, and each caller invents its own convention for building one.

A purpose-built type carries the contract in its own signature. A Matrix type validates its dimensions once in its constructor and exposes an indexer, so a malformed argument cannot be constructed in the first place; a collection of named row objects makes a ragged structure explicit rather than accidental. Either way the method body loses its defensive checks and the caller gains a compiler-checked shape.

Constructors, including primary constructors, are checked alongside methods: a public constructor is where a type first states what it accepts, so it is the signature where the shape contract matters most.

Single-dimension arrays are not reported: int[] has one length and no shape ambiguity. Only signatures reachable from outside the assembly are reported, because an internal or private signature can be reshaped later without breaking anyone.

Four further shapes are left alone because the declaration is not where the shape can be fixed. An override, and a method matching by name and arity a method of an interface the containing type implements, only restate a signature decided on the base or interface declaration — which is reported in its own right when this file declares it. A parameter array hides its outer level from the caller: params int[][] rows is a variadic of int[] and the caller writes M(row1, row2), so jaggedness only begins one level deeper. And the receiver of an extension method is the invocation target rather than an argument, with its type dictated by the type being extended.

public class Solver
{
    public double Determinant(double[,] matrix)        // FLAW — is a square matrix required?
    {
        return 0.0;
    }

    public void Load(int[][] rows)                     // FLAW — may rows be ragged, or null?
    {
    }

    public double Sum(double[] values)                 // OK — one dimension, one length
    {
        return 0.0;
    }

    public void AddRows(params int[][] rows)           // OK — a variadic of int[], not a jagged argument
    {
    }

    internal void Debug(double[,] scratch)             // OK — not part of the published surface
    {
    }

    public double Trace(Matrix matrix)                 // OK — the type states the contract
    {
        return matrix.Trace();
    }
}

public class Grid
{
    public Grid(int[,] cells)                          // FLAW — must the grid be square?
    {
    }
}

Remediation

Replace the array parameter with a type that states the shape it accepts.

  • Introduce a small domain type (Matrix, Grid, Board) that validates its dimensions in its constructor and exposes an indexer. Callers can no longer build an invalid argument.

  • For a ragged structure, accept a collection of row objects or an IReadOnlyList<IReadOnlyList<T>> so the nesting is deliberate and each level can be documented and validated on its own.

  • When the rank is fixed and small, take the components as separate parameters or as a tuple instead of hiding them behind indices.

If the signature cannot change, at minimum reduce its reach: make the member internal and expose a shaped wrapper as the public entry point.