C# 13 brings features that make it easier, safer, and faster to write code in the styles you know and love. You’ll find a full list of C# 13 features at What’s new in C# 13.
C# 13 fulfills a long standing feature request by allowing params
to be any of the collections supported by collection expressions, rather than just arrays. This feature builds on collection expressions that were introduced in C# 12.
Laying the groundwork with collection expressions
Collection expressions were introduced in C# 12 and offer an alternative to the myriad of ways that you previously created different kinds of collections. In the context of the method, this allows you to simplify use of collections in many scenarios, including calling methods:
// Prior to C# 12
WriteByteArray(new[] { (byte)1, (byte)2, (byte)3 });
WriteByteSpan(stackalloc[] { (byte)1, (byte)2, (byte)3 });
// After C# 12
WriteByteArray([1, 2, 3]);
WriteByteSpan([1, 2, 3]);
static void WriteByteArray(byte[] bytes) { }
static void WriteByteSpan(Span<byte> bytes) { }
This also allowed us to unify aspects of how C# works with collections. Note that collection expressions infer both the type of the collection and the type of the members.
params
arrays
params
has been in the language since C# 1.0. It allows calling code to include zero to many arguments as a comma delimited list. The method receives this list as an array, which might be empty:
WriteByteArray(1, 2, 3);
WriteByteArray();
static void WriteByteArray(params byte[] bytes) { }
Prior to C# 13, the params
must be declared as an array in the method’s parameter list.
In addition to calling with a list of values, you have always been able to call methods that take a params
with an array, and starting in C# 12, this can be a collection expression:
WriteByteArray(1, 2, 3);
WriteByteArray([1, 2, 3]);
byte[] bytes = [4, 5];
WriteByteArray([1, 2, 3, .. bytes]);
static void WriteByteArray(params byte[] bytes) { }
params
collections
Starting in C# 13, params
can be any of the collection types that support collection expressions:
WriteByteSpan(1, 2, 3);
WriteByteSpan();
static void WriteByteSpan(params Span<byte> bytes) { }
While that seems like a small change, it often enables the compiler to optimize your code. For example, if the method uses params Span<T>
, the compiler can use stack space to create the span for the method. That performs better than allocating an array.
Using a specific type can also communicate to callers how the collection will be used. For example, params IReadonlyList<T>
indicates that the collection will not be changed.
If you use params IEnumerable<T>
, your users can pass a list of literal values, an array, a List<T>
, any of the collection types that implement IEnumerable<T>
, or a LINQ expression:
WriteByteArray(1, 2, 3);
byte[] bytes = [1, 2, 3, 4, 5];
WriteByteArray(bytes.Where(x => x < 4));
static void WriteByteArray(params IEnumerable<byte> bytes) { }
When the params
receiver is an interface and the argument is a discrete list of elements or a collection expression, the compiler will use a concrete type. Many collection interfaces have a single implementation that would be a logical choice, such as ReadonlyCollection<T>
for IReadonlyList<T>
. Since IEnumerable<T>
does not have such an obvious choice and is so fundamental to .NET, it uses a special high-performance type for both param
and collection expressions.
Overloading
C# supports overloading methods – which means that multiple methods with the same name can exist, if they differ by the types of the parameters. You can overload on a params
collection, as you would other parameters.
For example, you might have an overload with params IEnumerable<T>
and one with params ReadOnlySpan<T>
in the same resolution scope. If you pass a list of values, the params ReadOnlySpan<T>
overload will be selected. If you pass an array, the params ReadOnlySpan<T>
overload will also be selected, because there is an implicit conversion from array to ReadOnlySpan<T>
. If you pass a List<T>
, the params IEnumerable<T>
overload will be used:
public class ParamCollections
{
public static void Overloads()
{
WriteNumbers(1, 2, 3);
WriteNumbers([1, 2, 3]);
WriteNumbers(new[] { 1, 2, 3 });
byte[] ints = [1, 2, 3, 4, 5];
WriteNumbers(ints.Where(x => x < 4));
}
// This code shifts from static local functions to private functions
// because overloading is not allowed for local functions.
private static void WriteNumbers<T>(params IEnumerable<T> values) => Console.WriteLine("IEnumerable");
private static void WriteNumbers<T>(params ReadOnlySpan<T> values) => Console.WriteLine("Span");
}
// The result is:
//
// Span
// Span
// Span
// IEnumerable
The compiler picks a reasonable overload for you. It will often be Span<T>
if it is available because this saves an allocation in the method call.
params
collections will make your applications faster, whether or not you add them to your own code, because the .NET runtime libraries can now use high performance types like Span<T>
in more places. You use the same concepts you’ve always used for params
, callers have more flexibility in how they call the method, and the compiler picks the overload.
Considering overloads
Overloads are a very powerful feature of C#. But as with many powerful things, proper usage is important. If there are multiple methods with the same name, they should do the same thing. They may differ in how they do it or differ by performance, but changing the types passed should not introduce breaking changes in your application. Often, this means calling a single implementation after adjusting the argument values.
As you can see in the example above, small changes in the calling code can result in different overloads being called. This is true for all C# applications, whether or not you use params
collections.
Summary
We’re excited about params
collection in C# 13 and can’t wait to hear what you think!
You can learn about all the features in C# 13 at What’s new in C# 13.
"If you pass a List, the params IEnumerable overload will be used"
Shouldn't it work with the span too ? It would make it so much easier to update existing code to use spans when possible without having to either :
1) Make 2 overload, with the "List" overload calling the Span overload
2) Go to every caller and manually convert to span, which goes against the whole point of this new feature
The entire use of...
To clarify your quote, I'll include that paragraph:
For example, you might have an overload with params IEnumerable and one with params ReadOnlySpan in the same resolution scope. If you pass a list of values, the params ReadOnlySpan overload will be selected. If you pass an array, the params ReadOnlySpan overload will also be selected, because there is an implicit conversion from array to ReadOnlySpan. If you pass a List, the params IEnumerable overload...
While what I said is true, there is actually a bigger issue between these specific types. An array is implicitly convertible to a span, but a List is not. Thus, if you removed the IEnumerable overload in the example, you would get a compiler error CS1503: Argument 1: cannot convert from ‘System.Collections.Generic.List’ to ‘System.Span’
Hi. I’m curious about how safe it is to use the field keyword in our code. Can I count on it being included in the upcoming stable versions of C#, even as an optional feature that can be enabled? Perhaps with only minor modifications? I really like this feature, but I don’t want to do unnecessary work if we’ll have to revert it all later.
I feel confident that the design of using this feature is the one we want. The preview status is to allow time for feedback on the breaking change mechanism, which will not affect you unless you were previously using . I do not think this feature will change, but, all preview features are subject to change and might not be released.
If you decide to use it, we'd really love to hear any feedback.
Agree, especially in expressions:
https://github.com/dotnet/roslyn/issues/73743
While from a technical perspective this is true, it just reads a little awkward, because a collection is formal less then a list.
Many collection interfaces have a single implementation that would be a logical choice, such as ReadonlyCollection for IReadonlyList.
Maybe another example feels a little more natural.
The ReadonlyCollection/IReadonlyList naming is an oddity in the .NET libraries API. That example clarifies that we are going for intent. The receiver said it could handle something readonly, so let’s give it something that is readonly. If I think of another example that clarifies that point well, I’ll change it due to the API naming.
Minor typo in the text: … implicit conversion from array to ReadONlySpan.
Thanks! Fixed.