【Preview】可変長引数に Span が利用可能になりました

# 概要

[C# preview5](https://github.com/dotnet/core/discussions/9350) で[可変長引数に `Span<T>` が利用可能](https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview5/libraries.md#params-offers-better-performance-with-span-overloads)になりました。コードを書くときにこの機能を使うことは稀ですが、既存のコードのパフォーマンスが少し良くなる可能性があります。

```cs
public static string Join(string? separator, params ReadOnlySpan<string?> value)
```

C# の[可変長引数](https://ufcpp.net/study/csharp/sp_params.html)は配列が使われていました。配列ということでヒープが発生するため、パフォーマンス上の理由で ↓ のようにオーバーロードがたくさん用意されていました。

```cs
// (string, object)
string.Format("{0}", "a");
// (string, object, object)
string.Format("{0}, {1}", "a", "b");
// (string, object, object, object)
string.Format("{0}, {1}, {2}", "a", "b", "c");
// (string, object[])
string.Format("{0}, {1}, {2} {3}", "a", "b", "c", "d");
```

これからはこういったオーバーロードを追加する必要はなくなりそうです（既存のオーバーロードは互換性のために残されます）。

## パフォーマンス


| Test                   | Score |      % |  CG0 |
| ---------------------- | ----: | -----: | ---: |
| ReadParamsSpan         |   101 | 100.0% |    0 |
| ReadParamsReadOnlySpan |    97 |  96.0% |    0 |
| ReadParamsArray        |    35 |  34.7% |    3 |
| WriteParamsSpan        |   105 | 104.0% |    0 |
| WriteParamsArray       |    25 |  24.8% |    1 |

実行環境: Windows11 x64 .NET Runtime 9.0.0 preview.6  
**Score** は高いほどパフォーマンスがよいです。  
**GC0** はガベージコレクション回数を表します（少ないほどパフォーマンスがよい）。

- `Span<T>` と `ReadOnlySpan<T>` にはあまり差がないようです。
- `Span<T>` と `ReadOnlySpan<T>` は `T[]` に比べてパフォーマンスがよいです。ヒープも発生していません。
- 参照型でもスタック上に配置し、パフォーマンスが向上しています（[InlineArray](https://ufcpp.net/study/csharp/datatype/inline-array/)）。

<details>
<summary>テストコード</summary>

```cs
using Xunit;

public class _SpanParamsTest
{
    [Fact]
    protected void ParamsSpan()
    {
        string GetStringFromSpan(params Span<string?> span) => string.Join(", ", span);

        string GetStringFromReadOnlySpan(params ReadOnlySpan<string?> readOnlySpan) => string.Join(", ", readOnlySpan);

        string GetStringFromArray(params string?[] array) => string.Join(", ", array);

        Assert.Equal("a, b, c", GetStringFromSpan("a", "b", "c"));
        Assert.Equal("a, b, c", GetStringFromReadOnlySpan("a", "b", "c"));
        Assert.Equal("a, b, c", GetStringFromArray("a", "b", "c"));
    }

    protected static Action ReadParamsSpan()
    {
        int SumLength(params Span<string?> span)
        {
            int result = 0;
            foreach (var n in span)
            {
                result += n?.Length ?? 0;
            }
            return result;
        }
        return () =>
        {
            for (int n = 0; n < 10000; ++n)
                SumLength("1", "12", "123", "1234", "12345", "123456", "1234567");
        };
    }

    protected static Action ReadParamsReadOnlySpan()
    {
        int SumLength(params ReadOnlySpan<string?> readOnlySpan)
        {
            int result = 0;
            foreach (var n in readOnlySpan)
            {
                result += n?.Length ?? 0;
            }
            return result;
        }
        return () =>
        {
            for (int n = 0; n < 10000; ++n)
                SumLength("1", "12", "123", "1234", "12345", "123456", "1234567");
        };
    }

    protected static Action ReadParamsArray()
    {
        int SumLength(params string[] array)
        {
            int result = 0;
            foreach (var n in array)
            {
                result += n?.Length ?? 0;
            }
            return result;
        }
        return () =>
        {
            for (int n = 0; n < 10000; ++n)
                SumLength("1", "12", "123", "1234", "12345", "123456", "1234567");
        };
    }

    protected static Action WriteParamsSpan()
    {
        int SumSquire(params Span<int> span)
        {
            int result = 0;
            foreach (ref var n in span)
            {
                n *= n;
                result += n;
            }
            return result;
        }
        return () =>
        {
            for (int n = 0; n < 10000; ++n)
                SumSquire(1, 2, 3, 4, 5, 6, 7);
        };
    }

    protected static Action WriteParamsArray()
    {
        int SumSquire(params int[] array)
        {
            int result = 0;
            foreach (ref var n in array.AsSpan())
            {
                n *= n;
                result += n;
            }
            return result;
        }
        return () =>
        {
            for (int n = 0; n < 10000; ++n)
                SumSquire(1, 2, 3, 4, 5, 6, 7);
        };
    }
}

```

</details>