サロゲートペアを扱うシステムでは、Rune を使おう

サロゲートペアを扱うシステムでは、Rune を使おう

はじめに

絵文字や異体字セレクタ(IVS)などを扱うシステムでは、「1文字 = 1 char という前提がすぐに崩れます。
.NET の string は内部的に UTF-16 を採用しているため、Unicode の一部文字は サロゲートペア(2つの char の組み合わせ)で表現されるからです。

その結果、string.Lengths[0] といった処理は「文字数」ではなく「UTF-16 のコードユニット数」になり、絵文字などを正しく1文字として扱えません。


サロゲートペアとは?

Unicode では U+10000 以上のコードポイントを表す際、16bit 1文字では収まらないため、
上位サロゲート下位サロゲートの2つの char を1文字として扱います。

例:

1
2
3
4
string s = "𩸽"; // U+29E3D
Console.WriteLine(s.Length); // 2
Console.WriteLine(s[0]); // 上位サロゲート
Console.WriteLine(s[1]); // 下位サロゲート

この例では、魚の絵文字「𩸽」(U+29E3D)がサロゲートペアで表現されているため、
見た目は1文字でも Length は2。
Substring などで途中を切り出すと、サロゲートペアが分断されて文字化けする恐れがあります。

.NET の解決策:System.Text.Rune

.NET Core 3.0 以降(.NET 5 以降を含む)では、System.Text.Rune が用意されています。
Rune は Unicode スカラ値 = 1文字 を表す構造体で、サロゲートペアもまとめて1単位として扱えます。

基本例

1
2
3
4
5
6
7
8
using System.Text;

string s = "𩸽ABC";

foreach (Rune rune in s.EnumerateRunes())
{
Console.WriteLine($"U+{rune.Value:X} {rune}");
}

出力:

1
2
3
4
U+29E3D 𩸽
U+0041 A
U+0042 B
U+0043 C

EnumerateRunes() はサロゲートペアを自動的に認識し、
正しい Unicode スカラ値を1つずつ返します。

Rune を使うメリット

  • 文字化け防止
    • サロゲートペアを分断せず「1文字単位」で安全に処理できる。
  • IVSや絵文字にも対応
    • 異体字セレクタや絵文字など、特殊コードポイントも正しく扱える。
  • 既存の string と共存可能
    • 既存APIはそのまま使いながら、文字操作だけを Rune ベースに置き換えられる。

実用ユーティリティの例
Rune を活用した便利メソッドをまとめておくと、レガシーコードの置き換えが容易になります。

1
2
3
4
5
6
7
8
9
10
11
public static class UnicodeHelper
{
public static int GetRuneCount(string s)
=> s.EnumerateRunes().Count();

public static string SubstringByRunes(string s, int runeStart, int runeCount)
=> string.Concat(
s.EnumerateRunes()
.Skip(runeStart)
.Take(runeCount));
}

これで 「Rune 単位の Length」 や 「Rune 単位の Substring」 を簡単に呼び出せます。

まとめ

UTF-16 では「1文字 = 1 char」は成り立たず、サロゲートペアやIVSを安全に扱う必要がある。

.NET では System.Text.Rune がその課題を解決する最適な手段。

段階的に Rune ベースへ移行することで、レガシーシステムでも国際化・絵文字対応を堅牢に実装できる。

ポイント: 「文字」を安全に扱うには Rune を使う。
サロゲートペアや絵文字対応が必要なシステムでは、今こそ Rune への移行を検討しましょう。

You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

コメント

You forgot to set the shortname for Disqus. Please set it in _config.yml.
You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.