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.