はじめに 「文字列反転」は“ただのお遊び”で終わらせるには惜しいテクニック
「文字列を逆順にする」——いかにも練習問題っぽいテーマですが、実務でも意外と出番があります。
ログや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#やっていることはとてもシンプルです。
- null や空文字なら、そのまま空文字を返す(呼び出し側を楽にするための“お作法”)。
ToCharArrayでstringをchar[]に変換する。Array.Reverseで配列を逆順に並べ替える。- 逆順になった
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(...) で文字列にしています。
見た目はスッキリしていますが、内部的にはイテレータを挟む分だけ、ToCharArray+Array.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#ここでやっていることは、
StringInfo.GetTextElementEnumeratorで「テキスト要素」を順に取り出す。- それぞれを
List<string>に詰める(1要素=見た目の1文字)。 List<T>.Reverse()で順番を逆にする。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 なので、新しい文字列を作る必要があるToCharArray+Array.Reverse が基本形
LINQ 版や手動ループ版も書けるが、実務ではシンプルさとパフォーマンスのバランスを考える
Unicodeの世界では「1文字=1char」とは限らず、厳密にやるなら StringInfo でテキスト要素単位で扱う
ユーティリティとしては、「用途ごとにどこまで厳密にするか」を決めておくことが大事
という、“文字列処理の考え方”そのものです。
ここまで理解できれば、「なんとなく Reverse している」段階から一歩進んで、
“用途と文字種を意識した、業務で使える文字列反転ユーティリティ”を、
自分のC#コードの中に自然に組み込めるようになっていきます。
