先々月書いた「C# 13 向けトリアージ」で紹介してた C# 13 候補の1つ、「\e エスケープ シーケンス」が早々に実装されてたという話です。

.NET 8 正式リリース記念の配信ではちょこっと触れてたんですが、そういえばブログには書いてなかったので紹介。

エスケープ文字

キーボードで打てないような文字や、画面に表示されない文字を入力したりするために、 「\n と書いたら改行(U+000A, new line, line feed)に置き換える」みたいな仕様があり、これをエスケープ シーケンス(escape sequence, 回避用の一連の文字列)と言います。

C# をはじめ、C 言語の影響を受けて作られた言語の多くは \ (reverse solidus, 逆スラッシュ)で始まる文字列によってエスケープします。 プログラミング言語だと他には ` (逆引用符、グレイブ アクセント, grave accent)とかを使うものがあったりしますが、 要は、利用頻度があまりない文字をエスケープ シーケンスの開始文字にすることが多いです。

一方で、ASCII コードには古よりずっと、エスケープ文字(U+001B, escape character)というものがあります。 名前通りエスケープ シーケンスの開始文字として使われるもので、 多くのターミナル アプリがこのエスケープ文字を使ったシーケンスに対応しています。 ANSI (American National Standards Institute) によって策定された標準仕様があって、大体のターミナルはこの仕様に基づいた実装を持っています。 (この仕様は ANSI X3.64 というそうです。)

例えば C# で以下のようなコードを書いて実行すると、たいていの環境で赤い文字が表示されるはずです。

Console.WriteLine("\u001b[31mred text");

\u001b がエスケープ文字(以下、ESC と表記)で、ESC + [31m という文字列を Console に書き込むとそれ以降の文字色が変わります。

エスケープのエスケープ

そして C# 13 候補として、このエスケープ文字(U+001B)に対する C# のエスケープ シーケンスとして、\e が提案・承認されました。

C# 12 以前でも \x + 16進数2桁とか、\u + 16進数4桁とか、 \U + 16進数8桁とか、 任意の文字コードを直接打ち込むエスケープ手段があったので、別にそれほどなくて困るものでもなかったりはします。 以下のコードの \x1b, \u001b, \U0000001b はいずれもエスケープ文字です。

Console.WriteLine("\x1b[31mred text");
Console.WriteLine("\u001b[4munderlined text");
Console.WriteLine("\U0000001b[0mreset style");

古からある仕様ですが、 長らく C# の主戦場だった Windows では 「文字のスタイル変更は Console.ForegroundColor などの API 経由で行ってほしい」 みたいな感じで、あまり ANSI X3.64 を利用する文化ではありませんでした。

しかし最近は Linux 上での C# 利用も増え、 Windows も今時っぽい新しいターミナルを搭載するようになり、 ANSI X3.64 を積極的に使いたいという要望がちらほら増えてきました。

また、 Windows Terminal が新しくなった今となっては、 Console.ForegroundColor などの .NET の API を使って操作できるものよりも、 ANSI X3.64 でやれることの方が多くなっていたりします。

そこで出てきたのが \e でエスケープ文字を表せるようにしてほしいという要望。

\e 提案の検討

この提案で得られるメリットや、かかるコストを考えてみましょう。

まずメリットの方。 前節で書いた通り、エスケープ文字を使いたいことはちらほらないこともなく、「あれば便利かも」とは思います。 とはいえ、毎回自分で ANSI X3.64 を書くかと言われると微妙。 「31番が赤」とかいちいち覚えないですからね。 C# でも、ANSI X3.64 出力用のライブラリを提供してくれている方がいらっしゃいます: Kokuban (安定の Cysharp)。

また、元から \x1b と書けたわけで、「\e と書けるようになって楽かどうか」と言われるとたった2文字の短縮です。 もちろん、「エスケープ文字の文字コードは何だったっけ?」というのを覚えるよりは「エスケープの頭文字をとって e」というのの方が覚えやすそうではあります。

コストに関しては、エスケープ シーケンスの解析用の switch ステートメントに1個 case を追加するだけです。 以下のたった3行の追加。

    case 'e':
        ch = '\u001b';
        break;

「C# 12 以下では使えない」みたいな判定を足すとしてもさらに追加で +3 行。 テストとかを足しても数百行程度の修正になります。 ここの case 1個くらいならコンパイル実行時のコストもほとんどなし。

要するに、割かし毒にも薬にもならない、低コスト低リターンな提案ということになります。

なので、C# チームによる判定は「Any Time」。 この「Any Time」は、

  • 自分たちで実装の労力は割かない
  • コミュニティによる Pull Request が来た場合は受け付ける

みたいな温度感です。

そして実装

「Any Time」のわりにもうすでに実装されたものがあるわけですが。 以下のコード、Visual Studio 17.9 Preview 1 (11月15日にリリース) で動きます。

Console.WriteLine("\e[31mred text");
Console.WriteLine("\e[4munderlined text");
Console.WriteLine("\e[0mreset style");

\e もう動いてる

普通、コミュニティ実装だとそこそこ時間がかかるんですけどね。 何せ、「専業でやっているわけじゃない外部の人のコードのレビュー」みたいなプロセスを経るので。

\e に関しては、

という感じ。 「Any Time」とは…

Pull Request を作った方、C# チームの人ですしね。 定時後とかにさらっとやっちゃった感じかなぁと。 ホリデーの飛行機の中で embedded language を実装しちゃうような人なので。

ということで、「.NET 9 のプレビュー版もまだ出てないのにもう C# 13 候補機能の1つが実装されてリリースされてる」という面白状況に。