params
Collections in C# 13 — Beyond Arrays, Toward Flexibility
The params
keyword in C# has long enabled developers to create methods that accept a variable number of arguments—but until C# 13, this flexibility was limited to arrays only.
That limitation is now gone.
C# 13 expands support for params
to include other collection types, such as:
Span<T>
ReadOnlySpan<T>
IEnumerable<T>
-
IList<T>
,ICollection<T>
,IReadOnlyList<T>
,IReadOnlyCollection<T>
- Any custom collection type with an
Add
method
In this post, you’ll explore:
- How
params
works beyond arrays - Real examples with
ReadOnlySpan<T>
andList<T>
- How the compiler handles interfaces
- Best practices for adopting this feature
What’s New in C# 13?
Prior to C# 13, you could only use params
with an array type:
public void Print(params string[] messages)
{
foreach (var msg in messages)
Console.WriteLine(msg);
}
Now, you can use other collection types like this:
public void Concat<T>(params ReadOnlySpan<T> items)
{
foreach (var item in items)
{
Console.Write(item);
Console.Write(" ");
}
Console.WriteLine();
}
✅ This eliminates unnecessary array allocations and unlocks stack-only data structures like Span<T>
.
Examples
Using ReadOnlySpan<T>
with params
public void Log(params ReadOnlySpan<char> lines)
{
foreach (var line in lines)
Console.WriteLine(line.ToString());
}
ReadOnlySpan<char> span1 = "Hello".AsSpan();
ReadOnlySpan<char> span2 = "World".AsSpan();
Log(span1, span2);
Using List<T>
with params
public void Merge(params List<string> entries)
{
foreach (var list in entries)
foreach (var item in list)
Console.WriteLine(item);
}
Using IEnumerable<T>
interface
public void Report(params IEnumerable<int> values)
{
foreach (var v in values)
Console.WriteLine(v);
}
Behind the scenes, the compiler synthesizes a temporary list to hold the arguments, as long as the target interface or class:
- Implements
IEnumerable<T>
- Has a public
Add(T item)
method (if it's a concrete collection)
Compiler Behavior
Target Type | Compiler Behavior |
---|---|
Array (T[] ) |
Packs args directly into array |
Span<T> / ReadOnlySpan<T>
|
Uses stack allocation if possible |
IEnumerable<T> |
Synthesizes a list or compatible collection |
Custom collection | Uses Add method if available |
This unlocks performance optimizations and better API ergonomics.
Constraints
- You must still use a single params parameter as the last argument.
- The type must either:
- Be an array
- Implement
IEnumerable<T>
and have a usableAdd
method
- Overuse can lead to ambiguous overload resolution when mixing with arrays
✅ Best Practices
Tip | Why it matters |
---|---|
Use Span<T> when stack-allocating params |
Avoids heap allocations |
Use IEnumerable<T> when consuming any collection |
Supports maximal flexibility |
Avoid params with large or deeply nested types |
Helps performance and readability |
Be mindful with overloads and type inference | Avoid ambiguity and type mismatches |
Learn More
Final Thoughts
C# 13’s params
collections represent a quiet but powerful evolution in API design. By allowing non-array collection types, developers can build safer, more efficient, and more ergonomic code — especially in performance-critical scenarios.
From spans to interfaces, the new flexibility means less boilerplate, fewer allocations, and better APIs.
Don’t just use params
— master it.
Written by: [Cristian Sifuentes] – .NET Core Specialist | C# API Designer | Clean Code Advocate
Share your experience with spans or params collections in the comments below.
Top comments (0)