Back to Blog
C#

C# Params Collection: Syntax, Behavior, and Pitfalls

c# params collection: Learn how the C# params keyword works with arrays and collections, including syntax, runtime behavior, performance tradeoffs, and common mistakes.

C# paramsvariable argumentsIEnumerablearrayscollectionsmethod overloading
Diagram showing a C# method with params keyword accepting multiple arguments into an array or collection icon.

The params keyword in C# allows a method to accept a variable number of arguments. When you declare a parameter as params int[] numbers, callers can pass either a single array or a comma-separated list of integers. The compiler converts the list into an array before the method body executes. This is the foundation of the c# params collection pattern.

The Classic params Array Behavior

public static int Sum(params int[] numbers) { return numbers.Sum(); } // Call with individual arguments int result1 = Sum(1, 2, 3); // Call with an existing array int[] values = { 4, 5, 6 }; int result2 = Sum(values);

The params keyword can only be applied to the last parameter in a method signature, and the parameter type must be a single-dimensional array. This has been the case since C# 1.0. The array-based form is the most widely used and is the default choice for most methods that need variable arguments.

How the Compiler Handles Implicit Array Creation

When you pass individual arguments, the compiler generates code that creates a new array and populates it with those arguments. This allocation happens on every call. If you pass an array directly, the compiler uses that array reference without copying. The distinction matters for performance and for mutation behavior.

int[] data = { 1, 2, 3 }; Sum(data); // No copy, data is passed by reference Sum(1, 2, 3); // Compiler creates int[3] and copies values

Because the array is passed by reference, modifying the array inside the method affects the caller's array. This is not a problem when the method only reads the values, but it can lead to subtle bugs if the method reassigns elements. If you need to guarantee that the method does not modify the input, consider using ReadOnlySpan<T> or explicitly documenting the contract.

Introducing params Collections in C# 13

Starting with C# 13, the params keyword can be used with collection types other than arrays, such as List<T>, IEnumerable<T>, and Span<T>. This feature is called params collections. It relies on the collection builder pattern that was introduced with collection expressions.

public static int Sum(params IEnumerable<int> numbers) { return numbers.Sum(); } int result = Sum(1, 2, 3, 4);

When you use a collection type, the compiler constructs the collection using the appropriate builder. For IEnumerable<T>, it typically creates a temporary list or array. The exact behavior depends on the target type and the compiler's collection expression support. This feature is still relatively new; it requires C# 13 and a compatible .NET runtime. If you are working on an older codebase, you should stick with arrays or explicitly accept a collection parameter.

Performance Implications: Array vs Collection

The classic array-based params has a predictable allocation pattern. Passing individual arguments always allocates a new array. Passing an existing array avoids allocation but gives the method a reference to the caller's array. With params collections, the compiler may allocate a collection and copy elements into it. For IEnumerable<T>, the implementation often uses a temporary array or list. The exact overhead depends on the collection type and the number of arguments. In hot paths, the extra allocation can be measurable.

If you are building a method that is called frequently with a small number of arguments, the array-based approach is usually sufficient. If you need to avoid allocation entirely, you can use params ReadOnlySpan<T> in C# 13, which allows stack-allocated spans and no heap allocation for small argument counts.

public static int Sum(params ReadOnlySpan<int> numbers) { int total = 0; foreach (int n in numbers) total += n; return total; }

This is the most allocation-friendly option, but it requires C# 13 and careful use because spans cannot be stored in fields or used across async boundaries.

Common Mistakes and Edge Cases

One common mistake is using params with a collection type that does not have a collection builder. For example, params HashSet<int> is not supported because HashSet<T> does not have a collection builder. The compiler will raise an error. Another edge case is passing null as the array argument. If you call Sum(null), the numbers parameter becomes null, not an empty array. You need to handle null explicitly if the method can receive it.

public static int Sum(params int[] numbers) { if (numbers == null) return 0; return numbers.Sum(); }

Also, params cannot be combined with optional parameters in a way that creates ambiguity. The compiler resolves overloads based on the argument list, and mixing params with other overloads can lead to unexpected resolution. For instance, having both Sum(int a, params int[] rest) and Sum(params int[] all) can cause the compiler to choose the wrong overload when you call Sum(1, 2).

Choosing Between params Array and Explicit Collection Parameters

If you need to accept a variable number of arguments, params is the idiomatic choice. But if you need to pass a collection that is already constructed, an explicit parameter may be clearer. For example, a method that accepts List<T> directly avoids the implicit array creation when the caller already has a list.

public static int SumList(List<int> numbers) { ... }

Use params when the caller is likely to pass individual values. Use an explicit collection parameter when the caller is likely to have a collection already. The decision also depends on whether you want to support the collection expression syntax in C# 12. With params IEnumerable<int>, you can call Sum(1, 2, 3) or Sum(myList), but the compiler may create a temporary collection in the former case. If you want to avoid any implicit allocation, prefer an array parameter or a ReadOnlySpan parameter. For most public APIs, the array-based params remains the safest and most compatible choice, especially when targeting older frameworks.

c# params collection: Practical Usage and Code Examples | RYUSLOG DEV