先々月書いた「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
に関しては、
- 10月17日に「Any Time」で承認
- 10月21日に Pull Request 出る
- 10月24日に Pull Request が通る
- 10月31日に main ブランチに merge
という感じ。 「Any Time」とは…
Pull Request を作った方、C# チームの人ですしね。 定時後とかにさらっとやっちゃった感じかなぁと。 ホリデーの飛行機の中で embedded language を実装しちゃうような人なので。
ということで、「.NET 9 のプレビュー版もまだ出てないのにもう C# 13 候補機能の1つが実装されてリリースされてる」という面白状況に。