【C#/.NET8版】文字列の先頭だけを大文字 or 小文字に変換する

ものすごい昔に「【C#】文字列の先頭だけを大文字 or 小文字に変換する」という記事を書きましたが、あれからかなり時間が経って(前に記事を書いたのは今から5年前).NETに Span<T>string.Create という機能が追加されよりパフォーマンス良く実装できるようになったのでもう一度実装例を考えたいと思います。

確認環境

  • .NET8(C# 12)
  • Windows11 + VisualStudio 2022

動作はWindowsのコンソールアプリで確認しています

実装コードは以下の通りです。StringExtension クラスを作成して実装します。

コード例

// StringExtension.cs

/// <summary>
/// <see cref="string"/> クラスを拡張します。
/// </summary>
public static class StringExtension
{
    /// <summary>
    /// 指定した index 番目の文字を大文字に変換します。
    /// </summary>
    public static string ToUpper(this string self, int index = 0)
    {
        return ToUpper(self, CultureInfo.InvariantCulture, index);
    }
    public static string ToUpper(this string self, CultureInfo cultureInfo, int index = 0)
    {
        if (self == null || index >= self.Length)
        {
            return self;
        }
        return string.Create(self.Length, (self, index, cultureInfo), (span, state) =>
        {
            state.self.CopyTo(span);
            span[state.index] = char.ToUpper(span[state.index], state.cultureInfo);
        });
    }

    /// <summary>
    /// 指定した index 番目の文字を小文字に変換します。
    /// </summary>
    public static string ToLower(this string self, int index = 0)
    {
        return ToLower(self, CultureInfo.InvariantCulture, index);
    }
    public static string ToLower(this string self, CultureInfo cultureInfo, int index = 0)
    {
        if (self == null || index >= self.Length)
        {
            return self;
        }
        return string.Create(self.Length, (self, index, cultureInfo), (span, state) =>
        {
            state.self.CopyTo(span);
            span[state.index] = char.ToLower(span[state.index], state.cultureInfo);
        });
    }
}

使い方

使い方は以下の通り。

string str = "abc123";
string newStr = str.ToUpper(0); // 先頭の文字を大文字に変換する
// newStr = Abc123

string newStr_2 = str.ToUpper(3);
// newStr_2 = abc123
//  → 変換できない場合元の文字列が返る

パフォーマンス比較

前回書いた記事 と 今回の記事のパフォーマンスをBenchmarkDotNet で比較してみました。計測コードは省いていますが、上記実装で小さめの文字列(100~500文字前後)に対して複数回処理を実行したときの結果となります。

Case1 が以前の記事の実装で、Case2 が今回の実装です。

前回の記事では処理時間が約半分になっています。これは、いちど string → char 配列に変換してメモリアロケーションが発生してた処理を、今回は string.Create と Span<T> を組み合わせたのでヒープ使用量を削減した結果です。

| Method | Mean      | Error     | StdDev    | Rank | Gen0   | Allocated |
|------- |----------:|----------:|----------:|-----:|-------:|----------:|
| Case1  | 150.91 ns |  76.67 ns |  4.202 ns |    2 | 0.0515 |     864 B |
| Case2  |  78.59 ns | 259.51 ns | 14.224 ns |    1 | 0.0257 |     432 B |

このように、最新機能を利用すれば文字列処理が以前より簡単にパフォーマンスよく実装できる事が確認できました。

短いですが以上です。