C# 13 新特性 params collection

C# 13 新特性 params collection

Intro

C# 12 中支持了 collection expression, 统一和简化了常见集合的赋值语法,我们可以使用 [1, 2, 3] 这样的语法来初始化集合,C# 13 扩展了 params 的用法,在之前的版本中我们只能使用 params int[] ,但是从 C# 13 开始,我们也可以使用 params List<int>/params IEnumerable<int>/params ReadOnlySpan<int> 等集合了

Sample

来看一下使用示例:

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

5f75e49a78b6e68853267592e4334439.png

可以看到 params 对于我们自定义的集合也是可以支持的

简单使用之后,想一下如果我们相同的重载怎么办呢,我们也来测试一下

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 });

可以先猜测一下输出结果会是什么呢?

6b17ecc183b3c7c84070c1e01d34d5b6.png

overload-test-1

从输出结果可以看到,前面两个匹配到的是 Span 方法,后面一个匹配到了数组方法,这里就是优先匹配了 Span 方法

将前面的 ReadOnlySpan 或者 Span 输出结果也还是一样的

那我们指定一下优先级试一下呢? .NET 9 里会引入一个 OverloadResolutionPriorityAttribute 可以用来指定方法解析的优先级,默认优先级是 0,优先级数字越大优先级越高,我们来给 Array 设置一个较高的优先级试一下

[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

我们再看下 params 在有 array, IEnumerable 以及 List 的时候重载会怎么样

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));

65edbbfc517eda0bb34f5ec5b733f8a8.png

这里的 case 有点像 collection expression 的使用,没有具体的 type 的时候,编辑器会确定地无法推断出你想要的是哪一个方法,这里的错误就提示不能够确定使用 array 方法还是使用 list 方法,越具体的类型匹配时优先级越高,IEnumerable 的优先级相对更低一些

当我们移除 list 或者 array 方法时,编译错误就会消失了,会默认使用具体类型的方法,有具体实现类型的会优先匹配

我们新增一个 ICollection 的重载时也不会报错

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:

1b4eabc0b8f51a7292fc0a57f1f90273.png

因为测试方法的逻辑非常简单,太快了,接近于 0 所以结果里有一些 ? 出现, 但是从其他的指标结果我们可以看到我们 Span 的性能更优, 且没有内存分配

.NET 9 里很多方法也是基于这一特性新增了很多 params span 的 方法重载, 可以参考 pr: https://github.com/dotnet/runtime/pull/100898

References

  • https://github.com/dotnet/csharplang/issues/7700

  • https://github.com/dotnet/csharplang/blob/main/proposals/params-collections.md

  • https://github.com/dotnet/csharplang/blob/main/proposals/params-span.md

  • https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/params-collections

  • https://github.com/dotnet/roslyn/issues/74067

  • https://github.com/WeihanLi/SamplesInPractice/blob/main/net9sample/Net9Samples/CSharp13Samples.cs#L12

  • https://github.com/dotnet/runtime/pull/100898

  • https://github.com/dotnet/runtime/pull/102831

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值