Back to Blog
C#

C# Params Array: Syntax and Usage

c# params array: Learn how the C# params array syntax works, when to use it, and how it affects method calls, overload resolution, and performance.

paramsC# methodsvariable argumentsarray parametersmethod overloading
Illustration of a C# method signature with params keyword expanding into multiple arguments, representing variable-length input.

c# params array requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The params array in C# lets a method accept a variable number of arguments of a specified type. It is declared by placing the params keyword before the last parameter in a method signature, and that parameter must be an array type. When you call such a method, you can pass either a comma-separated list of individual values or an actual array of the same element type. The compiler wraps the individual values into a new array automatically, so the method body always receives an array instance.

What the params Array Does in C#

The primary purpose of params is to remove the need for callers to manually construct an array when they want to pass a variable number of arguments. Without params, you would have to write code like Sum(new int[] { 1, 2, 3 }) or Sum(new int[0]) for an empty set. With params, the call becomes Sum(1, 2, 3) or Sum().

This behavior is purely a compile-time convenience. At runtime, the method receives a standard array. The params keyword does not change how the array is stored or accessed inside the method. It only affects the call site and overload resolution.

Declaring a Method with params

The params keyword can only be applied to the last parameter of a method, and that parameter must be a single-dimensional array type. Here is a minimal example:

public static int Sum(params int[] numbers) { int total = 0; foreach (int n in numbers) { total += n; } return total; }

The method can be called with zero or more integers:

int a = Sum(); // numbers = new int[0] int b = Sum(1, 2, 3); // numbers = new int[] { 1, 2, 3 } int c = Sum(5); // numbers = new int[] { 5 }

You can also pass an existing array directly:

int[] values = { 10, 20, 30 }; int d = Sum(values);

In that case, the same array reference is passed without copying. This distinction matters when you modify the array inside the method, because changes will be visible to the caller.

Calling Methods with Variable Arguments

When you call a params method with individual values, the compiler creates a new array at the call site. This allocation is a runtime cost, though for small argument lists it is usually negligible. If you are calling the method in a hot loop with many arguments, the repeated allocations can add up.

A common pattern is to use params for logging, string formatting, or configuration helpers where the number of inputs is small and the call frequency is moderate. For example:

public static void Log(string message, params object[] args) { // Format or store message with args }

Callers can write Log("User {0} logged in", userId) instead of building an object array manually. This keeps the call site readable and avoids a temporary array variable.

How Overload Resolution Treats params

Overload resolution in C# follows specific rules when params is involved. A method with a params parameter is considered in its expanded form when individual arguments are supplied, and in its normal form when an array is supplied. This can lead to subtle ambiguities.

Consider these two overloads:

public static void Print(string message) { } public static void Print(string message, params int[] values) { }

Calling Print("hello") picks the first overload because it matches exactly without needing the expanded form. Calling Print("hello", 1, 2) picks the second. If you call Print("hello", null), the compiler will report an ambiguity because null can be converted to both int[] and the expanded form with a single int? Actually, null cannot be converted to int (value type), so it resolves to the int[] overload. But if the second parameter were object[], null would be ambiguous.

The key point is that params methods participate in overload resolution as if the parameter were a normal array, plus an expanded form that matches individual arguments. This expanded form is only considered when the normal form does not match exactly.

Performance and Allocation Behavior

The main performance consideration for params is array allocation. When you pass individual arguments, the compiler generates a newarr instruction to create the array. For value types like int, the array holds the values directly; for reference types, it holds references.

If you call a params method with a large number of arguments frequently, the allocation pressure can become measurable. In such cases, you might provide an overload that takes a fixed set of parameters for the common cases. For example, a method that often receives two or three values could have overloads:

public static void Write(string format) { } public static void Write(string format, object arg1) { } public static void Write(string format, object arg1, object arg2) { } public static void Write(string format, params object[] args) { }

The compiler will prefer the fixed overloads when the argument count matches, avoiding the array allocation. This is a common pattern in .NET libraries, such as String.Format.

Another subtlety is that passing an existing array does not allocate a new array, but the method can modify the contents. If you want to protect the caller's array from modification, you should copy it inside the method or document that the method does not mutate the input.

Common Mistakes and Edge Cases

One common mistake is trying to use params with a multidimensional array or a jagged array. The params parameter must be a single-dimensional array. For example, params int[,] is not allowed. If you need to accept a variable number of arrays, you can use params int[][].

Another edge case is passing null to a params method. If you call Sum(null), the compiler treats null as an int[] reference, so inside the method numbers is null. This can cause a NullReferenceException if you iterate over it without a null check. The same applies to params object[]; passing null is not the same as passing no arguments.

When you use params with an interface or base class type, the compiler allows any derived type as an individual argument. For example, params IEnumerable<int>[] would accept multiple IEnumerable<int> instances, but each argument must be an IEnumerable<int>, not an int.

When to Avoid params

params is not always the right choice. If the number of arguments is fixed and known at compile time, a regular parameter is clearer and avoids the array allocation. If the arguments represent a collection of items that already exist as an array or list, accepting an IEnumerable<T> or IReadOnlyList<T> might be more flexible, especially if you want to avoid copying.

For high-performance APIs where allocations are critical, consider providing overloads for common argument counts or using Span<T> if you are targeting modern .NET. Span<T> cannot be used with params directly, but you can accept a ReadOnlySpan<T> and have callers pass a stack-allocated span. This is an advanced pattern that trades convenience for control.

Finally, be careful when combining params with optional parameters. The params parameter must be the last one, so you cannot have optional parameters after it. If you need both, you must design the method signature differently, such as using overloads.

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