C# params Keyword

The C# params keyword lets a method accept a variable number of arguments for one parameter. It matters because callers can pass zero, one, or many values naturally, while the method body still works with one ordinary array.

You have already used this idea when calling methods such as Console.WriteLine formatting overloads or APIs that accept many values. params is most useful when the number of inputs is small, flexible, and part of the same idea.

Overview: How params Works

A params parameter is a special parameter declared with the params modifier before an array type, such as params int[] numbers. From inside the method, there is nothing magical about it: numbers is an int[]. You can read its Length, loop over it, index it, pass it to another method, and use array features exactly as you would with any other array.

The special behavior happens at the call site. If the caller writes Sum(2, 4, 6), the compiler creates an int[] containing those three values and passes that array to the method. If the caller already has an int[], they can pass the array directly with Sum(scores). The same method accepts both styles.

A params parameter may also receive no values. A call such as Sum() passes an empty array, so the method should be written to handle length zero. This is a major difference from a normal required array parameter, where the caller must at least pass an array expression.

C# requires params to appear on the final parameter in a method declaration. That rule keeps calls understandable. If a method had normal parameters after the variable-length part, the compiler would not know where the repeated values end and where the later arguments begin. A method can also have only one params parameter.

Under the hood, the CLR does not run a special loop to collect arguments. The C# compiler binds the call, builds or reuses an array as needed, and emits a normal method call with a normal array argument. The compiled method signature contains one array parameter decorated with metadata that tells C# and other .NET languages it can be called in expanded form.

params improves call-site readability, but it is not a replacement for every collection parameter. If callers usually have a list, array, query, or large number of items already, accepting IEnumerable<T> or an array directly may be clearer and can avoid unnecessary array creation.

Syntax

static returnType MethodName(params elementType[] parameterName)
{
    statements;
}

static returnType MethodName(requiredType requiredName, params elementType[] parameterName)
{
    statements;
}
Part Meaning
params The modifier that allows expanded arguments at the call site.
elementType[] The array type the method receives, such as int[], string[], or decimal[].
parameterName The array variable used inside the method body.
Required parameters before params Normal required values that must be supplied before the flexible list.

The params parameter must be last. These calls are all valid when a method is declared as static int Sum(params int[] numbers): Sum(), Sum(5), Sum(5, 10, 15), and Sum(existingArray).

Examples

Summing Any Number of Integers

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine(Sum());
        Console.WriteLine(Sum(2, 4, 6));

        int[] scores = { 10, 20, 30 };
        Console.WriteLine(Sum(scores));
    }

    static int Sum(params int[] numbers)
    {
        int total = 0;

        foreach (int number in numbers)
        {
            total += number;
        }

        return total;
    }
}

Output:

0
12
60

The same Sum method handles zero values, several individual values, and an existing array. Inside the method, numbers is just an int[], so a foreach loop is enough to add the values.

Required Parameters Plus params

using System;

class Program
{
    static void Main()
    {
        Log("Build");
        Log("Deploy", "release", "manual");
        Log("Test", "unit", "fast", "passed");
    }

    static void Log(string eventName, params string[] tags)
    {
        string tagText = tags.Length == 0 ? "none" : string.Join(", ", tags);
        Console.WriteLine($"{eventName}: {tagText}");
    }
}

Output:

Build: none
Deploy: release, manual
Test: unit, fast, passed

A params parameter often follows one or more required parameters. Here every log entry needs an event name, but tags are optional and flexible. The method checks tags.Length so the no-tags case has useful output.

A Realistic Total with Optional Line Items

using System;
using System.Globalization;

class Program
{
    static void Main()
    {
        Console.WriteLine(FormatOrderTotal("A100", 19.99m, 5.50m));
        Console.WriteLine(FormatOrderTotal("B200"));

        decimal[] bulkItems = { 12.00m, 8.25m, 4.75m };
        Console.WriteLine(FormatOrderTotal("C300", bulkItems));
    }

    static string FormatOrderTotal(string orderId, params decimal[] lineItems)
    {
        decimal total = 0m;

        foreach (decimal item in lineItems)
        {
            total += item;
        }

        return orderId + " total: $" + total.ToString("0.00", CultureInfo.InvariantCulture);
    }
}

Output:

A100 total: $25.49
B200 total: $0.00
C300 total: $25.00

This example shows a common practical shape: a required identifier followed by a flexible number of same-type values. The caller can pass individual prices for a small order or pass an existing decimal[] for values that were already collected elsewhere.

params and Overload Resolution

using System;

class Program
{
    static void Main()
    {
        Print(5);
        Print(5, 10);
        Print(new int[] { 1, 2, 3 });
    }

    static void Print(int value)
    {
        Console.WriteLine("single: " + value);
    }

    static void Print(params int[] values)
    {
        Console.WriteLine("many: " + values.Length);
    }
}

Output:

single: 5
many: 2
many: 3

When a normal overload and a params overload are both candidates, the compiler chooses the best match. Print(5) uses the exact one-parameter overload. Print(5, 10) can only use the expanded params form, and passing an int[] uses the array form of the params overload.

How params Works Step by Step

  1. The compiler reads the method declaration and records that the final array parameter is a params parameter.
  2. At each call site, it checks whether the caller passed one array argument or several expanded element arguments.
  3. If expanded arguments are used, the compiler creates a new array of the parameter element type and stores each argument in order.
  4. If an existing array is passed, the method receives that array reference directly.
  5. The method runs with one ordinary array parameter. The CLR sees a normal method call and a normal array object.

This means params has the same memory behavior as arrays. Expanded calls allocate an array. For tiny helper calls this is usually fine. In very hot code paths, or when many values are already stored in a collection, repeated hidden array allocations can matter.

Common Mistakes

Putting Another Parameter After params

static void Report(params string[] labels, bool detailed)
{
    Console.WriteLine(labels.Length);
}

This does not compile because the params parameter must be the final parameter. Put required parameters before it:

using System;

class Program
{
    static void Main()
    {
        Report(detailed: true, "errors", "warnings");
    }

    static void Report(bool detailed, params string[] labels)
    {
        Console.WriteLine($"Detailed: {detailed}");
        Console.WriteLine($"Labels: {string.Join(", ", labels)}");
    }
}

Output:

Detailed: True
Labels: errors, warnings

Trying to Declare Two params Parameters

static void Compare(params int[] left, params int[] right)
{
    Console.WriteLine(left.Length + right.Length);
}

This does not compile because a method can have only one variable-length parameter list. Use two normal array parameters when there are two separate groups:

using System;

class Program
{
    static void Main()
    {
        int[] first = { 1, 2 };
        int[] second = { 10, 20, 30 };
        Compare(first, second);
    }

    static void Compare(int[] left, int[] right)
    {
        Console.WriteLine($"Left: {left.Length}, right: {right.Length}");
    }
}

Output:

Left: 2, right: 3

Using params for Large or Already-Collected Data

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine(CountNames("Ada", "Grace", "Linus"));
    }

    static int CountNames(params string[] names)
    {
        return names.Length;
    }
}

Output:

3

This is fine for a few direct values. But if callers normally have a List<string> or query result, forcing them into a params array may be awkward. Accepting IEnumerable<string> is often better for already-collected data:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> names = new List<string> { "Ada", "Grace", "Linus" };
        Console.WriteLine(CountNames(names));
    }

    static int CountNames(IEnumerable<string> names)
    {
        int count = 0;

        foreach (string name in names)
        {
            count++;
        }

        return count;
    }
}

Output:

3

Best Practices

  • Use params when callers naturally pass a small, flexible number of same-type values.
  • Keep the params parameter last, and use only one per method.
  • Handle the zero-argument case deliberately by checking Length or choosing a sensible empty result.
  • Prefer clear required parameters before params when the method needs context, such as a label, format, category, or identifier.
  • Avoid params object[] unless you are intentionally building a formatting or diagnostic API; it weakens type checking.
  • Be careful in performance-sensitive loops because expanded params calls allocate arrays.
  • Prefer IEnumerable<T>, IReadOnlyList<T>, or an array parameter when callers usually already have a collection.
  • Avoid overload sets where a params method competes confusingly with similar fixed-parameter overloads.

Practice Exercises

  1. Write a Max method that accepts params int[] numbers. Decide what it should do when no numbers are supplied.
  2. Create a BuildPath method with a required drive or root string and params string[] parts. Join the pieces with a slash.
  3. Write a PrintChecklist method that accepts a title and any number of checklist item strings. Print the title first, then each item on its own numbered line.

Summary

  • params lets callers pass zero, one, or many values for one final array parameter.
  • Inside the method, the params parameter is an ordinary array.
  • Expanded calls create an array at the call site; existing arrays can be passed directly.
  • A method can have only one params parameter, and it must be last.
  • params is best for small, flexible groups of same-type arguments.
  • For large or already-collected data, a normal collection parameter is often clearer and more efficient.