Summary#
params collects the trailing arguments into an array:
class Calc {
public decimal Total(params decimal[] xs) {
decimal t = 0m;
foreach (var x in xs) { t = t + x; }
return t;
}
}
[Test]
void Params_Collects_The_Trailing_Arguments() {
var c = new Calc();
Assert.Equal(6m, c.Total(1m, 2m, 3m));
Assert.Equal(0m, c.Total()); // no trailing arguments — an EMPTY array, not null
}Signature#
<Return> <Name>(params <T>[] <rest>) { … }
<Return> <Name>(<T> <first>, params <T>[] <rest>) { … }Description#
Where can params go, and what type must it be?#
Two rules, both C#'s, and both following from what params does — it collects the rest:
decimal Sum(params decimal[] xs, string tail) { … } // ✗ it must be the LAST parameter
decimal Sum(params decimal x) { … } // ✗ its type must be an ARRAY
decimal Sum(params decimal[] xs = null) { … } // ✗ it is already optional; no defaultA parameter before it is ordinary and keeps its own argument:
class Report {
public decimal Offset(decimal start, params decimal[] xs) {
decimal t = start;
foreach (var x in xs) { t = t + x; }
return t;
}
}
[Test]
void The_Leading_Parameter_Keeps_Its_Own_Argument() {
var r = new Report();
Assert.Equal(103m, r.Offset(100m, 1m, 2m));
}Passing an array directly still works#
If you already have the array, pass it — it binds to the parameter as-is rather than being wrapped in another array:
[Test]
void An_Actual_Array_Is_Not_Wrapped_Again() {
var c = new Calc();
decimal[] values = [4m, 5m];
Assert.Equal(9m, c.Total(values)); // one array of two — not one array containing one array
}Which of the two readings applies is decided by the argument's type, not by how many arguments there are — both
spellings pass exactly one. A decimal[] is the array; a decimal is one element of it.
params and overloads#
A params method takes part in overload resolution like any other, with one rule: the ordinary
reading is tried first for every candidate, and the collecting form is considered only if nothing matched ordinarily.
That has a consequence worth relying on: adding params to an existing method cannot change which overload an
existing call already picks. It can only make a call compile that did not before.
class Fmt {
public string Of(decimal d) { return "one"; }
public string Of(params decimal[] ds) { return "many"; }
}
[Test]
void The_Ordinary_Reading_Is_Tried_First() {
var f = new Fmt();
Assert.Equal("one", f.Of(1m)); // the exact single-argument method
Assert.Equal("many", f.Of(1m, 2m)); // only the collecting one can take two
}params is not part of a method's identity, so Sum(params decimal[]) and Sum(decimal[]) are the same method
and cannot both be declared — a call site could not tell them apart.
What is the collected array, inside the body?#
Inside the body, rest is an ordinary array: foreach over it, ask for its Count(), index it, pass it on. There is
nothing to learn beyond the call form.
See also#
- class methods — methods, and the member-body surface
- Method overloads — how a call site picks from a set of same-named methods
staticmethods —static, which combines withparamsfreely- Classes — fields,
const, and what a class is