C# Tips | 文字列処理:文字列反転

C# C#
スポンサーリンク

はじめに 「文字列反転」は“ただのお遊び”で終わらせるには惜しいテクニック

「文字列を逆順にする」——いかにも練習問題っぽいテーマですが、実務でも意外と出番があります。
ログやIDの一部をマスクして見せたいとき、右から何文字かを強調したいとき、簡易的な“見た目用の変形”をしたいときなど、ちょっとしたユーティリティとして持っておくと便利です。

ただし、C#の文字列は「見た目の1文字=常に char 1つ」ではありません。
単純に char を逆順に並べるだけだと、絵文字や一部の漢字・記号で崩れる可能性があります。
まずは「素朴な反転」を押さえつつ、「どこまで気にするか」を理解しておくのが大事です。

ここでは、初心者向けに、
基本的な文字列反転の書き方、Array.Reverse を使う方法、LINQ版、パフォーマンスを意識した書き方、
そして「Unicode的にちゃんとやるとどうなるか」の入口まで、かみ砕いて説明していきます。


基本:char 配列にしてから逆順に並べる

一番素直な実装パターン

C#の string は「変更不可(immutable)」なので、直接中身を書き換えることはできません。
その代わり、一度 char[] に展開してから並べ替え、新しい文字列を作るのが基本パターンです。

public static class StringReverseUtil
{
    public static string ReverseSimple(string? value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return string.Empty;
        }

        char[] chars = value.ToCharArray();

        Array.Reverse(chars);

        return new string(chars);
    }
}
C#

やっていることはとてもシンプルです。

  1. null や空文字なら、そのまま空文字を返す(呼び出し側を楽にするための“お作法”)。
  2. ToCharArraystringchar[] に変換する。
  3. Array.Reverse で配列を逆順に並べ替える。
  4. 逆順になった char[] から新しい string を作る。

動作例でイメージをつかむ

Console.WriteLine(StringReverseUtil.ReverseSimple("ABC"));      // "CBA"
Console.WriteLine(StringReverseUtil.ReverseSimple("こんにちは")); // "はちにんこ"
Console.WriteLine(StringReverseUtil.ReverseSimple("12345"));    // "54321"
Console.WriteLine(StringReverseUtil.ReverseSimple(""));         // ""
Console.WriteLine(StringReverseUtil.ReverseSimple(null));       // ""
C#

「とりあえず逆順にしたい」という用途なら、これで十分です。
まずはこの形を“基本形”として手に馴染ませておくとよいです。


LINQ を使った書き方と、その違い

new string(value.Reverse().ToArray()) というワンライナー

C#に慣れてくると、LINQを使ってこんな書き方もできます。

using System.Linq;

public static class StringReverseUtil
{
    public static string ReverseLinq(string? value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return string.Empty;
        }

        return new string(value.Reverse().ToArray());
    }
}
C#

value.Reverse() は「列を逆順に並べた IEnumerable<char>」を返し、
ToArray()char[] にし、それを new string(...) で文字列にしています。

見た目はスッキリしていますが、内部的にはイテレータを挟む分だけ、
ToCharArrayArray.Reverse より少しだけオーバーヘッドがあります。
業務で普通の長さの文字列を扱う分には誤差レベルですが、「パフォーマンスを意識したい場面では基本形のほうが素直」と覚えておくとよいです。


パフォーマンスを意識した“手動ループ版”

インデックスを使って自分で詰め替える

Array.Reverse に任せず、自分で前後を入れ替えていく書き方もあります。
アルゴリズムのイメージをつかむには、こちらも一度書いてみると理解が深まります。

public static class StringReverseUtil
{
    public static string ReverseManual(string? value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return string.Empty;
        }

        int length = value.Length;
        char[] result = new char[length];

        for (int i = 0; i < length; i++)
        {
            result[length - 1 - i] = value[i];
        }

        return new string(result);
    }
}
C#

このループは、「元の i 番目の文字を、結果の(末尾から i 番目)に入れる」という処理をしています。
Array.Reverse とやっていることは本質的に同じですが、「自分で制御している感」があるので、アルゴリズムの理解には向いています。

実務では、よほど特殊な理由がない限り Array.Reverse を素直に使えば十分です。


ここが重要:Unicode(サロゲートペア・結合文字)をどう扱うか

「1文字=1char」ではないケースがある

ここから少しだけ“プロっぽい話”をします。

C#の char は「UTF-16 の1単位」です。
しかし、Unicodeの世界では「1つの見た目の文字」が char 1つとは限りません。

代表的な例は次の2つです。

サロゲートペア(絵文字や一部の漢字など):char 2つで1文字
結合文字(「a」+「◌́」で「á」に見えるなど):複数のコードポイントで1文字

単純に char を逆順にすると、
これらが「バラバラに分解された状態」で並び替えられてしまうことがあります。

例えば、絵文字を含む文字列を反転したときに、
一部の環境では「変な記号」に見えたり、文字化けしたように見える可能性があります。

どこまで気にするかを決める

正直に言うと、「業務システムで日本語+英数字中心のテキストを扱う」程度なら、
char ベースの反転で困ることはほとんどありません。

一方で、

ユーザー名に絵文字を許可している
多言語対応で、結合文字を多用する言語を扱う
「見た目の1文字単位」で厳密に扱いたい

といった要件があるなら、
「グラフェムクラスター(見た目の1文字単位)」で扱う必要が出てきます。

その場合は、System.Globalization.StringInfo を使って「テキスト要素」単位で分解し、それを逆順に並べる、というアプローチになります。


もう一歩踏み込む:StringInfo を使った“見た目の1文字”単位の反転

StringInfo でテキスト要素を列挙する

StringInfo を使うと、「テキスト要素(見た目の1文字)」単位で列挙できます。

using System.Collections.Generic;
using System.Globalization;

public static class StringReverseUtil
{
    public static string ReverseTextElements(string? value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return string.Empty;
        }

        var elements = new List<string>();

        var enumerator = StringInfo.GetTextElementEnumerator(value);
        while (enumerator.MoveNext())
        {
            elements.Add((string)enumerator.Current!);
        }

        elements.Reverse();

        return string.Concat(elements);
    }
}
C#

ここでやっていることは、

  1. StringInfo.GetTextElementEnumerator で「テキスト要素」を順に取り出す。
  2. それぞれを List<string> に詰める(1要素=見た目の1文字)。
  3. List<T>.Reverse() で順番を逆にする。
  4. string.Concat で全部つなげて新しい文字列にする。

という流れです。

絵文字や結合文字を含む例

例えば、こんな文字列を考えます。

string s = "A😊B";
C#

ReverseSimple(charベース)だと、環境によっては絵文字が崩れる可能性がありますが、
ReverseTextElements なら「A」「😊」「B」という“見た目の1文字”単位で反転されます。

Console.WriteLine(StringReverseUtil.ReverseSimple(s));        // 単純なchar反転
Console.WriteLine(StringReverseUtil.ReverseTextElements(s));  // テキスト要素単位の反転
C#

厳密さが必要な場面では、こちらのアプローチを採用する価値があります。


業務ユーティリティとしてどうまとめるか

用途ごとに「どこまで厳密にするか」を分ける

実務でユーティリティ化するなら、
「用途ごとにどのレベルの反転を使うか」を分けておくとスッキリします。

例えば、こんなイメージです。

public static class StringReverse
{
    // 一般的な業務用途向け(日本語+英数字中心)
    public static string Reverse(string? value)
        => StringReverseUtil.ReverseSimple(value);

    // 絵文字や結合文字を含む“見た目重視”用途向け
    public static string ReverseVisual(string? value)
        => StringReverseUtil.ReverseTextElements(value);
}
C#

呼び出し側は、

「普通に逆順にしたいだけ」→ Reverse
「ユーザー名や表示テキストで、見た目の1文字単位を崩したくない」→ ReverseVisual

というように、意図に応じて使い分けられます。

null や空文字の扱いをユーティリティ側で決めておく

どの実装でも共通しているのが、

null や空文字なら、空文字を返す

という方針です。

これをユーティリティ側で決めておくことで、
呼び出し側で毎回 value ?? "" といったガードを書く必要がなくなります。
業務コードをきれいに保つための、地味だけど効く工夫です。


まとめ 「文字列反転ユーティリティ」は“単なるお遊び”から“一段深い文字列理解”への入口

文字列反転は、一見すると「ABC → CBA にするだけ」の簡単な処理ですが、
そこから見えてくるのは、

string は immutable なので、新しい文字列を作る必要がある
ToCharArrayArray.Reverse が基本形
LINQ 版や手動ループ版も書けるが、実務ではシンプルさとパフォーマンスのバランスを考える
Unicodeの世界では「1文字=1char」とは限らず、厳密にやるなら StringInfo でテキスト要素単位で扱う
ユーティリティとしては、「用途ごとにどこまで厳密にするか」を決めておくことが大事

という、“文字列処理の考え方”そのものです。

ここまで理解できれば、「なんとなく Reverse している」段階から一歩進んで、
“用途と文字種を意識した、業務で使える文字列反転ユーティリティ”を、
自分のC#コードの中に自然に組み込めるようになっていきます。

タイトルとURLをコピーしました