ParamsArrayMethod(1, 2, 3); ParamsListMethod(1, 2, 3); ParamsEnumerableMethod(1, 2, 3); ParamsSpanMethod(1, 2, 3); ParamsReadOnlySpanMethod(1, 2, 3); void ParamsReadOnlySpanMethod(params ReadOnlySpan<int> collection) { foreach (var item in collection) { Console.WriteLine(item); } } void ParamsSpanMethod(params Span<int> collection) { foreach (var item in collection) { Console.WriteLine(item); } } void ParamsListMethod(params List<int> list) { foreach (var item in list) { Console.WriteLine(item); } } void ParamsEnumerableMethod(params IEnumerable<int> array) { foreach (var item in array) { Console.WriteLine(item); } } void ParamsArrayMethod(params int[] array) { foreach (var item in array) { Console.WriteLine(item); } }这些在 C# 13 中都是完全合法的使用。在 C# 12 collection expression 的介绍中我们提到过,我们可以实现自定义的集合也支持 collection expression。我们来试试,params 是不是支持我们自定义的集合呢,自定义的集合定义如下:
[CollectionBuilder(typeof(CustomCollectionBuilder), nameof(CustomCollectionBuilder.CreateNumber))] file sealed class CustomNumberCollection : IEnumerable<int> { public required int[] Numbers { get; init; } public IEnumerator<int> GetEnumerator() { return (IEnumerator<int>)Numbers.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return Numbers.GetEnumerator(); } } file static class CustomCollectionBuilder { public static CustomNumberCollection CreateNumber(ReadOnlySpan<int> elements) { return new CustomNumberCollection() { Numbers = elements.ToArray() }; } }params 使用示例如下:
void ParamsCustomCollectionMethod(params CustomCollection<int> collection) { foreach (var item in collection) { Console.WriteLine(item); } } ParamsCustomCollectionMethod(1, 2, 3);dotnet run
public static void OverloadTest(params int[] array) { Console.WriteLine("Executing in Array method"); } public static void OverloadTest(params ReadOnlySpan<int> span) { Console.WriteLine("Executing in Span method"); }首先我们来对比一下 ReadOnlySpan 和数组
ParamsCollectionTest.OverloadTest(1, 2, 3); ParamsCollectionTest.OverloadTest([1, 2, 3]); ParamsCollectionTest.OverloadTest(new[] { 1, 2, 3 });可以先猜测一下输出结果会是什么呢?
[OverloadResolutionPriority(1)] public static void OverloadTest2(params int[] array) { Console.WriteLine("Executing in Array method"); } public static void OverloadTest2(params ReadOnlySpan<int> span) { Console.WriteLine("Executing in Span method"); }实际输出结果和之前还是一样的,这个 attribute 目前还不工作(现在使用的是 .NET 9 Preview 5),编译器目前还未支持:https://github.com/dotnet/roslyn/issues/74067
public static void OverloadTest3(params IEnumerable<int> values) { Console.WriteLine("Executing in IEnumerable method"); } public static void OverloadTest3(params int[] array) { Console.WriteLine("Executing in Array method"); } public static void OverloadTest3(params List<int> values) { Console.WriteLine("Executing in List method"); }测试代码:
ParamsCollectionTest.OverloadTest3(1, 2, 3); ParamsCollectionTest.OverloadTest3([1, 2, 3]); ParamsCollectionTest.OverloadTest3(Enumerable.Range(1, 3));
public static void OverloadTest3(params ICollection<int> values) { Console.WriteLine("Executing in ICollection method"); }最后来猜一下下面这样的重载输出结果会是什么呢?
public static void OverloadTest4(params IEnumerable<int> values) { Console.WriteLine("Executing in IEnumerable method"); } public static void OverloadTest4(params ICollection<int> values) { Console.WriteLine("Executing in ICollection method"); } public static void OverloadTest4(params IList<int> values) { Console.WriteLine("Executing in IList method"); } ParamsCollectionTest.OverloadTest4(1, 2, 3); ParamsCollectionTest.OverloadTest4([1, 2, 3]); ParamsCollectionTest.OverloadTest4(Enumerable.Range(1, 3));Span 的优先级比较高总体上来说会有一些优化,比如不需要创建数组从而避免内存分配,减少 GC 的压力,那具体会有多少差异呢,我们可以跑个简单的 benchmark 试一下
[SimpleJob] [MemoryDiagnoser] public class ParamsCollectionTest { [Benchmark(Baseline = true)] public int ParamsSpanMethod() { return ParamsOverloadMethod(1, 2, 3); } [Benchmark] public int ParamsArrayMethod() { return ParamsOverloadMethod(new[] { 1, 2, 3 }); } private int ParamsOverloadMethod(params ReadOnlySpan<int> span) { return span.Length; } private int ParamsOverloadMethod(params int[] array) { return array.Length; } }
benchmark result:
因为测试方法的逻辑非常简单,太快了,接近于 0 所以结果里有一些 ? 出现, 但是从其他的指标结果我们可以看到我们 Span 的性能更优, 且没有内存分配。.NET 9 里很多方法也是基于这一特性新增了很多 params span 的 方法重载, 可以参考 pr: https://github.com/dotnet/runtime/pull/100898