C# Params Multiple Arguments
c# params multiple arguments: Learn how the params keyword in C# lets you write methods that accept multiple arguments without explicit arrays, with practical examples...
When you want a C# method to accept a varying number of arguments, the params keyword is the standard tool. Instead of forcing callers to build an array or a list before every call, params lets them pass arguments directly. This article focuses on the practical syntax, behavior, and tradeoffs of using c# params multiple arguments in real code.
The Core Syntax: One Keyword, Many Arguments
A method that uses params declares a single array parameter, but callers can pass any number of individual arguments. The compiler collects those arguments into the array before the method body runs.
public static int Sum(params int[] numbers) { int total = 0; foreach (int n in numbers) { total += n; } return total; }
Callers can pass zero, one, or many integers:
int a = Sum(); // 0 int b = Sum(10); // 10 int c = Sum(1, 2, 3); // 6
The array is still a real array—it is allocated on the heap, and the method can inspect its Length property. But the call-site syntax avoids the boilerplate of new int[] { ... }.
The params keyword can appear only once in a method signature, and it must be the last parameter. That rule keeps the compiler unambiguous about where the variable part begins.
Calling with Arrays, Lists, or Existing Collections
params accepts an array directly, which is useful when a value is already stored as an array. A List<int> or any other IEnumerable<int> cannot be passed directly, because the compiler only converts individual arguments into the array—it does not iterate over a collection automatically.
int[] values = { 4, 5, 6 }; int d = Sum(values); // Works: copies the array reference List<int> list = new List<int> { 7, 8 }; // int e = Sum(list); // Compiler error: cannot convert from List<int> to int[]
To pass a list, call .ToArray() explicitly, which creates a new array and copies the elements.
int e = Sum(list.ToArray());
The same applies to LINQ results or any IEnumerable<int>. If you need to pass an arbitrary collection without the allocation cost, an overload or a different parameter type may be a better design.
Overloading with Regular Parameters
A common pattern is to provide an overload that takes individual typed parameters, then delegates to a params version. This is useful when you want to encourage a specific number of arguments while still supporting a variable list.
public static string Format(string template, params object[] args) { // Implementation using string.Format-like logic }
Overloading can get tricky when a method has both a params parameter and other overloads. The compiler uses the most specific match. Consider:
public static void Log(string message) { } public static void Log(string format, params object[] args) { }
A call like Log("Hello") resolves to the single-parameter overload, not the params one, because it requires fewer conversions. That is usually the expected behavior, but it is worth remembering when you expect a single argument to be treated as an array.
Handling Null and Empty Argument Lists
When a caller passes no arguments, the params array is an empty array, not null. That means a foreach loop or a Length check works safely without a null guard.
However, a caller can explicitly pass null as the array:
Sum(null);
That compiles because null is a valid int[]. Inside the method, the array reference is null, so a guard is sometimes necessary.
if (args == null) { return 0; // or throw ArgumentNullException }
The choice depends on whether a null argument indicates a caller bug or a legitimate empty state. For most APIs, it is safer to throw an ArgumentNullException with a clear parameter name.
Performance and Allocation Costs
The params keyword introduces a subtle allocation. If the method actually receives multiple arguments, the compiler generates an array allocation at the call site. If the method receives a single argument, the compiler may optimize away the array if the params parameter is an object[] and the argument is a reference type—but for value types like int, the array is still allocated.
More importantly, the array is a copy-on-write reference. The method receives a reference to the array the caller created. If the caller passes an existing array, no copying occurs; the method can observe changes the caller makes after the call. That can be surprising.
int[] data = { 1, 2 }; Sum(data); data[0] = 99; // The method saw the original values
Because the array is not cloned, methods that modify the array will mutate the caller's data. If your method needs to treat the arguments as immutable, copy them into a new array or document the mutation behavior.
Using Params for Format Strings and Logging
A classic use of params is to pass a format string and a variable set of values. This is how .NET's string.Format and logging frameworks accept arguments.
public static void LogMessage(string format, params object[] parameters) { string message = string.Format(format, parameters); // Write to file or output }
Callers can write:
LogMessage("User {0} logged in at {1}", userId, time);
The downside is loss of type safety: any object can be passed, and if the number of placeholders does not match the number of parameters, an exception occurs at runtime. That is a reasonable tradeoff for flexible logging, but avoid it for APIs where compile-time checks are feasible.
Variant: Params with Generic Types
You can use params with a generic type, but the compiler infers the array element type from the arguments.
public static T FirstOrDefault<T>(params T[] items) { return items.Length > 0 ? items[0] : default; }
Calls like FirstOrDefault(1, 2) and FirstOrDefault("a", "b") work naturally. However, you cannot specify a params parameter with a nullable type directly if the inference creates mixed types. For heterogeneous arguments, use object[].
Edge Cases and Common Pitfalls
One subtle issue is using params with an optional parameter. The params parameter itself is optional in the sense that the caller may provide zero arguments, but you cannot combine it with another optional parameter after it—the params must be last, and an optional parameter after would violate that rule.
Another common mistake is trying to use params in an interface implementation. The method signature in the interface must match exactly, including the params keyword. If the interface declares void Print(params object[] args), the implementing class must also use params. Otherwise, the compiler treats it as a different method.
When to Choose a Collection Parameter Instead
params is convenient, but it is not always the best API design. If a method operates on a large collection, requiring an explicit IEnumerable<T> or IReadOnlyList<T> communicates intent better and avoids the array allocation entirely.
| Criterion | params array | List or IEnumerable |
|---|---|---|
| Caller convenience | High | Lower (must build collection) |
| Allocation per call | Yes (array) | No if caller reuses collection |
| Type safety | Depends on element type | Stronger with generic constraints |
| Suitability | Small, fixed-format calls | Large or dynamic data sets |
Use params when a method naturally receives a small number of arguments that vary per call, such as string.Concat, Math.Max (though that uses overloads), or a custom aggregation. Prefer a collection when the set of inputs is computed dynamically, grows unbounded, or must be processed incrementally.
In summary, params remains a useful language feature for c# params multiple arguments because it simplifies call-site code without introducing new types. The main tradeoffs are the array allocation, the loss of compile-time arity checks for object[], and the reference semantics of the array. Understanding these behaviors lets you use params where it adds clarity and avoid it where a more explicit parameter model is better.