<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>neue cc</title>
    <link>http://neue.cc/</link>
    <description>C# Technical Blog</description>
    <language>ja</language>
    <lastBuildDate>Wed, 18 Mar 2026 22:40:13 +0900</lastBuildDate>
    <item>
      <guid isPermaLink="true">https://neue.cc/2026/03/15_iphone_to_android_leitzphone.html</guid>
      <link>https://neue.cc/2026/03/15_iphone_to_android_leitzphone.html</link>
      <title>iPhoneからAndroid(Leica Leitzphone powered by Xiaomi)に乗り換えました</title>
      <description>&lt;h1 data-pagefind-sort="date:2026-03-15" data-pagefind-meta="published:2026-03-15"&gt;&lt;a href="https://neue.cc/2026/03/15_iphone_to_android_leitzphone.html"&gt;iPhoneからAndroid(Leica Leitzphone powered by Xiaomi)に乗り換えました&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2026-03-15&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;15年ぐらいずっとiPhone使ってたんですが、&lt;a href="https://www.mi.com/jp/product/leica-leitzphone-powered-by-xiaomi/"&gt;Leica Leitzphone powered by Xiaomi&lt;/a&gt;に移行しました。レンズリングが回転するというギミックを知って（最高にクールじゃないですか！？）から即buyというほとんど衝動買いです。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/26260316_6.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;先に結論からいくと&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Leitzphoneはめっちゃ良い。レンズリング回転機構は最高に良いので一代限りにならず継続して欲しい&lt;/li&gt;
&lt;li&gt;Windows母艦の環境においてAndroidはiPhoneよりずっと良い、めっちゃ快適になった&lt;/li&gt;
&lt;li&gt;Android自体も悪くない、Xiaomi HyperOS + Snapdragon 8 Elite Gen 5は十分強力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;というわけで、全体的には大満足で、なんだったらもっと早く脱Appleすれば良かった……（？）&lt;/p&gt;
&lt;p&gt;私は基本的にPCはWindowsから離れられない(主にVisual Studioのため)ので、元々iOSでのエコシステム連携は全然強くはなかったのですが、それでもiPad, Apple TV, Apple Watch, AirPods, HomePod, AirTagと、せっかくなのでとApple製品で固めてしまっていて、それはそれなりにそこそこ便利ではあった。&lt;/p&gt;
&lt;p&gt;が、メインのイヤホンを&lt;a href="https://consumer.huawei.com/jp/audio/freeclip2/"&gt;HUAWEI FreeClip(FreeClip 2)&lt;/a&gt;に変え(ちなみにFreeClipはめっっっっっちゃくちゃ良い）、HomePodを&lt;a href="https://www.sonos.com/ja-jp/home"&gt;Sonos&lt;/a&gt;に変え、Apple TVは別に大して使ってないし、となると、よく考えると現状Apple製品エコシステムには別にもうあんま縛られてるわけではないな、と。AirTagは使えなくなっても、&lt;a href="https://www.mi.com/jp/product/xiaomi-tag/"&gt;Xiaomi Tag&lt;/a&gt;出たし。これはケース不要のデザインなのが使い勝手良き。&lt;/p&gt;
&lt;p&gt;機種変の際の手間がないのは大いに利点ではあったけれど、毎年新しいiPhoneに買い替えててきた(Pro Max -&amp;gt; Pro Max -&amp;gt; Air)ものの、ある意味、そのせいでマンネリ感があった。20万とかするものを買ってるのに、特にここ最近のiPhoneの新しいフィーチャーはコケ気味なので、なんか別になんも変わらんよね、を無限に繰り返してた。Airは薄い軽いで大きな変化で、良いっちゃあ良いけど、機能じゃあないので大きな感動とはまた違う感じだったんですよね、なんだったら機能面ではPro Maxに比べて低下していたわけだし。&lt;/p&gt;
&lt;p&gt;そしてAI Codingブームじゃないですが、ちょっとした個人的な不便を解消するアプリを自分のスマホに入れたいなあという思いがずっとあったんで、Vibe Codingで雑に作ってデプロイしたかったんですよね。それがWin x iPhoneだと絶対的にできない。じゃあ、もう、ついにAndroidですね、と。&lt;/p&gt;
&lt;h2&gt;Switch to Android&lt;/h2&gt;
&lt;p&gt;iOS -&amp;gt; iOSとまではいかなくとも、今どきもう全自動で一瞬でサクッと移行できるっすよーという話を信じた（？）んですが、そんなこたぁなかった。ほとんど全部手動になりました、ふぁぃ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;eSIM転送は無理なので再発行(&lt;a href="https://newsroom.kddi.com/news/detail/kddi_nr-925_4335.html"&gt;auと一部機種なら行ける&lt;/a&gt;らしい)&lt;/li&gt;
&lt;li&gt;画像は節約でiCloudに退避させるオプションにしていたせいか歯抜けで入ってしまった。どうしようかなーってところで、Webからエクスポートできないかなと思ったんですが1000件毎では全然足りない。最終的に&lt;a href="https://github.com/icloud-photos-downloader/icloud_photos_downloader"&gt;icloud_photos_downloader&lt;/a&gt;というスクレイピングダウンローダーを実行してローカルに全部落としてから、USB接続でPC to AndroidでDCIMに放り込みました。工程が目に見えてて確実＆十分高速だったので、これはアリ&lt;/li&gt;
&lt;li&gt;アプリ類は当然なら再ログイン＆ものによっては独自のデータ移行、みたいな感じなので（あとログインの仕方忘れてるとかApple認証で入っちゃってるので連携し直しとか）まぁ、延々と頑張るしかない、と&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;手動とはいえ、別になんだかんだでやれる範囲なのと、iOSにしかないようなアプリとか機能をそんなに多用していたわけでもないので、意外とスムーズといえばスムーズとも言える、でしょうか。最近のアプリはほとんどサブスクだし、古の買い切りアプリ/ゲームはどうせ起動できないので、十分切り捨てられました。しいていえば&lt;a href="https://annapurnainteractive.com/ja/games/florence"&gt;Florence&lt;/a&gt;だけは入れておきたかったのだけど、最新のAndroid OSに対応していない(?)っぽく移行できず、残念……。&lt;a href="https://www.4gamer.net/games/242/G024214/20131210085/"&gt;Super Hexagon&lt;/a&gt;もAndroid版だけストアから消滅してるし。我慢できるけど悲しいといえば悲しい。この2つは人生のトップ10ゲームに絶対に入れるんですが。&lt;/p&gt;
&lt;p&gt;Xiaomi HyperOSがiOSに似てる（これはかなり似せてる系ですよね？）ので、ホームのレイアウトやウィジェットなども、それなりに元通りにできました。&lt;/p&gt;
&lt;h2&gt;No Suica&lt;/h2&gt;
&lt;p&gt;Leica Leitzphone powered by Xiaomiは、グローバルで同一モデルということで、NFCは搭載していますが、日本独自のFeliCaは非搭載。マイナポータルからのマイナンバーカードでの認証やクレジットカードのタッチ決済はできますが、Suicaは入れられない。なるほど！Xiaomi端末は結構非搭載が多め、と。&lt;/p&gt;
&lt;p&gt;まぁそれは、いいことだと思います！余計なコストかけずグローバルモデルであるべきだし、そもそも私はSuica全然好きじゃないので！なくていいです！なんだったら、ないほうが思想的に良いです！&lt;/p&gt;
&lt;p&gt;と、思想的にはなくていいんですが、実用的には困るといえば困る……。日常的な決済でSuicaが必要なシーンはゼロなのですが（電子決済としてSuicaしか対応してない、なんてことはないので）、やっぱバス・電車がどうしても……。Suicaカードでも別にいいかなあとあ思ったんですが、チャージの仕様が微妙すぎて今更チャージはしたくないんですよねえ。オートチャージの仕様も終わってるので基本、使えないと思っていいし。&lt;/p&gt;
&lt;p&gt;そこで、最近ずっとApple Watch Ultra使ってるので(去年体調崩してから、頻繁に心拍計測して安心感を得ている……)、とりあえずApple WatchにSuicaを入れて凌ぐことにしました。今のところやってやれなくもなし。Apple Watch自体は心拍測るのにしか使ってないので（？）母艦と連動してなくても問題なし。チャージもWiFiがあればApple Watch単体でできるので、出先でイザという時になくなってもテザリングでチャージで回避、もできそうなので問題なく運用できそう。&lt;/p&gt;
&lt;h2&gt;Gboard to SwiftKey&lt;/h2&gt;
&lt;p&gt;入力システムは慣れの問題もあるので、あまりどうこう言いたくはないのですが、Gboardの日本語QWERTYの機能/レイアウトだけは、流石に駄目です。私は入力は 英語QWERTY + 日本語QWERTY でやっているのですが日本語キーボードのレイアウトが&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/20260316_1.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;スペースがキー2つ分しか取られてない！さすがに狭すぎる。そしてレイアウト変更や不要キー削除などで広げることはできない。←→の矢印キーとか削除したいんですが。そしてフリーカーソルがない。日本語キーボードではこのクソみたいな←→矢印でカーソル移動するしかない。英語キーボードだとスペースのドラッグでカーソル移動できるのに何で……？さすがにキー2つじゃ移動させられないから機能搭載していないということ？&lt;/p&gt;
&lt;p&gt;というわけでキーボードは&lt;a href="https://play.google.com/store/apps/details?id=com.touchtype.swiftkey&amp;amp;hl=ja"&gt;Microsoft SwiftKey&lt;/a&gt;を使うことにしました。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/20260316_2.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;細かいことは置いといて、とりあえずレイアウトに関しては100点です。英語/日本語切り替えでアルファベットのレイアウトの移動がないのが素晴らしい。かわりに「ー」がShiftの位置になっているのが打ち間違いをたまによくやってしまいますが、ここは慣れでなんとかなるでしょう。フリーカーソルも日本語/英語の両方でちゃんと付いてるし。&lt;/p&gt;
&lt;p&gt;ところでクリップボードに関しては明確にiOSのキーボードよりも、こちらのほうが良いですね、履歴や固定が便利すぎて、なんでiOSにはないんですかー？WindowsにもWin+V（よく使ってる）で（やっと）標準で入ってるというのに……！&lt;/p&gt;
&lt;p&gt;また、&lt;a href="https://www.microsoft.com/ja-jp/windows/sync-across-your-devices"&gt;Windows 11でのスマートフォン連携&lt;/a&gt;は明らかに超強力でした。Windowsのクリップボードがリアルタイムに即Androidのクリップボードに入る。ようはAndroidで打たなきゃいけない文章をPCでさっと書いてAndroidに送り込める、と。今までiPhoneでチマチマ文章打ってたのが馬鹿らしくなってきましたほんと。。。&lt;/p&gt;
&lt;p&gt;サッとカメラロールをチェックできるのも良いですね。スクリーンショットとかPC上で確認/取得したかったりするケースが多いので。いやあ、母艦Windows使っててiPhone使い続けていたのはなんとも愚かだった。&lt;/p&gt;
&lt;h2&gt;Leitzphone&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.mi.com/jp/product/leica-leitzphone-powered-by-xiaomi/"&gt;Leica Leitzphone powered by Xiaomi&lt;/a&gt;は3月5日に発売されたばかりの端末で、&lt;a href="https://www.mi.com/jp/product/xiaomi-17-ultra/"&gt;Xiaomi 17 Ultra&lt;/a&gt;にレンズリング回転機構を追加してデザイン変えて一部UIのアイコンを変えたもの、です。価格差がまぁまぁあるのですが、レンズリング回転機構は値段以上の価値があるのでLeitzphoneのほうがいいですよ絶対……！リングは回すと本体のハプティックが連動してコトコトするのがまた気持ちい。&lt;/p&gt;
&lt;p&gt;スペック的には最新のSnapdragon 8 Elite Gen 5を積んでいて文句無し、なのですが誰もスペックの話はしないし、なんだったら公式サイトもカメラの話しかしないぐらいに、カメラ推し、です。私が撮ったものはここに載せたりはしませんが、適当に撮ってもめっちゃ映える絵が出力されて、すんごく気持ちいい。&lt;/p&gt;
&lt;p&gt;ところでXiaomi 17 Ultra/Leitzphoneには&lt;a href="https://www.mi.com/jp/product/xiaomi-17-ultra-photography-kit-pro/"&gt;Photography Kit Pro&lt;/a&gt;というカメラグリップめいたものを接続することができて、完全に見た目がカメラになります。&lt;/p&gt;
&lt;p&gt;で、これの使用感がまためっちゃいいんですね、ポートレートモードで、レンズリングでズームしてグリップのダイヤルで絞り値を変えて、反押しシャッターとか、完全にカメラで気持ちいい。これはいいものだ。&lt;/p&gt;
&lt;p&gt;と、非常に気に入ったのですがケースをつけないと使いにくい（サイズがケース着用に合わされてる）ので、裸で使うか、カメラグリップを使うためにケースをつけて使うか（なおケースは一度付けるとめちゃくちゃ外しにくい）の二択を迫られて、最初はケース付きを選択したのですが最終的に裸で使うことにしました。いかんせんケースつけてると美しくない＆厚い＆重い＆端のスワイプの操作感が悪くなるで、徐々にツラミが増してきたので。&lt;/p&gt;
&lt;p&gt;カメラグリップなくても割と普通にちゃんとカメラっぽくいけそうですし。Leitzphoneのサイドのローレット加工（ギザギザ）は滑り止めとしても機能としていて、見た目だけじゃなく（カメラとして構えているときの）持ちやすさにも寄与してます。Leitzphoneの場合はレンズリングがあるので、ダイヤルやズームレバーが死に設定になりがちなのも、なくてもいいかな、と思った理由でしょうか。&lt;/p&gt;
&lt;p&gt;各種レビューとか見るとLeitzphoneのレンズリング別にいらなかったとかいう評価もあるんですが、ほんとに……？スマホでこんなに自然にズーム回せるのはレンズリングだからこその体験だし（そのためにかLeitzphoneの75-100mmは光学ズームまで搭載されている！）。しいて言えば縦で普通に持つとリングが回しにくい（レンズに手がかかっちゃう）のですが、ここはグリップ付けた時の感覚で、レンズを下にするとスススッと回せるんですよね。というわけですっかり縦撮りのときは逆さにするようになりました。&lt;/p&gt;
&lt;p&gt;というわけで裸でもいいといえばいいんですが、でも気分も変わるのでグリップも併用していきたいので、裸使用でも接続性の良いPhotography Kit、出してくれませんかね……？あと今のところレンズリングはカメラアプリ内でしか機能しないんですが、普通にボリューム操作とかにも使いたいですね……！今後のアップデートに期待！&lt;/p&gt;
&lt;h2&gt;Xiaomi Buds 5 Pro Wifi&lt;/h2&gt;
&lt;p&gt;メインのイヤホンは&lt;a href="https://consumer.huawei.com/jp/audio/freeclip2/"&gt;HUAWEI FreeClip 2&lt;/a&gt;で、オープンイヤー型は、音楽を聞くだけではない、何だったら音楽を聞いている時間のほうが短い、ぐらいの現代のイヤホンの使い方に非常にマッチしたデザインだと思っています。YouTube見たり配信見たりZoomしたりだと、ノイズキャンセリングよりもこちらのほうが耳に自然で圧倒的にいいんですよね。&lt;/p&gt;
&lt;p&gt;最近のノイズキャンセリングイヤホンの外音取り込みモードは、それ自体はどれも優秀で自然なのですが、耳をふさいでいるということの軽い圧迫感に加えて、話したり、歩いたり、食事したりといった際に発生する人間の内側から出すノイズに脆弱で違和感が強く出てしまうのが難点でした。特にイヤホンしたままの食事は、私は無理だなあ、という感じです。&lt;/p&gt;
&lt;p&gt;オープンイヤー型は付け心地の軽さに加えて、それらが全く発生しないので、外音取り込みモードがどれだけ進化しても超えられないんじゃあないかな、と。実際これはSONYの最新ハイエンドのノイズキャンセリングイヤホンWF-1000XM6でも意識されているみたいで&lt;a href="https://www.watch.impress.co.jp/docs/series/itsmo/2091602.html"&gt;「イヤフォン本体から耳への通気量が増えたことで、自らの足音や咀嚼音などの体内ノイズが大幅に減少しました」&lt;/a&gt;といった対策を取ったりしているみたいですが、つまり構造的に絶対にオープンイヤー型レベルの体内ノイズ減少にはならないでしょう。&lt;/p&gt;
&lt;p&gt;そんなオープンイヤー型の中でもFree Clipの出来は断然よく、そしてこないだ発売されたばかりのFree Clip 2は更に進化していてすごい、すごい！絶対買うべき！&lt;/p&gt;
&lt;p&gt;なのですが、それはそれとしてノイズキャンセリングしたいし音楽もちゃんと聞きたいというシチュエーションはいっぱい存在するので、ノイズキャンセリングイヤホンも欲しい。と、そこで&lt;a href="https://www.mi.com/jp/product/xiaomi-buds-5-pro/"&gt;Xiaomi Buds 5 Pro&lt;/a&gt;です。厳密にはXiaomi Buds 5 Pro Wifiです。&lt;/p&gt;
&lt;p&gt;高音質と言われるコーデックLDACでも990kbpsのところを、Wi-Fi接続によりBluetoothの限界を突破して最大4.2Mbpsの伝送速度（4倍！) 96kHz/24ビットの真のロスレスオーディオに対応している、と。これはQualcommのXPANという最新の技術で実現していて、伝送側も対応している必要があるので、今のところ現実的に使える組み合わせはXiaomiのハイエンド端末とXiaomi Buds 5 Pro Wifiだけ、です。&lt;/p&gt;
&lt;p&gt;Qualcommからは&lt;a href="https://av.watch.impress.co.jp/docs/topic/2002406.html"&gt;Bluetoothに代わり、Wi-Fiがイヤフォンのワイヤレス伝送の中核に。Qualcomm「XPAN」とは何か&lt;/a&gt;ということで夢いっぱいな規格という雰囲気ですが、しかしとりあえず今のところ使えるのはXiaomiだけ、と。&lt;/p&gt;
&lt;p&gt;せっかくそんなハイエンド端末を買ったので（LeitzphoneはXiaomi 17 Ultraと同スペック）、これは是非試したい。最新究極プロトコルとか、圧倒的なロマンに溢れていますから……！&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/26260316_3.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;感想としては、パーフェクト。Wi-Fi伝送すごい、XPANすごい、これは流行るべき……。&lt;/p&gt;
&lt;p&gt;正直Bluetooth伝送だとイマイチ（特に比較対象として、いま手元にある &lt;a href="https://www.bose.co.jp/ja_jp/products/headphones/earbuds/QCUE2-HEADPHONEIN.html#v=QCUE2-HEADPHONEIN-DPPLM-WW"&gt;Bose QuietComfort Ultra Earbuds (第2世代)&lt;/a&gt; が相手だと）なのですが、Wi-Fi伝送だと変わります。いうてそこまで変わらないでしょと思いきや、かなり良くなる。BluetoothとWi-Fiでチューニング変えてるんじゃないの？というぐらいに変わってる感じがある。Boseと比較してどうこうというのはおいておきますが、とりあえずWi-Fi伝送はすごい、と。&lt;/p&gt;
&lt;p&gt;音質設定のコンフィグが色々とっ散らかっているんですが、外耳道スキャンしてサウンド補正とか、聴覚テストして補正とか、色々入っているのも面白い。ただ、どっちも、ONにすると個人的にはむしろ悪化したのでオフにしちゃいましたが……。サウンドモードに関してはデフォルトはHarman AudioEFXだったのですが、これはHarman Masterのほうが好ましかったです。&lt;/p&gt;
&lt;p&gt;とりあえず、今のところ特別な組み合わせによる音質ということに、嬉しさが上乗せされてて満足度めっちゃ高い。&lt;/p&gt;
&lt;h2&gt;C# .NET for Android&lt;/h2&gt;
&lt;p&gt;Androidで嬉しいのは野良アプリを入れ放題ということ、実際、まだ一週間ですが幾つか野良アプリを入れる機会が。で、つまりは野良アプリだけじゃなくて自作の雑アプリも入れ放題というわけですね！&lt;/p&gt;
&lt;p&gt;というわけでAndroidアプリを作ったことはないのですが（やるのはUnityアプリのデプロイぐらい）、Claudeの助けを借りて雑にやっていきましょう。C#で。&lt;/p&gt;
&lt;p&gt;AIがやるならネイティブで（Kotlinで）やれよ、という話もあるんですが、自分のスキルセットにない言語や環境でVibe Codingでアプリケーションを作ると、コードをほとんど見なくなっちゃうんですよね、なんか読んでもあんまわからないし、みたいな。動きゃあなんでもいいだろ、と。そして動くなくなったら普通に詰んで投げ捨ててる。さすがにC#なら分からないなりに分かるのと、環境セットアップもほぼ不要でVisual Studioで完結できるので、楽でいいです。&lt;/p&gt;
&lt;p&gt;作りたいものは決まっていて、ストップウォッチです。いや、なんかXiaomiのコントロールセンターにストップウォッチ（というか時計/アラーム）がないので、サクッと起動できないんで普通に困ってるんですよ、ないわけないと思うんですけど、なんで？&lt;/p&gt;
&lt;p&gt;あとラップタイムを叩いた時にトータルタイムだけじゃなくて、そのラップタイムの時間もリアルタイム表記して欲しいんですよね。これApple Watchのストップウォッチができなくてイライラしています（なのでワークアウトの表記をカスタマイズしてセグメントを常時表記して代用してます）。Xiaomiの標準ストップウォッチもできてないです。&lt;/p&gt;
&lt;p&gt;適当なストップウォッチを探す旅に出るほどのものでもないので、こういうものこそ雑に自作する時代でしょう、小さな不満は自作で解決する時代！&lt;/p&gt;
&lt;p&gt;というわけで初めての.NET for Androidです。Visual StudioでAndroidのテンプレートで作ってCtrl + F5すると、いきなり&lt;code&gt;dotnet workload restore&lt;/code&gt;しろと言われる。体験悪い……。幸いrestoreしただけでビルドは通って、デプロイしようとするとエミュレーター入れてね、とエミュレーターインストールウィンドウまで出てきて親切……！&lt;/p&gt;
&lt;p&gt;ただエミュレーターで開発してくのは興味なくて、今はVibeで実機にボンボン突っ込みたいだけなので、Leitzphoneのほうを開発者モードを有効にしてUSBデバッグを有効にして、&lt;code&gt;Error ADB0010: Mono.AndroidTools.InstallFailedException: Failure [INSTALL_FAILED_USER_RESTRICTED: Install canceled by user]&lt;/code&gt;で怒られる。これはUSB経由でインストールも有効にしてなかったせいらしいので、そっちも有効にする（なお、これはXiaomiアカウントを作ってログインしとかないと有効化できないらしい。個人端末なので普通にログインしてるので問題ないんですが、法人での開発用端末だと面倒ですね）。&lt;/p&gt;
&lt;p&gt;これでVisual Studio上でもデプロイ先にXiaomiがいるじゃないですかー。25128PNA1GがLeitzphoneのモデル名らしい。あとはこれでデプロイ、と。&lt;/p&gt;
&lt;p&gt;そんなわけで初めてのインストールを済ませたら、後は&lt;code&gt;MainActivity.cs&lt;/code&gt;と&lt;code&gt;activity_main.xml&lt;/code&gt;をClaudeに雑に編集してもらう、ということで何度かダメ出しして10往復ぐらいした後の&lt;code&gt;AndroidApp1&lt;/code&gt;がこちら。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/26260316_4.jpg" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;全然いいんじゃないでしょうか……！ストップウォッチに見た目も機能もなくて結構なので、全然これでヨシです。業務の複雑なコードの仕事ですらこなせる昨今、こんなドシンプルな要求に対するコードを生成できることに驚きは特になく。とはいえ、もはやそんな当たり前のことを当たり前にやってもらえることにしみじみ喜びがあります。いかんせん、Android知識ゼロなので、多分手書きしてたらこれ作るのそれなりにドキュメント読んでチュートリアルこなす必要があったので、それを完全にスキップして数分で完成なら上出来すぎます。&lt;/p&gt;
&lt;p&gt;コードは正直クソみたいなコードだな、とは思いましたが（長ったらしいのでここには貼りません）、AndroidのXmlも分からんし &lt;code&gt;Android.App.Activity&lt;/code&gt; もわからんのに、ここまでやってくれるなら言うことは特にないです。&lt;/p&gt;
&lt;p&gt;さて、アプリが及第点なので、不満点の解消としてコントロールセンターから起動できるようにしたい、と。ClaudeによるとTileServiceを実装すれば追加可能になるよ、と。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;using Android.App;
using Android.Content;
using Android.Service.QuickSettings;

namespace AndroidApp1;

[Service(
    Name = &amp;quot;AndroidApp1.StopwatchTileService&amp;quot;,
    Label = &amp;quot;ストップウォッチ&amp;quot;,
    Icon = &amp;quot;@drawable/ic_stopwatch&amp;quot;,
    Exported = true,
    Permission = &amp;quot;android.permission.BIND_QUICK_SETTINGS_TILE&amp;quot;)]
[IntentFilter(new[] { &amp;quot;android.service.quicksettings.action.QS_TILE&amp;quot; })]
public class StopwatchTileService : TileService
{
    public override void OnClick()
    {
        base.OnClick();

        var intent = new Intent(this, typeof(MainActivity));
        intent.AddFlags(ActivityFlags.NewTask);

        var pendingIntent = PendingIntent.GetActivity(
            this, 0, intent, PendingIntentFlags.Immutable)!;
        StartActivityAndCollapse(pendingIntent);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;やってることはシンプルそうですが、こっちはミリしらなので、別にレビューすることもなく素通ししかない。&lt;/p&gt;
&lt;p&gt;アイコンもXMLで生成してくれてありがたい。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-xml"&gt;&amp;lt;vector xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;
    android:width=&amp;quot;24dp&amp;quot;
    android:height=&amp;quot;24dp&amp;quot;
    android:viewportWidth=&amp;quot;24&amp;quot;
    android:viewportHeight=&amp;quot;24&amp;quot;&amp;gt;
	&amp;lt;!-- stopwatch body --&amp;gt;
	&amp;lt;path
        android:fillColor=&amp;quot;#FFFFFF&amp;quot;
        android:pathData=&amp;quot;M12,4C7.03,4 3,8.03 3,13s4.03,9 9,9 9,-4.03 9,-9S16.97,4 12,4zM12,20c-3.86,0 -7,-3.14 -7,-7s3.14,-7 7,-7 7,3.14 7,7 -3.14,7 -7,7z&amp;quot; /&amp;gt;
	&amp;lt;!-- hand --&amp;gt;
	&amp;lt;path
        android:fillColor=&amp;quot;#FFFFFF&amp;quot;
        android:pathData=&amp;quot;M11,8h2v6h-2z&amp;quot; /&amp;gt;
	&amp;lt;!-- top button --&amp;gt;
	&amp;lt;path
        android:fillColor=&amp;quot;#FFFFFF&amp;quot;
        android:pathData=&amp;quot;M11,1h2v3h-2z&amp;quot; /&amp;gt;
&amp;lt;/vector&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;というわけで無事ストップウォッチがコントロールセンターに追加されました（右下）&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/26260316_5.jpg" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;完成！&lt;/p&gt;
&lt;p&gt;ここまでサクサク行ったので、更に欲張って、iPhoneのストップウォッチと同じ用にXiaomiのダイナミックアイランド(Xiaomi HyperIslandというらしい）に対応させてやろう、と思ったら、なんと独自の非公開データを投げつけなきゃいけないとか複雑骨折仕様なので、少しやらせたけど、うまくできなかったので撤退しました。これはXiaomiが悪い。ちなみに独自仕様は、次のHyperOS 3.1からは標準仕様に乗っかるらしいので、無理やり頑張るよりそれを待ったほうがお得っぽいのでヨシ。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;せっかくなので断捨離したからとかもありますが、全体的に満足度は非常に高くなりました。面倒くさくはありましたが、移行して良かった。色々良さはあるんですが、やっぱLeitzphone自体が良いのが何より、かな。ハプティックフィードバックのあるレンズリングをグルグル回す原始的な楽しさと、雑にぱっと撮ってもそれなりに見栄えがしてしまうライカマジックのカメラは官能度高いです。&lt;/p&gt;
&lt;p&gt;AndroidなのかXiaomi HyperOSなのか、という切り分けもありますが、iOSに比べてぱっと見は一緒なのだけど、すぐに作りの甘さみたいなのは感じてしまいます。特に文字周りがイマイチになりがちではあるかなあ。Windowsはもっとボロボロなので全く許容範囲なのですが！ガクガクするような動きの遅さとか引っ掛かりは一切感じず、ちゃんとヌルッとしてるので全然アリではありますよ。&lt;/p&gt;
&lt;p&gt;サードパーティアプリまで広げると、ダイナミックアイランド(Xiaomi HyperIsland)が全然活かされていない。iOSでのライブアクティビティがダイナミックアイランドに格納されるのはかなり便利だったので、ちゃんと動いて欲しいと思ったんですよね。雑アプリ作っていて知ったんですが、現状はだいぶ独自対応、というか非公開の仕様に沿った出力が必要なようで、こういうOSの分断は、そりゃあサードパーティアプリはカバーできるわけがないよねえ、と理解しました。中国国内のアプリはかなり対応していそうですがグローバルなアプリが完全に未対応になっちゃってるんですね。ただしこれは、標準APIと繋ぎ合わせて対応させていくみたいな流れ(HyperOS 3.1(Beta)ではAndroid 16の標準API経由で対応できそうらしい)も合わせて、時が解決していく流れでやってるのならヨシ、ということで。&lt;/p&gt;
&lt;p&gt;また、これもHyperOS起因なのか、素のカスタマイズ製はiOSよりもないですね。コントロールセンターにアプリも追加できないし。思ってたよりも遥かにガチガチで何も追加できない雰囲気で(iOSはなんだかんだでショートカット組めば何でもできるみたいなところがあった）、これは雑アプリを作ってカバーするか、といった気持ちでいます。&lt;/p&gt;
&lt;p&gt;とはいえ総評としては本当に改めて大満足で（移行が面倒くさくてサンクコスト爆発してるので、そういい聞かせているところもなくはない）、WindowsユーザーはAndroid使うべきですよ過激派に転向します……！&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Sun, 15 Mar 2026 00:00:00 +0900</pubDate>
      <a10:updated>2026-03-15T00:00:00+09:00</a10:updated>
    </item>
    <item>
      <guid isPermaLink="true">https://neue.cc/2025/12/30_year.html</guid>
      <link>https://neue.cc/2025/12/30_year.html</link>
      <title>2025年を振り返る</title>
      <description>&lt;h1 data-pagefind-sort="date:2025-12-30" data-pagefind-meta="published:2025-12-30"&gt;&lt;a href="https://neue.cc/2025/12/30_year.html"&gt;2025年を振り返る&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2025-12-30&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;今年のC#やりこみ振り返り……！&lt;/p&gt;
&lt;p&gt;新規:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/ZLinq"&gt;ZLinq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/ToonEncoder"&gt;ToonEncoder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大きめアップデート&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/ConsoleAppFramework/"&gt;ConsoleAppFramework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;作りかけ&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/NativeCompressions"&gt;NativeCompressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/CompilerBrain"&gt;CompilerBrain&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;毎年、今年のやりこみ度は大丈夫かな？という気になりますが、とりあえずZLinqが大型案件（？）だったので大丈夫でしょう。注目度(Star数は現在4800行きました)も高く、実装の安定度も高く、奇抜性も高くということで、いいものになっているのではないかと思います。Star数でいうと、&lt;a href="https://github.com/Cysharp/UniTask"&gt;UniTask&lt;/a&gt;は10000超えしました！&lt;a href="https://github.com/Cysharp/"&gt;GitHub/Cysharp&lt;/a&gt;全体では45000超え、neueccやMessagePack-CSharp名義のものも含めれば、トータルで60000を超えるということで、C#関連でいったら世界屈指と言えるのではないでしょうか。&lt;/p&gt;
&lt;p&gt;そんなわけで悪くない結果だと思うのですが、一方で今年はブログをほとんど書いていません！年々書かなくなっていますが、過去一で書いていません！理由としては体調不良で、ZLinqを終えてちょっとした辺りグッと体調を崩してしまって色々と停滞してしまいました。OSS関連も意識的に手を付けないようにしていたので、Issue/PR系も過去一番に手を付けられていません……！言い訳がましいことを言うと、作りかけを完了できなかったのもこの辺が悪く――。といった感じでは不満の残る結果でもあります。一応、復調傾向にはあるので、来年はじっくり取り込みたいとは思っています。&lt;/p&gt;
&lt;p&gt;これは私が発表したわけではありませんが&lt;/p&gt;
&lt;iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/2f01ecc693a047fb90137ebb43640572" title="【U/Day Tokyo 2025】Cygames流 最新スマートフォンゲームの技術設計 〜『Shadowverse: Worlds Beyond』におけるアーキテクチャ再設計の挑戦～" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"&gt;&lt;/iframe&gt;
&lt;p&gt;というセッションで&lt;a href="https://shadowverse-wb.com/ja/"&gt;Shadowverse: Worlds Beyond&lt;/a&gt;のアーキテクチャに関する発表があったのですが、かなり&lt;a href="https://github.com/Cysharp/MagicOnion/"&gt;Cysharp/MagicOnion&lt;/a&gt;について言及してもらっています。触れられているとおり、初期から開発に関わらせていただいています。採用ライブラリも「MagicOnion、R3、ZLinq、MessagePack for C#、UniTask、YetAnotherHttpHandler」とアグレッシブに新しいものも詰んでもらいました。リリースを迎えられるのは、やはりとても嬉しいですね……！&lt;/p&gt;
&lt;h2&gt;なんとか of the Year&lt;/h2&gt;
&lt;p&gt;Game of the Year総舐めですが、やはり私も&lt;a href="https://expedition33.sega.jp/"&gt;Clair Obscur: Expedition 33&lt;/a&gt;を挙げたいと思います。ああ、こういうの好きだったよなあという感覚と、ただの懐古主義じゃない完全に現代的に調和した内容。こうあって欲しいという思いが見事実現化されているようです。めちゃくちゃフランスを感じるところは、西洋風のエスニックで、馴染みあるようで全然なくてめちゃくちゃ新鮮でもあって、まぁ、とにかく良い、文句無し……！全てが気合入っているというよりは、見せ場には力をしっかり入れつつ、手を抜くところはしっかり抜くというバランス感覚は開発的には大事ですねえ。ゲームバランスが序盤-中盤がかなり緊張感ある面白さだったのに反して、後半はインフレバトルに突入するのは、一粒で二度美味しい、というには、やはり大味になりすぎた気がするので若干残念ですが、まぁ味変ということで、それはそれで良いでしょふ。&lt;/p&gt;
&lt;p&gt;なんか買ったものとしては、軽量化計画ということで財布を&lt;a href="https://shop.zenlet.co/pages/cache-30"&gt;Zenlet Cache 3.0&lt;/a&gt;に変えたのですが、ポケットがすっきりして実際めっちゃ良き。数年前から小銭入れのない財布を使ってきて、まぁまぁやれてたので、去年は&lt;a href="http://ridgewallet.jp/products/allproducts.html"&gt;the RIDGE&lt;/a&gt;というマネークリップを使っていたのですが、更に突き詰めたいと思って色々探していて見つけたのがCache 3.0でした。&lt;/p&gt;
&lt;p&gt;マイナンバーカードもiPhoneに入るようになったし（とはいえマイナンバーカードは持ち歩いていますが）、楽天銀行も&lt;a href="https://www.rakuten-bank.co.jp/atm/smartphone-atm/"&gt;スマホATMに対応&lt;/a&gt;したしということで、カードも厳選して持ち歩く枚数をかなり減らせるので、最小限の最小限で十分かな、と。最小限のカード(クレジットカードとマイナンバーカードとその他一枚)と最小限の現金(1万円札1枚と1000円札1枚)で問題なし。Phoneにペタっと付く＆キックスタンドになるのも便利なので、良い塩梅でした。&lt;/p&gt;
&lt;p&gt;iPhoneはiPhone Airに変更しました。ここ数年ずっとPro Maxだったので、こちらも一気に軽量化……！基本的に欲しいのは画面サイズだったので、Pro以上Max以下の画面サイズは個人的にはジャストです。まぁ不人気すぎて後継機器は出なそうなので、Foldが来たら一気に重量化しちゃいますが……。バッテリー持ちがかなり悪いので、マグセーフ型のバッテリーとして&lt;a href="https://www.amazon.co.jp/dp/B0DTF62YJK"&gt;MATECH(マテック)® Qi2 モバイルバッテリー マグセーフ&lt;/a&gt;を合わせています。これはマグセーフに加えてスタンドが付いてるところがポイントで、外で、というよりかは家の中で、休日とかに1日中持たないので、スタンド代わりに使いながら充電することで1日持たせる、みたいな使い方をしています。というわけで、スタンド付きが重要です。財布もスタンド付きが重要だったということで、今年はスタンドブームが来てます……！&lt;/p&gt;
&lt;p&gt;バッテリーといえば&lt;a href="https://www.ankerjapan.com/products/a1695"&gt;Anker Power Bank (25000mAh, Built-In &amp;amp; 巻取り式USB-Cケーブル)&lt;/a&gt;が今年買って、一番便利だったもの、かも。これも完全に家用なのですが、ワイヤレスで移動可能な軽量充電ステーションとして使ってます。コンセントの近くまで行かないと充電できないのが何かと不便だったのですが、これがあればどこでも充電ステーション化できるので、ちょっとした小物の充電がめっちゃ楽になりました。25000mAhあれば、私の使い方的には一週間は持つので、この容量が個人的にはベスト（あまり大きくするとデカいし重いしになるので）。ケーブルが2本ビルトインで生えてるのもいい塩梅です。&lt;/p&gt;
&lt;p&gt;というわけで巻取り式ケーブル内蔵ブームが来たので&lt;a href="https://www.ankerjapan.com/products/a2658"&gt;Anker Nano Charger (35W, 巻取り式 USB-Cケーブル)&lt;/a&gt;と&lt;a href="https://www.ankerjapan.com/products/a91c8"&gt;Anker Nano Charging Station (7-in-1, 100W, 巻取り式 USB-Cケーブル)&lt;/a&gt;も調達して、便利度が上がりました。&lt;/p&gt;
&lt;h2&gt;来年&lt;/h2&gt;
&lt;p&gt;使ってないわけではないですが、AIブームから乗り遅れている＆Vibe Codingには懐疑派として、&lt;a href="https://github.com/Cysharp/CompilerBrain"&gt;CompilerBrain&lt;/a&gt;というC# Coding Agentの計画があるので、来年初頭には見せれるものに仕上げたいと思っています。あと今年溜まったOSS Issueもなんとかやりきりたい……！&lt;/p&gt;
&lt;p&gt;それとUnityのCoerCLR対応がついに来年くる、っぽいので、めっちゃ楽しみにしています。今まで以上にC#やり込みパワーが火を吹くときが来ましたか……！言語を極めることで、表現できることの幅というのは広がるものなのです、ということを実証していきますよ……！&lt;/p&gt;
&lt;p&gt;今年はOSSとお金に関する話も多くトピックスに上がって来ました。C#でも、やはりホットなトピックスで、有名ライブラリの有償化や、提言など色々が色々です。私としては引き続きOSSベースでやっていきますし、有償化は全く考えていません。とはいえメンテナーに強く負荷がかかることもあり、無償の奉仕を求めるのが当然ではありません。持続性のあるOSSエコシステムを求めるなら、なおのこと、お金に関しては強く考える必要があります。そんなわけで改めて、特に直接の還元ができているわけではないですが、&lt;a href="https://github.com/sponsors/neuecc"&gt;sponsors/neuecc&lt;/a&gt;への寄付はとてもありがたく思っています。&lt;/p&gt;
&lt;p&gt;というわけで引き続きC#を発展させていきますので、来年も頑張りましょう……！&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Tue, 30 Dec 2025 00:00:00 +0900</pubDate>
      <a10:updated>2025-12-30T00:00:00+09:00</a10:updated>
    </item>
    <item>
      <guid isPermaLink="true">https://neue.cc/2025/12/23_toonencoder.html</guid>
      <link>https://neue.cc/2025/12/23_toonencoder.html</link>
      <title>ToonEncoder - C#とLLMのためのJSON互換フォーマットエンコーダー</title>
      <description>&lt;h1 data-pagefind-sort="date:2025-12-23" data-pagefind-meta="published:2025-12-23"&gt;&lt;a href="https://neue.cc/2025/12/23_toonencoder.html"&gt;ToonEncoder - C#とLLMのためのJSON互換フォーマットエンコーダー&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2025-12-23&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;&lt;a href="https://github.com/toon-format/toon"&gt;Token-Oriented Object Notation(TOON)&lt;/a&gt;というJSON互換のフォーマットのシリアライザー（エンコードのみ）を作りました。TOONは、適切に活用することで、LLMとの対話時に、トークンを大きく節約できる可能性を秘めています。コンパクトなライブラリーではありますが、内部的には全てUTF8ベースで処理していて、&lt;code&gt;IBufferWriter&amp;lt;byte&amp;gt;&lt;/code&gt;対応やSource Generatorによるシリアライザー生成など、現代的なライブラリとしての基本機能は十分備えています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/ToonEncoder"&gt;GitHub - Cysharp/ToonEncoder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;もちろん、競合と比べてもパフォーマンスやメモリ効率は圧倒的に良いです。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/Cysharp/ToonEncoder/main/img/image.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;この辺はとにかく私がシリアライザーの設計に慣れすぎていて(&lt;a href="https://github.com/MessagePack-CSharp/MessagePack-CSharp"&gt;MessagePack-CSharp&lt;/a&gt;, &lt;a href="https://github.com/Cysharp/MemoryPack"&gt;MemoryPack&lt;/a&gt;, &lt;a href="https://github.com/neuecc/Utf8Json"&gt;Utf8Json&lt;/a&gt;, etc...)実績もノウハウもありまくりなので……！ジャンルがジャンルなのでAIでとりあえず動くものにしましたっぽいライブラリも多い感じですが、全然勝負になりません。なんせこちらは温かみのある手作りコードですから……！ハイパーハンドメイドクラフトコーディング。現状のAI生成のコードレベルは、トップレベルからは、ほど遠いと実際思ってます。動くものはできるし、それは凄いんですけど、ね。&lt;/p&gt;
&lt;p&gt;さて、まずTOONについて軽く説明すると、以下のJSONのデータが&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-json"&gt;{
  &amp;quot;context&amp;quot;: {
    &amp;quot;task&amp;quot;: &amp;quot;Our favorite hikes together&amp;quot;,
    &amp;quot;location&amp;quot;: &amp;quot;Boulder&amp;quot;,
    &amp;quot;season&amp;quot;: &amp;quot;spring_2025&amp;quot;
  },
  &amp;quot;friends&amp;quot;: [&amp;quot;ana&amp;quot;, &amp;quot;luis&amp;quot;, &amp;quot;sam&amp;quot;],
  &amp;quot;hikes&amp;quot;: [
    {
      &amp;quot;id&amp;quot;: 1,
      &amp;quot;name&amp;quot;: &amp;quot;Blue Lake Trail&amp;quot;,
      &amp;quot;distanceKm&amp;quot;: 7.5,
      &amp;quot;elevationGain&amp;quot;: 320,
      &amp;quot;companion&amp;quot;: &amp;quot;ana&amp;quot;,
      &amp;quot;wasSunny&amp;quot;: true
    },
    {
      &amp;quot;id&amp;quot;: 2,
      &amp;quot;name&amp;quot;: &amp;quot;Ridge Overlook&amp;quot;,
      &amp;quot;distanceKm&amp;quot;: 9.2,
      &amp;quot;elevationGain&amp;quot;: 540,
      &amp;quot;companion&amp;quot;: &amp;quot;luis&amp;quot;,
      &amp;quot;wasSunny&amp;quot;: false
    },
    {
      &amp;quot;id&amp;quot;: 3,
      &amp;quot;name&amp;quot;: &amp;quot;Wildflower Loop&amp;quot;,
      &amp;quot;distanceKm&amp;quot;: 5.1,
      &amp;quot;elevationGain&amp;quot;: 180,
      &amp;quot;companion&amp;quot;: &amp;quot;sam&amp;quot;,
      &amp;quot;wasSunny&amp;quot;: true
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TOONで表現すると以下のように小さくなります。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-yaml"&gt;context:
  task: Our favorite hikes together
  location: Boulder
  season: spring_2025
  friends[3]: ana,luis,sam
  hikes[3]{id,name,distanceKm,elevationGain,companion,wasSunny}:
    1,Blue Lake Trail,7.5,320,ana,true
    2,Ridge Overlook,9.2,540,luis,false
    3,Wildflower Loop,5.1,180,sam,true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSONというよりかは、YAMLとCSVのハイブリッドのようなもので、特に、テーブルとして(CSVとして)表現できる、プリミティブ要素のみを含むオブジェクトの配列が、CSV的に出力されるのでデータが大きく縮みます。この縮み幅がLLMにおけるトークンの節約に繋がるということでちょっとだけ脚光を浴びました。ならよくわからんフォーマットじゃなくてCSVでいいじゃん、というと、CSVだけだとテーブルのみで付随情報がつけられなくて実用には厳しいので、こちらのほうが使い勝手は良い印象です。また、JSONと相互互換のある仕様にしていることで、JSONからのDrop-in replacementが可能というのもセールスポイントにはなっています。&lt;/p&gt;
&lt;p&gt;個人的な所感としてはTOONはヒューマンリーダブルではないです。TOONは効率性に寄せているため、配列の表現方法が3種類あります。ToonEncoderではTabularArray、InlineArray、NonUniformArrayと呼んでいますが、3種類あると正直読みづらいよね。また、TabularArrayとNonUniformArrayがオブジェクトのネストと合わさると、インデントがわけわからなくなります。LLMは、よくわからん形式とはいえ、ヒューマンリーダブルなら、なんとなくちゃんと読み取ってくれている雰囲気がありますが、そうした破綻した状態で解釈を正しく持ってくれるかどうかには不安があります。&lt;/p&gt;
&lt;p&gt;というわけで、JSONを全て置き換えるのではなく、ピンポイントにCSV的なテーブル(TabularArrya)か、フラットなオブジェクトにTabularArrayを末尾に足したぐらいのものに適用するのが、トークン効率的にもLLMの理解力的にも人間のリーダビリティ的にもちょうど良いのではないかと思っています。実際ToonEncoderではそうした運用で最高なパフォーマンスが出るように調整してありますし、Microsoft.Extensions.AIとの組み合わせで、一部の型のみToon化する、といった連携ができるようになっています。&lt;/p&gt;
&lt;h2&gt;Microsoft.Extensions.AIと一緒に使う&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.nuget.org/packages/ToonEncoder"&gt;NuGet/ToonEncoder&lt;/a&gt;からダウンロードしてもらうとコアライブラリ―とSource Generatorが同梱でついてきます。なお最小ターゲットプラットフォームは .NET 10 です。&lt;/p&gt;
&lt;p&gt;基本的には&lt;code&gt;Encode&lt;/code&gt;で&lt;code&gt;JsonElement&lt;/code&gt;、または&lt;code&gt;T value&lt;/code&gt;を変換できます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;using Cysharp.AI;

var users = new User[]
{
    new (1, &amp;quot;Alice&amp;quot;, &amp;quot;admin&amp;quot;),
    new (2, &amp;quot;Bob&amp;quot;, &amp;quot;user&amp;quot;),
};

// simply encode
string toon = ToonEncoder.Encode(users);

// [2]{Id,Name,Role}:
//   1,Alice,admin
//   2,Bob,user
Console.WriteLine(toon);

public record User(int Id, string Name, string Role);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回はプリミティブ要素のみのオブジェクト配列のため、表形式レイアウト(TabularArray)としてシリアライズされています。&lt;/p&gt;
&lt;p&gt;具体的な利用法としては&lt;a href="https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai"&gt;Microsoft.Extensions.AI&lt;/a&gt;のFunction Callingに適用する場合は、対応する型のコンバーターを設定した &lt;code&gt;JsonSerializerOptions&lt;/code&gt; を用意し、オプションに渡してあげると良いでしょう。また、Source Generatorを使うと、効率的なJsonConverterを生成してくれます。使用方法は対象の型に&lt;code&gt;[GenerateToonTabularArrayConverter]&lt;/code&gt;するだけです！&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;public IEnumerable&amp;lt;AIFunction&amp;gt; GetAIFunctions()
{
    var jsonSerializerOptions = new JsonSerializerOptions
    {
        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
        WriteIndented = false,
        DefaultIgnoreCondition = JsonIgnoreCondition.Never,
        Converters =
        {
            // setup generated converter
            new Cysharp.AI.Converters.CodeDiagnosticTabularArrayConverter(),
        }
    };
    jsonSerializerOptions.MakeReadOnly(true); // need MakeReadOnly(true) or setup converter to TypeInfoResolver

    var factoryOptions = new AIFunctionFactoryOptions
    {
        SerializerOptions = jsonSerializerOptions
    };

    yield return AIFunctionFactory.Create(GetDiagnostics, factoryOptions);
}

[Description(&amp;quot;Get error diagnostics of the target project.&amp;quot;)]
public CodeDiagnostic[] GetDiagnostics(string projectName)
{
    // ...
}

// Trigger of Source Generator
[GenerateToonTabularArrayConverter]
public class CodeDiagnostic
{
    public string Code { get; set; }
    public string Description { get; set; }
    public string FilePath { get; set; }
    public int LocationStart { get; set; }
    public int LocationLength { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この例の場合、&lt;code&gt;CodeDiagnostic[]&lt;/code&gt;の件数が多いと、JsonとToonでトークン消費量にかなりの差が出てToonの優位度が高まります。ただし、Toonには得手不得手があるので、特性を見てToonを適用するか(Converterを追加するか)そのままにする(Json)かを選んでいくといいと思っています。&lt;/p&gt;
&lt;p&gt;フラットな階層のオブジェクト（プリミティブ, プリミティブ要素の配列, プリミティブ要素のみで構成されたオブジェクトの配列）の生成の場合は、別の属性&lt;code&gt;[GenerateToonSimpleObjectConverter]&lt;/code&gt;によりTabularArray + 追加のメタデータといったシナリオに対応できます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;var item = new Item
{
    Status = &amp;quot;active&amp;quot;,
    Users = [new(1, &amp;quot;Alice&amp;quot;, &amp;quot;Admin&amp;quot;), new(2, &amp;quot;Bob&amp;quot;, &amp;quot;User&amp;quot;)]
};

var toon = Cysharp.AI.Converters.ItemSimpleObjectConverter.Encode(item);

// Status: active
// Users[2]{Id,Name,Role}:
//   1,Alice,Admin
//   2,Bob,User
Console.WriteLine(toon);

[GenerateToonSimpleObjectConverter]
public record Item
{
    public required string Status { get; init; }
    public required User[] Users { get; init; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Json to Toon&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ToonEncoder.Encode&lt;/code&gt;は &lt;code&gt;JsonElement&lt;/code&gt; から &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;byte[]&lt;/code&gt; への変換、 &lt;code&gt;IBufferWriter&amp;lt;byte&amp;gt;&lt;/code&gt;, &lt;code&gt;ToonWriter&lt;/code&gt;への書き込みをサポートします。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;namespace Cysharp.AI;

public static class ToonEncoder
{
    public static string Encode(JsonElement element);

    public static void Encode&amp;lt;TBufferWriter&amp;gt;(ref TBufferWriter bufferWriter, JsonElement element)
        where TBufferWriter : IBufferWriter&amp;lt;byte&amp;gt;;

    public static void Encode&amp;lt;TBufferWriter&amp;gt;(ref ToonWriter&amp;lt;TBufferWriter&amp;gt; toonWriter, JsonElement element)
        where TBufferWriter : IBufferWriter&amp;lt;byte&amp;gt;;

    public static byte[] EncodeToUtf8Bytes(JsonElement element);

    public static async ValueTask EncodeAsync(Stream utf8Stream, JsonElement element, CancellationToken cancellationToken = default);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;IBufferWriter&amp;lt;byte&amp;gt;&lt;/code&gt;のオーバーロードを用いるとUTF8で直接データを書き込むため、&lt;code&gt;string&lt;/code&gt;変換を介すよりもパフォーマンスが高くなります。&lt;/p&gt;
&lt;p&gt;EncodeではJsonElementがarrayの際に、TabularArrayかInlineArrayかNonUniformArrayかどうかを全件チェックしてから書き込みしますが、&lt;code&gt;JsonElement&lt;/code&gt;が&lt;code&gt;array&lt;/code&gt;かつ、全ての要素の出現順序が等しく、全てがプリミティブ(Array, Objectではない)であることを保証できる場合は &lt;code&gt;EncodeAsTabularArray&lt;/code&gt; メソッドを用いると検査を省くため、より高いパフォーマンスで変換できます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;namespace Cysharp.AI;

public static class ToonEncoder
{
    public static string EncodeAsTabularArray(JsonElement array);

    public static void EncodeAsTabularArray&amp;lt;TBufferWriter&amp;gt;(ref TBufferWriter bufferWriter, JsonElement array)
        where TBufferWriter : IBufferWriter&amp;lt;byte&amp;gt;;

    public static byte[] EncodeAsTabularArrayToUtf8Bytes(JsonElement array);

    public static async ValueTask EncodeAsTabularArrayAsync(Stream utf8Stream, JsonElement array, CancellationToken cancellationToken = default);

    public static void EncodeAsTabularArray&amp;lt;TBufferWriter&amp;gt;(ref ToonWriter&amp;lt;TBufferWriter&amp;gt; toonWriter, JsonElement array)
        where TBufferWriter : IBufferWriter&amp;lt;byte&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;というのが基本的な変換の仕様になっています。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;この記事は、C# Advent Calendar 2025に特にエントリーしていない記事ですが、時期的にはだいたいそんな感じです。&lt;/p&gt;
&lt;p&gt;このToonEncoderは、&lt;a href="https://github.com/Cysharp/CompilerBrain"&gt;Cysharp/CompilerBrain&lt;/a&gt;という全然まだできてないC# Coding Agentのパーツとして用意しました。結構データ大量にドカドカするので節約したいなあ、と思い……。そんなわけで来年初頭はCompilerBrainやっていきます、多分……！&lt;/p&gt;
&lt;p&gt;ところで改めて正直なところTOON自体は別に全然いいフォーマットとは思えません。というかどちらかといえば相当厳しい……。が、まぁマーケティング的にJSON互換でDrop-in replacementというのが響いたのはありそうだし、実際CSVだと厳しいっちゃあ厳しいので、とりあえず仕様があるという点で妥協として悪くないといえば悪くない選択かもしれません。&lt;/p&gt;
&lt;p&gt;複雑なデータをシリアライズする気はない、ということが&lt;code&gt;[GenerateToonTabularArrayConverter]&lt;/code&gt;と&lt;code&gt;[GenerateToonSimpleObjectConverter]&lt;/code&gt;に現れています。これはAnalyzerも兼ねていて非対応なネストしたプロパティとか持たせようとするとコンパイルエラーにするという、ようはToonのサブセットみたいなものを疑似的に作り出しているんですね。もちろんJsonElement経由のメソッドを呼べば、ちゃんとネストしたプロパティとかはシリアライズできます。一応用意されている公式のテストスイートには（意図的にサポートしていない機能を除いて）全件合格しています。&lt;/p&gt;
&lt;p&gt;またライブラリ名の通り、Encodeしかサポートしていません。Decodeはできません。LLMに送信するためのものなのでだから、デコードは別にいらないでしょう。&lt;/p&gt;
&lt;p&gt;といった感じで色々と手を抜いたコンパクトさもあるのですが、それなりに実用的にはなっているので、興味ある方は是非是非試してみてください！&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Tue, 23 Dec 2025 00:00:00 +0900</pubDate>
      <a10:updated>2025-12-23T00:00:00+09:00</a10:updated>
    </item>
    <item>
      <guid isPermaLink="true">https://neue.cc/2025/07/14_mvp-renew15.html</guid>
      <link>https://neue.cc/2025/07/14_mvp-renew15.html</link>
      <title>Microsoft MVP for Developer Technologies(.NET)を再々々々々々々々々々々々々々受賞しました</title>
      <description>&lt;h1 data-pagefind-sort="date:2025-07-14" data-pagefind-meta="published:2025-07-14"&gt;&lt;a href="https://neue.cc/2025/07/14_mvp-renew15.html"&gt;Microsoft MVP for Developer Technologies(.NET)を再々々々々々々々々々々々々々受賞しました&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2025-07-14&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;Microsoft MVPは一年ごとに再審査されるのですが、今年も更新しました。2011年から初めて15回目です。昨今の予算の事情を考えると、席も少なくなっているのではないかという気配があるので、いつまでも居座るのはどうなのかという気もしますが、まぁ実績出してるから、しょうがないね……？&lt;/p&gt;
&lt;p&gt;毎年新しいOSSでヒット作を出すのも大変ですよ、と思いつつ去年は&lt;a href="https://github.com/Cysharp/R3"&gt;R3&lt;/a&gt;、今年は&lt;a href="https://github.com/Cysharp/ZLinq"&gt;ZLinq&lt;/a&gt;を出しました。これは文句なしでいいんじゃないかと。ZLinqは本当に作るの大変だったんで報われたい（？）&lt;/p&gt;
&lt;p&gt;今回の審査期間中の対外的な発表は以下の3つがありました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/neuecc/dot-netnofei-tong-qi-zhan-lue-tounitytonoxiang-hu-yun-yong"&gt;.NETの非同期戦略とUnityとの相互運用&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/neuecc/r3nokodokarajian-rushi-jian-linqshi-zhuang-zui-shi-hua-konkarentopuroguramingushi-li"&gt;R3のコードから見る実践LINQ実装最適化・コンカレントプログラミング実例&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/neuecc/cysharpnoossqun-karajian-rumodern-c-number-noxian-zai-di"&gt;CysharpのOSS群から見るModern C#の現在地&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2025年に入ってからは何もやっていないので、何か機会があれば、と伺ってはいるのですが中々どうして。今年はAI関連に力を入れたいと思っているので、まずはOSSで何か出してから、というのがいつもの自分のパターンなので、それは近々出します……！あとはZLinqに関してはアーキテクチャを話す場が作れるといいんですが、まぁそのうちどこかで。今のところ予定は完全に未定です。&lt;/p&gt;
&lt;p&gt;自社のタイトルではないので私のほうから大きく言えることはないのですが、先日は&lt;a href="https://shadowverse-wb.com/ja/"&gt;大きなタイトル&lt;/a&gt;がリリースされたというのもあるので、そういったところからもC#(クライアント/サーバー)の力を世の中にアピールできるといいな、とは思っています。&lt;/p&gt;
&lt;p&gt;というわけで引き続き、C#の推進やっていきます！&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Mon, 14 Jul 2025 00:00:00 +0900</pubDate>
      <a10:updated>2025-07-14T00:00:00+09:00</a10:updated>
    </item>
    <item>
      <guid isPermaLink="true">https://neue.cc/2025/05/05_ZLinq.html</guid>
      <link>https://neue.cc/2025/05/05_ZLinq.html</link>
      <title>ゼロアロケーションLINQライブラリ「ZLinq」のリリースとアーキテクチャ解説</title>
      <description>&lt;h1 data-pagefind-sort="date:2025-05-05" data-pagefind-meta="published:2025-05-05"&gt;&lt;a href="https://neue.cc/2025/05/05_ZLinq.html"&gt;ゼロアロケーションLINQライブラリ「ZLinq」のリリースとアーキテクチャ解説&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2025-05-05&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;&lt;a href="https://github.com/Cysharp/ZLinq"&gt;ZLinq&lt;/a&gt; v1を先月リリースしました！structとgenericsベースで構築することによりゼロアロケーションを達成しています。またLINQ to Span, LINQ to SIMD, LINQ to Tree(FileSystem, JSON, GameObject, etc.)といった拡張要素と、任意の型のDrop-in replacement Source Generator。そして.NET Standard 2.0, Unity, Godotなどの多くのプラットフォームサポートまで含めた大型のライブラリとなっています！現在GitHub Starsも2000を超えました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/ZLinq"&gt;https://github.com/Cysharp/ZLinq&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;structベースのLINQそのものは珍しいものではなく、昔から多くの実装が挑戦してきました。しかし、真に実用的と言えるものはこれまでありませんでした。極度なアセンブリサイズの肥大化、オペレーターの網羅の不足、最適化不足で性能が劣るなど、実験的な代物を抜け切れていないものばかりでした。ZLinqでは実用的に使えるものを目指し、.NET 10(Shuffle, RightJoin, LeftJoinなど新しいものも含む)に含まれる全てのメソッドとオーバーロードの100%のカバーと、99%の挙動の互換性の確保、そしてアロケーションだけではなく、SIMD化も含めて、多くのケースにおける性能面で勝てるように実装しました。&lt;/p&gt;
&lt;p&gt;それが出来るのは、そもそも私のLINQ実装の経験はものすごく長くて、2009年4月に&lt;a href="https://github.com/neuecc/linq.js/"&gt;linq.js&lt;/a&gt;というJavaScript用のLINQ to Objectsライブラリを公開しています(linq.jsは現在もForkした人が今もメンテナンスされているようです、素晴らしい！)。他にもUnityで広く使われているReactive Extensionsライブラリ&lt;a href="https://github.com/neuecc/UniRx"&gt;UniRx&lt;/a&gt;を実装し、直近ではそれの進化版である&lt;a href="https://github.com/Cysharp/R3"&gt;R3&lt;/a&gt;を公開したばかりです。バリエーションとしても&lt;a href="https://assetstore.unity.com/packages/tools/integration/linq-to-gameobject-24256"&gt;LINQ to GameObject&lt;/a&gt;、&lt;a href="https://github.com/neuecc/LINQ-to-BigQuery"&gt;LINQ to BigQuery&lt;/a&gt;、&lt;a href="https://github.com/Cysharp/SimdLinq/"&gt;SimdLinq&lt;/a&gt;といったものを作っていました。これらに、ゼロアロケーション関連ライブラリ(&lt;a href="https://github.com/Cysharp/ZString"&gt;ZString&lt;/a&gt;, &lt;a href="https://github.com/Cysharp/ZLogger"&gt;ZLogger&lt;/a&gt;)やハイパフォーマンスシリアライザー(&lt;a href="https://github.com/MessagePack-CSharp/MessagePack-CSharp/"&gt;MessagePack-CSharp&lt;/a&gt;, &lt;a href="https://github.com/Cysharp/MemoryPack"&gt;MemoryPack&lt;/a&gt;)の知見を掛け合わせることで、標準ライブラリの上位互換という野心的目標を達成できました。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/Cysharp/ZLinq/raw/main/img/benchmarkhead.jpg" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;これはシンプルなベンチマークで、Where, Where.Take, Where.Take.Selectとメソッドチェーンを重ねれば重ねるほど、通常はアロケーションが増えていきますがZLinqはずっとゼロです。&lt;/p&gt;
&lt;p&gt;性能は元のソース、個数、値の型、そしてメソッドの繋げ方によって変わってきます。多くのケースで性能面で有利なことを確認するために、ZLinqでは様々なケースのベンチマークを用意し、GitHub Actions上で走らせています。&lt;a href="https://github.com/Cysharp/ZLinq/actions/workflows/benchmark.yaml"&gt;ZLinq/actions/Benchmark&lt;/a&gt;。構造上どうしても負けてしまうケースも存在はするのですが、現実的なケースではほとんど勝っています。&lt;/p&gt;
&lt;p&gt;ベンチマーク上極端に差が出るものでいえば、シンプルにSelectを複数回繰り返したものは、SystemLinqもZLinqも特殊な最適化が入っていないケースになりますが、大きな性能差が出ています。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/20250505_1.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;シンプルなケースでは、DistinctやOrderByなど中間バッファを必要とするものは、積極的なプーリングによりアロケーションを大きく抑えているため、差が大きくなります(ZLinqは原則&lt;code&gt;ref strcut&lt;/code&gt;であり短寿命が期待できるため、プーリング利用はややアグレッシブにしています)。例えばこのベンチマークはDistinctです。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/20250505_2.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;LINQはメソッド呼び出しのパターンにより特殊な最適化がかかるなど、アロケーションを抑えるだけでは性能面で常に勝てるわけではありません。そうしてオペレーターの繋がりによる最適化に関しても、これは.NET 9で最適化されたパターンとして&lt;a href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/"&gt;Performance Improvements in .NET 9&lt;/a&gt;で紹介されている例ですが、ZLinqではそれらの最適化を全て実装し、より高いパフォーマンスを引き出しています。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/20250505_3.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;ZLinqの良いところとして、これらLINQの進化による最適化の恩恵を、最新の.NETだけではなく、全ての世代の.NET(.NET Frameworkも含む)が得られることでもあります。&lt;/p&gt;
&lt;p&gt;利用法はシンプルに、&lt;code&gt;AsValueEnumerable()&lt;/code&gt;呼び出しを追加するだけです。オペレーターに関しては100%網羅しているので、既存コードからの置き換えも全て問題なくコンパイルが通り、動作します。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;using ZLinq;

var seq = source
    .AsValueEnumerable() // only add this line
    .Where(x =&amp;gt; x % 2 == 0)
    .Select(x =&amp;gt; x * 3);

foreach (var item in seq) { }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ZLinqでは挙動の互換性を保証するために、dotnet/runtimeのSystem.Linq.Testsを移植して &lt;a href="https://github.com/Cysharp/ZLinq/tree/main/tests/System.Linq.Tests"&gt;ZLinq/System.Linq.Tests&lt;/a&gt; 常に走らせています。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/Cysharp/ZLinq/raw/main/img/testrun.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;9000件のテストケースのカバーにより、動作を保証しています（Skipしているケースはref structであるため、同一テストコードを動かせない場合によるものなど）&lt;/p&gt;
&lt;p&gt;また、 &lt;code&gt;AsValueEnumerable()&lt;/code&gt; すら省略したDrop-In Replacementを任意で有効化するSource Generatorも提供しています。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;[assembly: ZLinq.ZLinqDropInAttribute(&amp;quot;&amp;quot;, ZLinq.DropInGenerateTypes.Everything)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://github.com/Cysharp/ZLinq/raw/main/img/dropin.jpg" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;この仕組みにより、Drop-In Replacementの範囲を自由にコントロールすることができます。&lt;code&gt;ZLinq/System.Linq.Tests&lt;/code&gt;自体がDrop-In Replacementにより、既存テストコードを変えずにZLinqで動作するようになっています。&lt;/p&gt;
&lt;h2&gt;ValueEnumerableのアーキテクチャと最適化&lt;/h2&gt;
&lt;p&gt;使い方などはReadMeを参照してもらえればいいので、ここでは最適化の話を深堀します。ただたんなるシーケンスを遅延実行するだけ、ではないところが、アーキテクチャ上の特色であり、他の言語のコレクション処理ライブラリと比べても、多くの工夫が詰まっています。&lt;/p&gt;
&lt;p&gt;連鎖のベースとなる&lt;code&gt;ValueEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;の定義はこうなっています。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;public readonly ref struct ValueEnumerable&amp;lt;TEnumerator, T&amp;gt;(TEnumerator enumerator)
    where TEnumerator : struct, IValueEnumerator&amp;lt;T&amp;gt;, allows ref struct // allows ref structは.NET 9以上の場合のみ
{
    public readonly TEnumerator Enumerator = enumerator;
}

public interface IValueEnumerator&amp;lt;T&amp;gt; : IDisposable
{
    bool TryGetNext(out T current); // as MoveNext + Current

    // Optimization helper
    bool TryGetNonEnumeratedCount(out int count);
    bool TryGetSpan(out ReadOnlySpan&amp;lt;T&amp;gt; span);
    bool TryCopyTo(scoped Span&amp;lt;T&amp;gt; destination, Index offset);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これを基にして、例えばWhereなどのオペレーターはこうした連鎖が続きます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;public static ValueEnumerable&amp;lt;Where&amp;lt;TEnumerator, TSource&amp;gt;, TSource&amp;gt; Where&amp;lt;TEnumerator, TSource&amp;gt;(this ValueEnumerable&amp;lt;TEnumerator, TSource&amp;gt; source, Func&amp;lt;TSource, Boolean&amp;gt; predicate)
    where TEnumerator : struct, IValueEnumerator&amp;lt;TSource&amp;gt;, allows ref struct
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;IValueEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;ではなくてこのようなアプローチを取ったのは、&lt;code&gt;(this TEnumerable source) where TEnumerable : struct, IValueEnumerable&amp;lt;TSource&amp;gt;&lt;/code&gt;のような定義にすると、&lt;code&gt;TSource&lt;/code&gt;への型推論が効かなくなります。これはC#が型引数の制約からは型推論をしないという言語仕様上の制限(&lt;a href="https://github.com/dotnet/csharplang/discussions/6930"&gt;dotnet/csharplang#6930&lt;/a&gt;)があるためで、もしそのような定義のまま実装をすると、インスタンスメソッドとして大量の組み合わせを定義することになります。それをやったのが&lt;a href="https://github.com/kevin-montrose/LinqAF"&gt;LinqAF&lt;/a&gt;であり、その結果&lt;a href="https://kevinmontrose.com/2018/01/17/linqaf-replacing-linq-and-not-allocating/"&gt;100,000+ methods and massive assembly sizes&lt;/a&gt;ということで、あまり良い結果をもたらしていません。&lt;/p&gt;
&lt;p&gt;LINQにおいては実装は全て&lt;code&gt;IValueEnumerator&amp;lt;T&amp;gt;&lt;/code&gt;側にあり、また、全てのEnumeratorはstructのため、&lt;code&gt;GetEnumerator()&lt;/code&gt;ではなくて、共通で&lt;code&gt;Enumerator&lt;/code&gt;のコピー渡しするだけで、それぞれのEnumeratorが独立したステートで処理できることに気付いたので、&lt;code&gt;IValueEnumerator&amp;lt;T&amp;gt;&lt;/code&gt;を&lt;code&gt;ValueEnumerable&amp;lt;TEnumerator, T&amp;gt;&lt;/code&gt;でラップするだけ、という構成に最終的になりました。これにより型が制約側ではなくて型宣言側に現れるので、型推論での問題もありません。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TryGetNext&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;次にイテレートの本体であるMoveNextについて詳しく見ていきましょう。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;// Traditional interface
public interface IEnumerator&amp;lt;out T&amp;gt; : IDisposable
{
    bool MoveNext();
    T Current { get; }
}

// iterate example
while (e.MoveNext())
{
    var item = e.Current; // invoke get_Current()
}

// ZLinq interface
public interface IValueEnumerator&amp;lt;T&amp;gt; : IDisposable
{
    bool TryGetNext(out T current);
}

// iterate example
while (e.TryGetNext(out var item))
{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C#の &lt;code&gt;foreach&lt;/code&gt; は &lt;code&gt;MoveNext() + Current&lt;/code&gt; に展開されるわけですが、問題が二点あります。一つはメソッド呼び出し回数で、イテレート毎にMoveNextとget_Currentの2回必要です。もう一つはCurrentのために、変数を保持する必要があること。そこで、それらを&lt;code&gt;bool TryGetNext(out T current)&lt;/code&gt;にまとめました。これによりメソッド呼び出し回数が一度で済みパフォーマンス上有利です。&lt;/p&gt;
&lt;p&gt;なお、この &lt;code&gt;bool TryGetNext(out T current)&lt;/code&gt; 方式は、例えば&lt;a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html"&gt;Rustのイテレーター&lt;/a&gt;で採用されています。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-Rust"&gt;pub trait Iterator {
    type Item;
    // Required method
    fn next(&amp;amp;mut self) -&amp;gt; Option&amp;lt;Self::Item&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;変数の保持に関してはピンとこないと思うので、例としてSelectの実装を見てください。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;public sealed class LinqSelect&amp;lt;TSource, TResult&amp;gt;(IEnumerator&amp;lt;TSource&amp;gt; source, Func&amp;lt;TSource, TResult&amp;gt; selector) : IEnumerator&amp;lt;TResult&amp;gt;
{
    // フィールドが3つ
    IEnumerator&amp;lt;TSource&amp;gt; source = source;
    Func&amp;lt;TSource, TResult&amp;gt; selector = selector;
    TResult current = default!;

    public TResult Current =&amp;gt; current;

    public bool MoveNext()
    {
        if (source.MoveNext())
        {
            current = selector(source.Current);
            return true;
        }

        return false;
    }
}

public ref struct ZLinqSelect&amp;lt;TEnumerator, TSource, TResult&amp;gt;(TEnumerator source, Func&amp;lt;TSource, TResult&amp;gt; selector) : IValueEnumerator&amp;lt;TResult&amp;gt;
    where TEnumerator : struct, IValueEnumerator&amp;lt;TSource&amp;gt;, allows ref struct
{
    // フィールドが2つ
    TEnumerator source = source;
    Func&amp;lt;TSource, TResult&amp;gt; selector = selector;

    public bool TryGetNext(out TResult current)
    {
        if (source.TryGetNext(out var value))
        {
            current = selector(value);
            return true;
        }

        current = default!;
        return false;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;IEnumerator&amp;lt;T&amp;gt;&lt;/code&gt;は&lt;code&gt;MoveNext()&lt;/code&gt;で進めて&lt;code&gt;Current&lt;/code&gt;で返す、という都合上、&lt;code&gt;Current&lt;/code&gt;のフィールドが必要です。ところがZLinqでは進めると同時に値を返すため、フィールドに保持する必要がありません。これは、全体が&lt;code&gt;struct&lt;/code&gt;ベースで構築されているZLinqではかなり大きな違いがあります。ZLinqではメソッドチェーンの度に、以前のstructを丸ごと抱える(&lt;code&gt;TEnumerator&lt;/code&gt;がstruct)構造になるため、メソッドチェーンを重ねる度に構造体のサイズが肥大化していきます。常識的な範囲内でメソッドチェーンを重ねる限りは、パフォーマンス上も問題にはなっていなかったのですが、それでも小さければ小さいほどコピーコストが小さくなり性能面で有利にはなります。1バイトでも構造体を小さくする、ためにも&lt;code&gt;TryGetNext&lt;/code&gt;の採用は必然でした。&lt;/p&gt;
&lt;p&gt;TryGetNextの欠点は、共変・反変をサポートできないことです。ただし私は、そもそもイテレーターや配列から共変・反変のサポートは撤廃すべきだと思っています。&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;との相性が悪いため、メリット・デメリットを天秤にかけると、時代遅れの概念だと言えます。具体例を出すと、配列のSpan化は失敗する可能性があり、それはコンパイル時には検出できず実行時エラーとなります。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;// ジェネリクスの変性によりDerived[]をBase[]で受け取る。
Base[] array = new Derived[] { new Derived(), new Derived() };

// その場合、Span&amp;lt;T&amp;gt;へのキャストやAsSpan()は実行時エラーになる！
// System.ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array.
Span&amp;lt;Base&amp;gt; foo = array;

class Base;
class Derived : Base;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;以前に追加された機能のため、もうどうにもならないとは思いますが、現代の.NETはあらゆるところでSpanが活用されるようになっているので、それが実行時エラーになる可能性をはらんでいる時点で、使い物にならないと考えてもいいはずです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TryGetNonEnumeratedCount / TryGetSpan / TryCopyTo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全てを愚直に列挙するだけだと、パフォーマンスは最大化されません。例えばToArrayするときに、もしサイズの変動がないなら(&lt;code&gt;array.Select().ToArray()&lt;/code&gt;)、&lt;code&gt;new T[count]&lt;/code&gt;のように固定長配列を作ることができます。SystemLinqでも、そうした最適化を実現するために、内部的には&lt;code&gt;Iterator&amp;lt;T&amp;gt;&lt;/code&gt;型が使われているのですが、引数は&lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;のため、必ず &lt;code&gt;if (source is Iterator&amp;lt;TSource&amp;gt; iterator)&lt;/code&gt; のようなコードが必要になっています。&lt;/p&gt;
&lt;p&gt;ZLinqでは最初からLINQのための定義を前提にできるため、すべて織り込み済みで用意しています。ただし、むやみやたらに増やすのはアセンブリサイズの肥大化を招くため、必要最小限の定義で、最大限の効果を生み出すように調整したのが、この3つのメソッドとなっています。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TryGetNonEnumeratedCount(out int count)&lt;/code&gt;は、元のソースが有限の個数であり、途中にフィルタリング系メソッド(WhereやDistinctなど。TakeやSkipは算出可能なため含まない)が挟まらない場合は成功します。ToArrayなどのほか、OrderByやShuffleなど中間バッファが必要な時に効果が出るケースもあります。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TryGetSpan(out ReadOnlySpan&amp;lt;T&amp;gt; span)&lt;/code&gt;は、元ソースが連続的なメモリとして取得できる場合には、オペレーターによってはSIMDが適用されて劇的なパフォーマンス向上に繋がったり、Spanによるループ処理によって集計パフォーマンスが高まるなど、性能面で大きな違いをもたらす可能性があります。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TryCopyTo(scoped Span&amp;lt;T&amp;gt; destination, Index offset)&lt;/code&gt;は内部イテレーターによってパフォーマンスを向上させる仕組みです。外部イテレーターと内部イテレーターについて説明すると、例えば&lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;は&lt;code&gt;foreach&lt;/code&gt;と&lt;code&gt;ForEach&lt;/code&gt;の両方が選べます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;// external iterator
foreach (var item in list) { Do(item); }

// internal iterator
list.ForEach(Do);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;見た目は似ていますが、性能面で違いがあります。foreachは素直な構文で書けている。ForEachはデリゲート渡し。処理の実体まで分解すると&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;// external iterator
List&amp;lt;T&amp;gt;.Enumerator e = list.GetEnumerator();
while (e.MoveNext())
{
    var item = e.Current;
    Do(item);
}

// internal iterator
for (int i = 0; i &amp;lt; _size; i++)
{
    action(_items[i]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これはデリゲート呼び出し(+デリゲート生成アロケーション)のオーバーヘッド vs イテレーターのMoveNext + Current呼び出しの対決になっていて、イテレート速度自体は内部イテレーターのほうが速い。この場合デリゲート呼び出しのほうが軽量な場合があり、ベンチマーク的に内部イテレーターのほうが有利な可能性があります。&lt;/p&gt;
&lt;p&gt;もちろん、ケースバイケースであることと、ラムダ式にキャプチャが発生したり、普通の制御構文が使えない(continueなど)ことから、私としては&lt;code&gt;ForEach&lt;/code&gt;は使うべきではないし、拡張メソッドで&lt;code&gt;ForEach&lt;/code&gt;のようなものを独自定義すべきではない、とも思っていますが、原理的にはこのような違いが存在します。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TryCopyTo(scoped Span&amp;lt;T&amp;gt; destination, Index offset)&lt;/code&gt;は、デリゲートではなく&lt;code&gt;Span&lt;/code&gt;を受け取ることで限定的に内部イテレーター化しました。&lt;/p&gt;
&lt;p&gt;これもSelectを例に出すと、ToArrayの場合にCountが取れているとSpanを渡して内部イテレーターで処理します。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;public ref struct Select
{
    public bool TryCopyTo(Span&amp;lt;TResult&amp;gt; destination, Index offset)
    {
        if (source.TryGetSpan(out var span))
        {
            if (EnumeratorHelper.TryGetSlice(span, offset, destination.Length, out var slice))
            {
                // loop inlining
                for (var i = 0; i &amp;lt; slice.Length; i++)
                {
                    destination[i] = selector(slice[i]);
                }
                return true;
            }
        }
        return false;
    }
}

// ToArray
if (enumerator.TryGetNonEnumeratedCount(out var count))
{
    var array = GC.AllocateUninitializedArray&amp;lt;TSource&amp;gt;(count);

    // try internal iterator
    if (enumerator.TryCopyTo(array.AsSpan(), 0))
    {
        return array;
    }

    // otherwise, use external iterator
    var i = 0;
    while (enumerator.TryGetNext(out var item))
    {
        array[i] = item;
        i++;
    }

    return array;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のように、SelectはSpanは作れませんが、元ソースがSpanを作れるなら、内部イテレーターとして処理することでループ処理を高速化することが可能です。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TryCopyTo&lt;/code&gt;の定義は普通の&lt;code&gt;CopyTo&lt;/code&gt;と違って、&lt;code&gt;Index offset&lt;/code&gt;を持っています。また、destinationはソースサイズよりも小さいことを許しています（通常の.NETのCopyToはdestinationが小さいと失敗する)。これによって、destinationのサイズが1の場合、IndexによってElementAtが表現できます。そして0ならFirstだし^1の場合はLastになります。&lt;code&gt;IValueEnumerator&amp;lt;T&amp;gt;&lt;/code&gt;自体に&lt;code&gt;First&lt;/code&gt;, &lt;code&gt;Last&lt;/code&gt;, &lt;code&gt;ElementAt&lt;/code&gt;を持たせると、クラス定義として無駄が多くなってしまいますが（アセンブリサイズにも影響が出る）、小さいdestinationとIndexを持たせることにより、一つのメソッドでより多くの最適化ケースをカバーできるようになりました。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;public static TSource ElementAt&amp;lt;TEnumerator, TSource&amp;gt;(this ValueEnumerable&amp;lt;TEnumerator, TSource&amp;gt; source, Index index)
    where TEnumerator : struct, IValueEnumerator&amp;lt;TSource&amp;gt;, allows ref struct
{
    using var enumerator = source.Enumerator;
    var value = default(TSource)!;
    var span = new Span&amp;lt;T&amp;gt;(ref value); // create single span
    if (enumerator.TryCopyTo(span, index))
    {
        return value;
    }
    // else...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ところで、このTryGetNextや内部イテレーターに関しては、2007年の時点で &lt;a href="https://nyaruru.hatenablog.com/entry/20070818/p1"&gt;https://nyaruru.hatenablog.com/entry/20070818/p1&lt;/a&gt; で紹介されていました。この記事はずっと頭に残っていて、ようやくこうして20年経って理屈通りの実現ができました。という点でも少し感慨深いです。2008年前後はLINQ登場前後ということで、このあたりの話がアツかった時代なんですよねー。&lt;/p&gt;
&lt;h2&gt;LINQ to Span&lt;/h2&gt;
&lt;p&gt;ZLinqは .NET 9 以上であれば、&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;や&lt;code&gt;ReadOnlySpan&amp;lt;T&amp;gt;&lt;/code&gt;に対しても、全てのLINQオペレーターを繋げることができます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;using ZLinq;

// Can also be applied to Span (only in .NET 9/C# 13 environments that support allows ref struct)
Span&amp;lt;int&amp;gt; span = stackalloc int[5] { 1, 2, 3, 4, 5 };
var seq1 = span.AsValueEnumerable().Select(x =&amp;gt; x * x);

// If enables Drop-in replacement, you can call LINQ operator directly.
var seq2 = span.Select(x =&amp;gt; x);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Span対応のLINQを謳ったライブラリも、世の中には多少ありますが、それらは&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;にだけ拡張メソッドを定義する、といったようなものであり、汎用的な仕組みではありませんでした。網羅されるオペレーターも制約があり、一部のものに限られていました。それは言語的にも&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;をジェネリクス引数として受け取ることができなかったためで、汎用的に処理できるようになったのは .NET 9で&lt;code&gt;allows ref struct&lt;/code&gt;が登場してくれたおかげです。&lt;/p&gt;
&lt;p&gt;ZLinqでは&lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;と&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;に何の区別もありません、全て平等に取り扱われます。&lt;/p&gt;
&lt;p&gt;ただし、&lt;code&gt;allows ref struct&lt;/code&gt;の言語/ランタイムサポートが必要なため、&lt;code&gt;ZLinq自体は&lt;/code&gt;.NET Standard 2.0以上の全ての.NETをサポートしていますが、&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;対応に関してのみ.NET 9以上限定の機能となっています。また、これにより.NET 9以上の場合は、全てのオペレーターが&lt;code&gt;ref struct&lt;/code&gt;になっている、という違いがあります。&lt;/p&gt;
&lt;h2&gt;LINQ to SIMD&lt;/h2&gt;
&lt;p&gt;System.Linqでは、一部の集計メソッドがSIMDによって高速化されています。例えば一部のプリミティブ型の配列に直接SumやMaxを呼び出すと高速化されています。これらの呼び出しはforで処理するよりも遥かに高速化されます。とはいえ、&lt;code&gt;IEnumerbale&amp;lt;T&amp;gt;&lt;/code&gt;がベースであるため、適用可能な型が限定的であるなどの欠点を感じています。ZLinqでは&lt;code&gt;IValueEnumeartor.TryGetSpan&lt;/code&gt;によって&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;が取得できる場合が対象となるコレクションとなるため、より汎用的になっています（もちろん&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;に適用することもできます)。&lt;/p&gt;
&lt;p&gt;対応するメソッドは以下のようなものになっています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Range&lt;/strong&gt; to ToArray/ToList/CopyTo/etc...&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeat&lt;/strong&gt; for &lt;code&gt;unmanaged struct&lt;/code&gt; and &lt;code&gt;size is power of 2&lt;/code&gt; to ToArray/ToList/CopyTo/etc...&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sum&lt;/strong&gt; for &lt;code&gt;sbyte&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;ushort&lt;/code&gt;, &lt;code&gt;uint&lt;/code&gt;, &lt;code&gt;ulong&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SumUnchecked&lt;/strong&gt; for &lt;code&gt;sbyte&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;ushort&lt;/code&gt;, &lt;code&gt;uint&lt;/code&gt;, &lt;code&gt;ulong&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Average&lt;/strong&gt; for &lt;code&gt;sbyte&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;ushort&lt;/code&gt;, &lt;code&gt;uint&lt;/code&gt;, &lt;code&gt;ulong&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Max&lt;/strong&gt; for &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;sbyte&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;ushort&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;uint&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;ulong&lt;/code&gt;, &lt;code&gt;nint&lt;/code&gt;, &lt;code&gt;nuint&lt;/code&gt;, &lt;code&gt;Int128&lt;/code&gt;, &lt;code&gt;UInt128&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Min&lt;/strong&gt; for &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;sbyte&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;ushort&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;uint&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;ulong&lt;/code&gt;, &lt;code&gt;nint&lt;/code&gt;, &lt;code&gt;nuint&lt;/code&gt;, &lt;code&gt;Int128&lt;/code&gt;, &lt;code&gt;UInt128&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contains&lt;/strong&gt; for &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;sbyte&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;ushort&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;uint&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;ulong&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;char&lt;/code&gt;, &lt;code&gt;nint&lt;/code&gt;, &lt;code&gt;nuint&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SequenceEqual&lt;/strong&gt; for &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;sbyte&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;ushort&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;uint&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;ulong&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;char&lt;/code&gt;, &lt;code&gt;nint&lt;/code&gt;, &lt;code&gt;nuint&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;Sum&lt;/code&gt;はオーバーフローをチェックします。これは処理においてオーバーヘッドとなっているため、独自に&lt;code&gt;SumUnchecked&lt;/code&gt;というメソッドも追加しています。性能差は以下のようになり、Uncheckedのほうがより高速です。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/20250505_4.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;これらメソッドは条件がマッチした場合に暗黙的に適用されるということであり、SIMDを狙って適用させるには内部パイプラインへの理解が必要とされています。そこで&lt;code&gt;T[]&lt;/code&gt; or &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code&gt;ReadOnlySpan&amp;lt;T&amp;gt;&lt;/code&gt;には&lt;code&gt;.AsVectorizable()&lt;/code&gt;というメソッドを用意しました。SIMD適用可能な&lt;code&gt;Sum&lt;/code&gt;, &lt;code&gt;SumUnchecked&lt;/code&gt;, &lt;code&gt;Average&lt;/code&gt;, &lt;code&gt;Max&lt;/code&gt;, &lt;code&gt;Min&lt;/code&gt;, &lt;code&gt;Contains&lt;/code&gt;, and &lt;code&gt;SequenceEqual&lt;/code&gt;を明示的に呼び出すことができます（ただし&lt;code&gt;Vector.IsHardwareAccelerated &amp;amp;&amp;amp; Vector&amp;lt;T&amp;gt;.IsSupported&lt;/code&gt;ではない場合は通常の処理にフォールバックされるため、必ずしもSIMDが適用されることを保証するわけではありません）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;int[]&lt;/code&gt; or &lt;code&gt;Span&amp;lt;int&amp;gt;&lt;/code&gt;には&lt;code&gt;VectorizedFillRange&lt;/code&gt;というメソッドが追加されます。これは&lt;code&gt;ValueEunmerable.Range().CopyTo()&lt;/code&gt;と同じ処理で、連番で埋める処理がSIMDで高速化されます。連番が必要になる局面で、forで埋めるよりも遥かに高速なので、覚えておくといいかもしれません。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/20250505_5.png" alt="" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vectorizable Methods&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SIMDによるループ処理を手書きするのは、慣れが必要で少し手間がいります。そこでFuncを引数に与えることでカジュアルに使えるヘルパーをいくつか用意しました。デリゲートを経由するオーバーヘッドが発生するためインラインで書くよりもパフォーマンスは劣りますが、カジュアルにSIMD処理できるという点では便利かもしれません。これらは引数に&lt;code&gt;Func&amp;lt;Vector&amp;lt;T&amp;gt;, Vector&amp;lt;T&amp;gt;&amp;gt; vectorFunc&lt;/code&gt;と&lt;code&gt;Func&amp;lt;T, T&amp;gt; func&lt;/code&gt;を受け取り、ループの埋められるところまで&lt;code&gt;Vector&amp;lt;T&amp;gt;&lt;/code&gt;で処理し、残りを&lt;code&gt;Func&amp;lt;T&amp;gt;&lt;/code&gt;で処理します。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;T[]&lt;/code&gt;, &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;には&lt;code&gt;VectorizedUpdate&lt;/code&gt;というメソッドが用意されています。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;using ZLinq.Simd; // needs using

int[] source = Enumerable.Range(0, 10000).ToArray();

[Benchmark]
public void For()
{
    for (int i = 0; i &amp;lt; source.Length; i++)
    {
        source[i] = source[i] * 10;
    }
}

[Benchmark]
public void VectorizedUpdate()
{
    // arg1: Vector&amp;lt;int&amp;gt; =&amp;gt; Vector&amp;lt;int&amp;gt;
    // arg2: int =&amp;gt; int
    source.VectorizedUpdate(static x =&amp;gt; x * 10, static x =&amp;gt; x * 10);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/article_img/20250505_6.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;forよりも高速、ですが、パフォーマンスはマシン環境やサイズによって変わるので、盲目的に使うのではなくて、都度検証することをお薦めします。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AsVectorizable()&lt;/code&gt;には&lt;code&gt;Aggregate&lt;/code&gt;, &lt;code&gt;All&lt;/code&gt;, &lt;code&gt;Any&lt;/code&gt;, &lt;code&gt;Count&lt;/code&gt;, &lt;code&gt;Select&lt;/code&gt;, and &lt;code&gt;Zip&lt;/code&gt;が用意されています。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;source.AsVectorizable().Aggregate((x, y) =&amp;gt; Vector.Min(x, y), (x, y) =&amp;gt; Math.Min(x, y))
source.AsVectorizable().All(x =&amp;gt; Vector.GreaterThanAll(x, new(5000)), x =&amp;gt; x &amp;gt; 5000);
source.AsVectorizable().Any(x =&amp;gt; Vector.LessThanAll(x, new(5000)), x =&amp;gt; x &amp;lt; 5000);
source.AsVectorizable().Count(x =&amp;gt; Vector.GreaterThan(x, new(5000)), x =&amp;gt; x &amp;gt; 5000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;パフォーマンスは、データ次第ではありますが一例としてはCountで、このぐらいの差が出ることもあります。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/20250505_7.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Select&lt;/code&gt;, &lt;code&gt;Zip&lt;/code&gt;に関しては、後続に&lt;code&gt;ToArray&lt;/code&gt;か&lt;code&gt;CopyTo&lt;/code&gt;を選びます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;// Select
source.AsVectorizable().Select(x =&amp;gt; x * 3, x =&amp;gt; x * 3).ToArray();
source.AsVectorizable().Select(x =&amp;gt; x * 3, x =&amp;gt; x * 3).CopyTo(destination);

// Zip2
array1.AsVectorizable().Zip(array2, (x, y) =&amp;gt; x + y, (x, y) =&amp;gt; x + y).CopyTo(destination);
array1.AsVectorizable().Zip(array2, (x, y) =&amp;gt; x + y, (x, y) =&amp;gt; x + y).ToArray();

// Zip3
array1.AsVectorizable().Zip(array2, array3, (x, y, z) =&amp;gt; x + y + z, (x, y, z) =&amp;gt; x + y + z).CopyTo(destination);
array1.AsVectorizable().Zip(array2, array3, (x, y, z) =&amp;gt; x + y + z, (x, y, z) =&amp;gt; x + y + z).ToArray();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zipなんかは結構面白い＆ちゃんと高速なので、使いどころあるかもしれません(2つのVec3のマージとか)。&lt;/p&gt;
&lt;p&gt;&lt;img src="/article_img/20250505_8.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;LINQ to Tree&lt;/h2&gt;
&lt;p&gt;皆さんLINQ to XMLを使ったことはありますか? LINQの登場した2008年は、まだまだXML全盛期で、LINQ to XMLのあまりにも使いやすいAPIには衝撃を受けました。しかし、すっかり時代はJSONでありLINQ to XMLを使うことはすっかりなくなりました。&lt;/p&gt;
&lt;p&gt;しかし、LINQ to XMLの良さというのは、ツリー構造に対するLINQ的操作のリファレンスデザインだと捉えることができます。ツリー構造がLINQになる、そのガイドライン。LINQ to Objectsと非常に相性の良い探索の抽象化。その代表例がRoslynのSyntaxTreeに対する操作で、AnalyzerやSource Generatorを書くのにDescendantsなどのメソッドを日常的に利用しています。&lt;/p&gt;
&lt;p&gt;そこでZLinqはそのコンセプトを拡張し、ツリー構造に対して汎用的に &lt;code&gt;Ancestors&lt;/code&gt;, &lt;code&gt;Children&lt;/code&gt;, &lt;code&gt;Descendants&lt;/code&gt;, &lt;code&gt;BeforeSelf&lt;/code&gt;, and &lt;code&gt;AfterSelf&lt;/code&gt; が適用できるインターフェイスを定義しました。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/Cysharp/ZLinq/raw/main/img/axis.jpg" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;これはUnityのGameObjectへの走査の図ですが、標準でFileSystem(DirectoryTreeはツリー構造)やJSON(System.Text.JsonのJsonNodeに対してLINQ to XML的な操作を可能にする)を用意しています。もちろん、任意にインターフェイスを実装することで追加することもできます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;public interface ITraverser&amp;lt;TTraverser, T&amp;gt; : IDisposable
    where TTraverser : struct, ITraverser&amp;lt;TTraverser, T&amp;gt; // self
{
    T Origin { get; }
    TTraverser ConvertToTraverser(T next); // for Descendants
    bool TryGetHasChild(out bool hasChild); // optional: optimize use for Descendants
    bool TryGetChildCount(out int count);   // optional: optimize use for Children
    bool TryGetParent(out T parent); // for Ancestors
    bool TryGetNextChild(out T child); // for Children | Descendants
    bool TryGetNextSibling(out T next); // for AfterSelf
    bool TryGetPreviousSibling(out T previous); // BeforeSelf
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例えばJSONに対しては&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;var json = JsonNode.Parse(&amp;quot;&amp;quot;&amp;quot;
// snip...
&amp;quot;&amp;quot;&amp;quot;);

// JsonNode
var origin = json![&amp;quot;nesting&amp;quot;]![&amp;quot;level1&amp;quot;]![&amp;quot;level2&amp;quot;]!;

// JsonNode axis, Children, Descendants, Anestors, BeforeSelf, AfterSelf and ***Self.
foreach (var item in origin.Descendants().Select(x =&amp;gt; x.Node).OfType&amp;lt;JsonArray&amp;gt;())
{
    // [true, false, true], [&amp;quot;fast&amp;quot;, &amp;quot;accurate&amp;quot;, &amp;quot;balanced&amp;quot;], [1, 1, 2, 3, 5, 8, 13]
    Console.WriteLine(item.ToJsonString(JsonSerializerOptions.Web));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;といったように書くことができます。&lt;/p&gt;
&lt;p&gt;Unityには&lt;code&gt;GameObject&lt;/code&gt;や&lt;code&gt;Transform&lt;/code&gt;、Godotには&lt;code&gt;Node&lt;/code&gt;へのLINQ to Treeを標準で用意しました。アロケーションや走査のパフォーマンスにかなり気を使って書かれているので、手動でループを回すよりも、もしかしたら高速かもしれません。&lt;/p&gt;
&lt;h2&gt;OSSと私&lt;/h2&gt;
&lt;p&gt;ここ数ヶ月で.NET関連のOSSには幾つか事件がありました。名のしれたOSSの商業ライセンス化、など……。私は、&lt;a href="https://github.com/Cysharp"&gt;github/Cysharp&lt;/a&gt;で出しているOSSの数は40を超え、個人やMessagePack organizationなどのものも含めると、総スター数では50000を超えるなど.NET周りのサードパーティーとしては最大規模でのOSS提供者なのではないかと思います。&lt;/p&gt;
&lt;p&gt;商業化、に関しては予定はありません、が、メンテナンスに関しては規模が大きくなってきたため、追いつかなくなっている面が多々あります。OSSが批判を覚悟で商業化を試みるの要因として、メンテナーに対する精神的な負荷というのが大きい（時間に対しての報酬が全く見合っていない）のですが、私も、まぁ、大変です！&lt;/p&gt;
&lt;p&gt;金銭面は置いておいて、お願い事としては、メンテナンスが滞ることがあることは多少受け入れて欲しい！今回のZLinqのような大きなライブラリを仕込んでいる最中は、集中する時間が必要なため、他のライブラリのIssueやPRへの応答が数ヶ月音信不通になります。意識的に全く見ないようにしています、タイトルすら見てません（ダッシュボードや通知のメールなども一切目にしないようにしています）。そうした不義理を働くことで創造的なライブラリを生み出すことができるのだ、これは必要な犠牲なのです……！&lt;/p&gt;
&lt;p&gt;また、そうじゃなくても、面倒見てるライブラリの数が多すぎるのでローテートでも数ヶ月の遅延が発生することは、あります。もうこれは絶対的なマンパワーが不足しているため、しょうがないじゃないですかー、というわけで、そのしょうがないを受け入れて、ちょっと返事が遅れるだけでthis library is dead的なこと言わないで欲しいなあ、というのが正直なところです！言われると辛い！なるべく努力はしたいんですが、特に新しいライブラリの創造は時間をめちゃくちゃ取られて大量の遅延が発生して、その遅延が更に遅延を呼んで泥沼になって精神を削っていくのですよー。&lt;/p&gt;
&lt;p&gt;あとはMicrosoft関連でイラッとさせられてモチベーションを削られるとか、この辺はC#関連のOSSあるあるが発生したりしたりしながらも、なるべく末永く続けていきたいとは思っています。&lt;/p&gt;
&lt;p&gt;かなり危機感は持っているので、AIによってどこまで負荷の軽減ができるのか、というところをテーマに、ある程度実験場として色々やっていきたいなあ、と思っています。うまくいけば、よりコアに集中できる環境になってくれるわけですしね。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;ZLinqの構造は最初のプレビュー版公開後のフィードバックで結構変わっていて、&lt;a href="https://github.com/Akeit0"&gt;@Akeit0&lt;/a&gt;さんにはコアとなる&lt;code&gt;ValueEnumerable&amp;lt;TEnumerator, T&amp;gt;&lt;/code&gt;という定義や&lt;code&gt;TryCopyTo&lt;/code&gt;への&lt;code&gt;Index&lt;/code&gt;の追加など、パフォーマンスに重要なコア部分の提案を多く頂きました！また、&lt;a href="https://github.com/filzrev"&gt;@filzrev&lt;/a&gt;さんからは多大なテスト・ベンチマークのインフラストラクチャーを提供してもらいました。互換性確保やパフォーマンス向上は、この貢献がなければ成しえませんでした。お二人には深く感謝します。&lt;/p&gt;
&lt;p&gt;改めて、ゼロアロケーションLINQライブラリというコンセプト自体はそこまで珍しいものでもなく、今までもライブラリが死屍累々と転がっていたわけですが、ZLinqは徹底度合いが違う。経験と知識があるうえで、精神論で気合で、全メソッド実装、テストケースも全部流して完全互換、最適化類もSIMD含めて全部実装する、をやり切ったのが立派なところなのではないかな、と。いや、ほんとこれめっちゃ大変だったのです……。&lt;/p&gt;
&lt;p&gt;タイミングとしても.NET 9/C# 13が、フルセットでやりたいことが全部やれる言語機能となったことは、やる気を後押ししてくれました。と、同時に、Unityや.NET Standard 2.0対応も大事にできたのもいいことです。&lt;/p&gt;
&lt;p&gt;ただのゼロアロケーションLINQというだけではなく、LINQ to Treeはお気に入りの機能なので是非使ってみて欲しいですね……！そもそもに元々は、10年前に作っていたLINQ to GameObjectをモダン化しよう、というのが出発点でした。昔のコードだったのでかなりベタ書きだったのですが、もうちょっと抽象化したほうがいいかな、と弄っているうちに、だったらゼロアロケーションLINQとしての抽象化まで進化させてしまったほうがいいのでは、という思いつきに至ったのでした。&lt;/p&gt;
&lt;p&gt;ところで、LINQのパフォーマンスのネックの一つとしてはデリゲートがあり、一部のライブラリはstructでFuncのようなものを模写するValueDelegateというアプローチがあるのですが、それはあえて採用していません。というのも、それらの定義はかなり手間なので、現実的にはやってられないはずです。そこまでやるなら普通にインラインで書いたほうがマシなので、LINQでValueDelegate構造を使う意味はありません。そんなベンチマークハックのためだけに内部構造の複雑化とアセンブリサイズの肥大化を招くのは無駄なので、System.Linqと互換のFuncのみを受け入れるスタイルにしています。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Cysharp/R3"&gt;R3&lt;/a&gt;が.NET標準のSystem.Reactiveを置き換えるものという野心的ライブラリでしたが、System.Linqの置き換えはそれよりも遥かに大きな、あるいは大袈裟すぎる代物なので、採用に抵抗感はあるんじゃないかなー、と思います。ですが、置き換えるだけのメリットは掲示できていると思うので、是非とも試してみてくれると嬉しいです！&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Mon, 05 May 2025 00:00:00 +0900</pubDate>
      <a10:updated>2025-05-05T00:00:00+09:00</a10:updated>
    </item>
    <item>
      <guid isPermaLink="true">https://neue.cc/2024/12/30_year.html</guid>
      <link>https://neue.cc/2024/12/30_year.html</link>
      <title>2024年を振り返る</title>
      <description>&lt;h1 data-pagefind-sort="date:2024-12-30" data-pagefind-meta="published:2024-12-30"&gt;&lt;a href="https://neue.cc/2024/12/30_year.html"&gt;2024年を振り返る&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2024-12-30&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;今年も&lt;a href="https://cysharp.co.jp/"&gt;Cysharp&lt;/a&gt;はちゃんと生存していて良きかな、というわけでサイトが相変わらずペライチなのでそろそろリニューアルしたいと思って幾星霜。&lt;/p&gt;
&lt;p&gt;そんなわけで今年もC#をやりこみ（？）していました……！&lt;/p&gt;
&lt;p&gt;新規:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/R3"&gt;R3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/Claudia"&gt;Claudia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/Utf8StreamReader"&gt;Utf8StreamReader&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大型アップデート&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/ConsoleAppFramework"&gt;ConsoleAppFramework v5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MessagePack-CSharp/MessagePack-CSharp"&gt;MessagePack for C# v3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Cysharp/MasterMemory"&gt;MasterMemory v3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;うーん、十分でしょ！割といつも年の中で浮き沈みはあって、調子でないなあ、ここ数ヶ月ダメだぁ、みたいな気持ちになることが割とあるのですが、振り返ってみれば十分すぎるでしょ！むしろやりすぎでしょ！というわけで、C#最前線キープとしては全く問題ないでしょう。&lt;/p&gt;
&lt;p&gt;ハイライトとしてはやはり年初の&lt;a href="https://neue.cc/2024/02/27_R3.html"&gt;R3 - C#用のReactive Extensionsの新しい現代的再実装&lt;/a&gt;ですかね……！これは、めちゃくちゃ大変でした。物量とかそのものの実装難易度とかもそうなのですが、スタンダードとなっているインターフェイスや仕様を変えるという判断を通しているんですよね。これが、ちゃんと成り立たせられるのか、それで普及させられるのか、という悩みもあり、また、インターフェイスも作りながら割とクルクル最後まで変えながらやってたので、完成して良かったし、1年弱経って、ちゃんと受け入れられているのを見てようやくホッと一息です。Unityにおいても、今年はNuGet化を強烈に推進していったわけですが、なんだかんだで受け入れてもらえってるような気がしますがどうでしょう……？&lt;/p&gt;
&lt;p&gt;Claudiaや、なんかブログに書く機会を逸して書いてない気がするのですがUtf8StreamReaderなんかも中々いい感じではあったと思います。&lt;/p&gt;
&lt;p&gt;そして大型アップデート系はSource Generator祭り。まず&lt;a href="https://neue.cc/2024/06/13_ConsoleAppFramework_v5.html"&gt;ConsoleAppFramework v5 - ゼロオーバーヘッド・Native AOT対応のC#用CLIフレームワーク&lt;/a&gt;は傑作かと！これは自信作ですねー、めちゃくちゃいいものが出来たと思ってます。直近の&lt;a href="https://neue.cc/2024/12/16_ConsoleAppFramewrok_v5_3_0.html"&gt;ConsoleAppFramework v5.3.0 - NuGet参照状況からのメソッド自動生成によるDI統合の強化、など&lt;/a&gt;で完全に仕上がりました。&lt;/p&gt;
&lt;p&gt;MessagePack for C#は長年懸案だったSource Generator化をついに果たしました。そして色々あって共同メンテナーが離脱したことにより、再度主導権が私の方に戻っています。この辺のことは思うところは割とあるのですが、まぁ結果的には良かったかな、と思ってます。再びやれることの幅も広がったので、&lt;a href="https://github.com/Cysharp/MemoryPack"&gt;MemoryPack&lt;/a&gt;ともどもで来年は強化していきたいと思っています。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://neue.cc/2024/12/20_mastermemory_v3.html"&gt;MasterMemory v3 - Source Generator化したC#用の高速な読み込み専用インメモリデータベース&lt;/a&gt;も、ずっとSource Generator化したいと思って2年ぐらい放置していた案件なので、ようやく解消できて嬉しい話ですね。しかもやってたら想像通りにめちゃくちゃDX(Developer Experience)よくなってるので、やっと理想が実現できた、というかむしろ時代がやっと追いついた（なんせこの辺の仕組みはSource Generator以前に構築していたパターンなので）という気持ちです。&lt;/p&gt;
&lt;p&gt;Cysharpの提供しているライブラリから単独コードジェネレーターは消滅して全てSource Generator化し、そして&lt;a href="https://neue.cc/2024/01/15_shareprojectinunity.html"&gt;.NETプロジェクトとUnityプロジェクトのソースコード共有最新手法&lt;/a&gt;でも書いたようにUnityとのコードシェアもかなりやりやすい手法が確立できたので、まさにこれはC#大統一理論元年……！「出来ない」よりは「出来たほうがいい」ので、別に今までのやり方が悪かったとは思いませんが、ようやく理想形に到達できた、という感じではあります。来年初頭にはMagicOnionのMessagePack for C# v3対応を出す予定で、これで全てのパーツが揃います！&lt;/p&gt;
&lt;h2&gt;なんとか of the Year&lt;/h2&gt;
&lt;p&gt;今年一番大きかった変化として、メモ環境に&lt;a href="https://obsidian.md/"&gt;Obsidian&lt;/a&gt;を全面導入したのですが、これは超絶良いですね。Daily Notesの有用性というのをようやく理解しました。有料課金して同期することで更に便利、プラグインもマシマシで便利、まぁ入れすぎてもしょうがないので適度に絞ってますよ、と思ったんですが、意外と結構はいってるかも……。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/user-attachments/assets/f44a7f98-36b6-4699-8ab4-1158d5796f19" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/user-attachments/assets/c4bc0d3e-7707-451b-ba6e-1cff03e31733" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;TODOもObsidianに寄せるようにしていますが、特にTODOプラグインなどは使わずにDaily Notesで表現できるように若干工夫しています（TemplaterでJavaScript書いてDaily Notesの生成時にチェック済みのTODOタスクは自動で消すようにしてる＆未チェックのTODOタスクは引き継ぐようにしている）。TODOアプリは無限に彷徨って毎年違うものに変えてたりするのですが、これが一番手に馴染んでるので決定版ということでいいかなー。&lt;/p&gt;
&lt;p&gt;もう一つ革命的に良かったと思うのは&lt;a href="https://consumer.huawei.com/jp/audio/freeclip/"&gt;HUAWEI FreeClip&lt;/a&gt;。これはとんでもなく良くてビックリした。HUAWEI製品のクオリティの高さにもビビッた。オープンイヤー型のイヤホンなわけですが、付け心地も良いし細かいところも良く出来てるし音質もしっかりしてる。言う事無し。オープンイヤーは、外音取り込みとは耳への圧迫感が自然さが全然違うんですよねえ、これだと1日中付けっぱなしとまではしないけど、割と頻繁に耳につけといて、音を聞くことが増えました。講演動画とか英語のリスニングとか日常生活に流せるといい感じ度が上がります。あとダラダラYouTube見る頻度が相当上がってしまった……。あまり使い分けとかは出来ないタイプの人間なので今まで使っていたAirPods Proはお蔵入りしてFreeClip一本使いになってます。ノイズキャンセリング性能がーというのと真逆なわけですが、外音と混じった音楽も、それはそれで心地よいのでいい感じなので、騒音環境下でもそこまで気にならず使えてる気がします。&lt;/p&gt;
&lt;p&gt;あとは、家のキーボードを&lt;a href="https://www.realforce.co.jp/products/series_rc1.html"&gt;Realforce RC1&lt;/a&gt;に変更しました。HHKBにF1-12キーが追加されたようなレイアウトなわけですが、まず、キーボードにF1-F12は必須なんですよね！Visual Studio的に！あとは、日本語キーボードレイアウトじゃないとダメ人間なので、ずっとコンパクト配列にしたいなあと思いつつも選択肢がなくてなあ、と、テンキーレスぐらいで我慢していたので、満を持しての本命というわけでした。&lt;/p&gt;
&lt;p&gt;それとFnキーとの組み合わせによるハードウェアキーレイアウト変更が柔軟かつ安定性が高いことに気づいた！昔から無変換＋ESDFを十字キーとして使う癖があって、AutoHotKeyなどソフトウェアでフックするやつを使って実現していたのですが、挙動的に不安定（抜けが出たりするのが辛い）なのが気になってました。が、Realforceの設定で無変換と変換をFnキーにしてしまって、Fnキーとの組み合わせでESDFを十字キーにしてしまえば完璧だった……！というわけで現在のレイアウトがこちら。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/user-attachments/assets/4824b421-878d-42ee-8bd2-1c7dcc201a3d" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;そんなにキーボードから手を離さないで全部操作出来ないと！みたいな感じではなく、右手はマウス行きしちゃうので、左手側に詰め込みがちです。とにかくESDFでの十字キー化が安定したのがめっちゃ嬉しい。これ、WSDFじゃないんですか？というところなのですが、主に使うシチュエーションはテキストエディタでの十字移動なので、ESDFはホームポジションから手を動かさずに十字キーになるのがWSDFに比べての圧倒的利点です。それとQAZが空くので、そこにもキーを詰め込めるのも嬉しい。&lt;/p&gt;
&lt;p&gt;というわけでQ, AはHome/End(ちなみに私はHomeめちゃくちゃよく使います)。Z, CにShift + Home/End。XにShift + @(つまり```)。VにWin + V。それと1, 2, 3にはAlt + 1, 2, 3を入れています、というのも私は&lt;a href="https://arc.net/"&gt;Arc&lt;/a&gt;というブラウザを使っているのですが、これのスペースの切り替えがAltになっていて、AltよりもFn（元の無変換）を使うことが多いので、そのまま切り替えられるようにしたほうが便利かな、と。&lt;/p&gt;
&lt;p&gt;そしてFn + 半角/全角にCtrl + Shift + Alt + Eを割り当てて、これはWindowsのアプリケーションへのグローバルショートカットキーで&lt;a href="https://www.voidtools.com/"&gt;Everything&lt;/a&gt;を宛ててます。あまりキーをフックするようなのをソフトウェア側で仕込みたくはないのですが、Windows標準機能ならまぁ良いでしょう、ということで。&lt;/p&gt;
&lt;p&gt;マクロが欲しいとかショートカットキー登録数が少ないとか思うところもありますが、全体的にはかなり相当良いです！&lt;/p&gt;
&lt;p&gt;キーボードといえば、iPad Pro用に&lt;a href="https://www.logicool.co.jp/ja-jp/products/tablet-keyboards/keys-to-go2-apple.920-013030.html"&gt;logicool Keys-To-Go 2 for iPad&lt;/a&gt;を買ったのですが、これもかなり良くて体験変わりました。今までモバイルキーボード難民で全然しっくり来るものがなかったのですが、これが一番アリだなあ、という感じですね……！&lt;/p&gt;
&lt;p&gt;iPadには&lt;a href="https://www.amazon.co.jp/dp/B0972XHTRF"&gt;prendre タブレットスタンド iPad&lt;/a&gt;を貼り付けてキックスタンドにしてます。たった17g追加するだけで自立する！これは超便利。軽量化したとはいえ、重たいiPadなのでケースとか入れてこれ以上重たくしたくはない。が、自立してくれないと不便、で、色々探して買ったのがこれでした。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/user-attachments/assets/9532238d-203b-4556-bf8e-4c0ccb9a38af" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;横はもちろん、縦でもちゃんと安定してくれる。粘着テープで貼り付けるタイプは剥がれる危険性があるわけですが、iPad Proが軽量化してくれたおかげもあってそこそこ安定しています。ただし両面テープは付属のは剥がして、色々試した結果&lt;a href="https://www.amazon.co.jp/dp/B00BPJLP3G"&gt;スリーエム(3M) 3M 両面テープ 超強力 スーパー多用途 薄手 幅12mm&lt;/a&gt;に落ち着きました。あまり強力すぎると、それはそれで剥がしにくくて売るときとかに泣いちゃう（本当に剥がれない……！）ので、粘着力が基本なのですが、その上でいざというときに剥がれてくれるかどうかのバランスも大事……。&lt;/p&gt;
&lt;p&gt;というわけかで&lt;a href="https://av.watch.impress.co.jp/docs/news/1589552.html"&gt;iPad Pro 13インチ&lt;/a&gt;も買ったのですが、これは満足感高いです。違いは、やっぱ有機ELディスプレイですかねー、今までのiPadの画質って割と不満足というか、どう見てもiPhoneよりも画質悪いじゃん！という感じで萎え萎えだった（のであまり使わなくなっていった……）のですが、今回の画質ならOKです！というわけで利用頻度上がりました。ちなみにNano-textureガラスではありません。いや、最初Nano-textureガラスのやつを買っちゃったんですが、これ普通に画質めちゃくちゃ悪いんですよ。インターネットマンが画質は大して変わらないとか言うから信じたのにめっちゃ悪くて……。耐えられなかったのですみませんがの返品からの買い直しコンボさせていただきました……。&lt;/p&gt;
&lt;p&gt;そして、&lt;a href="https://povo.jp/"&gt;povo&lt;/a&gt;。ずっとauだったのですが、povoに乗り換えました。で、これがめちゃくちゃいい、というかiPadでの利用にとてもいい。SIM付きモデル買ったのですが、auでのデータ共有がうまくできず（難易度高すぎ＆なんかバグってると思う……）塩漬けだったのです。が、povoで単独での契約だと、当たり前ですがスムーズに通信できて快適。iPadもテザリングがそこそこiPhoneとスムーズにつながるからなくてもいいじゃん、とか思ってなくもなかったのですが、単独で通信できる快適さはぜんぜん違う！そして、私の用途的に別にそんなに毎日通信するわけでもないので、あんまりギガはいらないんですが、povoだとプロモーションと合わせると実質0円運用できるのが、とてもいい感じです。例えばローソン500円購入券がpovoで500円で買えて0.3GBの通信料がついてくる、とかだと、どうせ500円買うんだしpovoで買ってiPadに0.3GBチャージしとくかあ、みたいな。&lt;/p&gt;
&lt;p&gt;最後に、Game of the Yearは今更グランツーリスモ7ということで（？）。というのもLogicoolの&lt;a href="https://gaming.logicool.co.jp/ja-jp/products/driving/pro-racing-wheel.html"&gt;PROレーシングホイール&lt;/a&gt; + PRO RACING PEDALSを買ったのですが、これが抜群にいい……！今まで(G923)とは桁違い、というか実際桁違いで、G923が2.4nmというフィードバック力しか出せていないのですが、PROレーシングホイール11nm出る！11nmって別に全くピンと来ないのですが、触ってみると2.4nmはスカスカで、逆に11nmをフルに出すと重くて曲げられないレベル（実際、筋肉痛になった……）。そんなわけで一気に楽しくなったので、今年は一番グランツーリスモ7をやってた気がします。はい。&lt;/p&gt;
&lt;h2&gt;来年&lt;/h2&gt;
&lt;p&gt;C#でSaaS作りたい欲求はずっとあるので、OSSメンテナンス業が重くのしかかりつつも、来年はそっち側でも進展を見せたいと思ってます……！Cysharpももう少し大きくしたいとは思っているので、引き続きよろしくおねがいします。&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Mon, 30 Dec 2024 00:00:00 +0900</pubDate>
      <a10:updated>2024-12-30T00:00:00+09:00</a10:updated>
    </item>
    <item>
      <guid isPermaLink="true">https://neue.cc/2024/12/20_mastermemory_v3.html</guid>
      <link>https://neue.cc/2024/12/20_mastermemory_v3.html</link>
      <title>MasterMemory v3 - Source Generator化したC#用の高速な読み込み専用インメモリデータベース</title>
      <description>&lt;h1 data-pagefind-sort="date:2024-12-20" data-pagefind-meta="published:2024-12-20"&gt;&lt;a href="https://neue.cc/2024/12/20_mastermemory_v3.html"&gt;MasterMemory v3 - Source Generator化したC#用の高速な読み込み専用インメモリデータベース&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2024-12-20&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;&lt;a href="https://github.com/Cysharp/MasterMemory"&gt;MasterMemory&lt;/a&gt; v3出しました！ついにSource Generator化されました！&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/user-attachments/assets/e804fa52-f6a5-4972-a510-0b3b17a31230" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;MasterMemoryはC#のインメモリデータベースで、高速で、メモリ消費量が少なく、タイプセーフ。というライブラリです。SQLiteを素朴に使うよりも &lt;em&gt;4700&lt;/em&gt;倍高速だぞ、と。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/46207/61031896-61890800-a3fb-11e9-86b7-84c821d347a4.png" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;もともとMasterMemoryはC#コードからC#コードを生成するという、Source Generatorのなかった時代にSource Generatorのようなことをやる先進的な設計思想を持ったシステムでした。今回移植してみて、あまりにもスムーズに移植できるし、旧来のコードも全く手を付けずにそのまま動いたので我ながら感心しました。やっと時代が追い付いたか……。&lt;/p&gt;
&lt;p&gt;というわけで、以下のようなC#定義からデータベース構築のためのコードと、クエリ部分がSource Generatorによって自動生成されます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;[MemoryTable(&amp;quot;person&amp;quot;), MessagePackObject(true)]
public record Person
{
    [PrimaryKey]
    public required int PersonId { get; init; }
    
    [SecondaryKey(0), NonUnique]
    [SecondaryKey(1, keyOrder: 1), NonUnique]
    public required int Age { get; init; }

    [SecondaryKey(2), NonUnique]
    [SecondaryKey(1, keyOrder: 0), NonUnique]
    public required Gender Gender { get; init; }

    public required string Name { get; init; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/46207/61035808-cb58e000-a402-11e9-9209-d51665d1cd56.png" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;C#コードとして生成されるので、クエリが全て入力補完も効くし戻り値も型付けされていてタイプセーフなのはもちろん、パフォーマンスの良さにも寄与しています。&lt;/p&gt;
&lt;p&gt;読み取り専用データベースとして使うので、クラス定義はイミュータブルのほうがいいわけですが、最近のC#は &lt;code&gt;record&lt;/code&gt;, &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;required&lt;/code&gt; といった機能が提供されているので、Readonly Databaseとしての使い勝手が更に上がりました。Unityでは&lt;code&gt;required&lt;/code&gt;は使えませんが&lt;code&gt;record&lt;/code&gt;と&lt;code&gt;init&lt;/code&gt;は使えるので、Unityでも問題ありません。&lt;/p&gt;
&lt;p&gt;なお、Unity版は今回からNuGetForUnityでの提供となります。また、MessagePack for C#もSource Generator対応のv3を要求します。&lt;/p&gt;
&lt;h2&gt;Next&lt;/h2&gt;
&lt;p&gt;MasterMemory、実は結構使われています。ゲームでも採用されているものを割と見かけるようになりました。なので、外部ツール由来のコード生成の面倒さにはだいぶ心を痛めていたので、ようやく解消できて本当に嬉しい！&lt;/p&gt;
&lt;p&gt;v2からv3へのマイグレーションもそんなに大変ではない、はずです。あえて生成コードの品質や、コアの関数、メソッドシグネチャなどには一切手を加えていないので、今までコマンドラインツールを叩いていた部分を削除するだけで、そのまま動き出すぐらいの代物になっています。名前空間の設定だけ、アセンブリ属性で行ってください。&lt;/p&gt;
&lt;p&gt;そのうえでrecord対応（今までしてなかった！）や#nullable enable対応（今までしてなかった！）を追加しているので、生成部分以外の使い勝手も上がっているはずです。&lt;/p&gt;
&lt;p&gt;今後は&lt;a href="https://github.com/Cysharp/MemoryPack"&gt;MemoryPack&lt;/a&gt;対応や、そもそものAPIの更なるモダン化（現状はnetstandard2.0なので古い）、全体的に改修したいところ(ImmutableBuilderなど生成コードの差し替え部分)、などなどやれること自体はめっちゃありますので、折を見て手を入れていけるといいかなあ、と思っています。&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Fri, 20 Dec 2024 00:00:00 +0900</pubDate>
      <a10:updated>2024-12-20T00:00:00+09:00</a10:updated>
    </item>
    <item>
      <guid isPermaLink="true">https://neue.cc/2024/12/16_ConsoleAppFramewrok_v5_3_0.html</guid>
      <link>https://neue.cc/2024/12/16_ConsoleAppFramewrok_v5_3_0.html</link>
      <title>ConsoleAppFramework v5.3.0 - NuGet参照状況からのメソッド自動生成によるDI統合の強化、など</title>
      <description>&lt;h1 data-pagefind-sort="date:2024-12-16" data-pagefind-meta="published:2024-12-16"&gt;&lt;a href="https://neue.cc/2024/12/16_ConsoleAppFramewrok_v5_3_0.html"&gt;ConsoleAppFramework v5.3.0 - NuGet参照状況からのメソッド自動生成によるDI統合の強化、など&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2024-12-16&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;&lt;a href="https://github.com/Cysharp/ConsoleAppFramework"&gt;ConsoleAppFramework&lt;/a&gt; v5の比較的アップデートをしました！v5自体の詳細は以前に書いた&lt;a href="https://neue.cc/2024/06/13_ConsoleAppFramework_v5.html"&gt;ConsoleAppFramework v5 - ゼロオーバーヘッド・Native AOT対応のC#用CLIフレームワーク&lt;/a&gt;を参照ください。v5はかなり面白いコンセプトになっていて、そして支持されたと思っているのですが、幾つか使い勝手を犠牲にした点があったので、今回それらをケアしました。というわけで使い勝手がかなり上がった、と思います……！&lt;/p&gt;
&lt;h2&gt;名前の自動変換を無効にする&lt;/h2&gt;
&lt;p&gt;コマンドネームとオプションネームは、デフォルトでは自動的にkebab-caseに変換されます。これはコマンドラインツールの標準的な命名規則に従うものですが、内部アプリケーションで使うバッチファイルの作成に使ったりする場合などには、変換されるほうが煩わしく感じるかもしれません。そこで、アセンブリ単位でオフにする機能を今回追加しました。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;using ConsoleAppFramework;

[assembly: ConsoleAppFrameworkGeneratorOptions(DisableNamingConversion = true)]

var app = ConsoleApp.Create();
app.Add&amp;lt;MyProjectCommand&amp;gt;();
app.Run(args);

public class MyProjectCommand
{
    public void Execute(string fooBarBaz)
    {
        Console.WriteLine(fooBarBaz);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;[assembly: ConsoleAppFrameworkGeneratorOptions(DisableNamingConversion = true)]&lt;/code&gt;によって自動変換が無効になります。この例では &lt;code&gt;ExecuteCommand --fooBarBaz&lt;/code&gt; がコマンドとなります。&lt;/p&gt;
&lt;p&gt;実装面でいうと、Source Generatorにコンフィグを与えるのはAdditionalFilesにjsonや独自書式のファイル(例えばBannedApiAnalyzersのBannedSymbols.txt)を置くパターンが多いですが、ファイルを使うのは結構手間が多くて面倒なんですよね。boolの1つや2つを設定するぐらいなら、アセンブリ属性を使うのが一番楽だと思います。&lt;/p&gt;
&lt;p&gt;実装手法としては&lt;code&gt;CompilationProvider&lt;/code&gt;から&lt;code&gt;Assembly.GetAttributes&lt;/code&gt;で引っ張ってこれます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;var generatorOptions = context.CompilationProvider.Select((compilation, token) =&amp;gt;
{
    foreach (var attr in compilation.Assembly.GetAttributes())
    {
        if (attr.AttributeClass?.Name == &amp;quot;ConsoleAppFrameworkGeneratorOptionsAttribute&amp;quot;)
        {
            var args = attr.NamedArguments;
            var disableNamingConversion = args.FirstOrDefault(x =&amp;gt; x.Key == &amp;quot;DisableNamingConversion&amp;quot;).Value.Value as bool? ?? false;
            return new ConsoleAppFrameworkGeneratorOptions(disableNamingConversion);
        }
    }

    return new ConsoleAppFrameworkGeneratorOptions(DisableNamingConversion: false);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これを他のSyntaxProviderからのSourceとCombineしてやれば、生成時に属性の値を参照できるようになります。&lt;/p&gt;
&lt;h2&gt;ConfigureServices/ConfigureLogging/ConfigureConfiguration&lt;/h2&gt;
&lt;p&gt;ゼロディペンデンシーを掲げている都合上、特定のライブラリに依存したコードを生成することができないという制約がConsoleAppFramework v5にはありました。そのため、DIとの統合時に自分でServiceProviderをビルドしなければならないなの、利用には一手間必要でした。そこで、NuGetでのDLLの参照状況を解析し、&lt;code&gt;Microsoft.Extensions.DependencyInjection&lt;/code&gt;が参照されていると、&lt;code&gt;ConfigureServices&lt;/code&gt;メソッドが&lt;code&gt;ConsoleAppBuilder&lt;/code&gt;から使えるという実装を追加しました。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;var app = ConsoleApp.Create()
    .ConfigureServices(service =&amp;gt;
    {
        service.AddTransient&amp;lt;MyService&amp;gt;();
    });

app.Add(&amp;quot;&amp;quot;, ([FromServices] MyService service, int x, int y) =&amp;gt; Console.WriteLine(x + y));

app.Run(args);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これによりフレームワークそのものはゼロディペンデンシーでありながら、ライブラリ依存のコードも生成することができるという、新しい体験を提供します。これは&lt;code&gt;MetadataReferencesProvider&lt;/code&gt;から引っ張ってきて生成処理に回すことで実現しました。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;var hasDependencyInjection = context.MetadataReferencesProvider
    .Collect()
    .Select((xs, _) =&amp;gt;
    {
        var hasDependencyInjection = false;

        foreach (var x in xs)
        {
            var name = x.Display;
            if (name == null) continue;

            if (!hasDependencyInjection &amp;amp;&amp;amp; name.EndsWith(&amp;quot;Microsoft.Extensions.DependencyInjection.dll&amp;quot;))
            {
                hasDependencyInjection = true;
                continue;
            }

            // etc...
        }

        return new DllReference(hasDependencyInjection, hasLogging, hasConfiguration, hasJsonConfiguration, hasHost);
    });

context.RegisterSourceOutput(hasDependencyInjection, EmitConsoleAppConfigure);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参照の解析は複数のものに対して行っていて、他にも&lt;code&gt;Microsoft.Extensions.Logging&lt;/code&gt;が参照されていれば&lt;code&gt;ConfigureLogging&lt;/code&gt;が使えるようになります。なので&lt;a href="https://github.com/Cysharp/ZLogger"&gt;ZLogger&lt;/a&gt;と組み合わせれば&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;// Package Import: ZLogger
var app = ConsoleApp.Create()
    .ConfigureLogging(x =&amp;gt;
    {
        x.ClearProviders();
        x.SetMinimumLevel(LogLevel.Trace);
        x.AddZLoggerConsole();
        x.AddZLoggerFile(&amp;quot;log.txt&amp;quot;);
    });

app.Add&amp;lt;MyCommand&amp;gt;();
app.Run(args);

// inject logger to constructor
public class MyCommand(ILogger&amp;lt;MyCommand&amp;gt; logger)
{
    public void Echo(string msg)
    {
        logger.ZLogInformation($&amp;quot;Message is {msg}&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;といったように、比較的すっきりと設定が統合できます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;appsettings.json&lt;/code&gt;から設定ファイルを引っ張ってくるというのも最近では定番パターンですが、これも&lt;code&gt;Microsoft.Extensions.Configuration.Json&lt;/code&gt;を参照していると&lt;code&gt;ConfigureDefaultConfiguration&lt;/code&gt;が使えるようになり、これは&lt;code&gt;SetBasePath(System.IO.Directory.GetCurrentDirectory())&lt;/code&gt;と&lt;code&gt;AddJsonFile(&amp;quot;appsettings.json&amp;quot;, optional: true)&lt;/code&gt;を自動的に行います（追加でActionでconfigureすることも可能、また、ConfigureEmptyConfigurationもあります）。&lt;/p&gt;
&lt;p&gt;なのでコンフィグを読み込んでクラスにバインドしてコマンドにDIで渡す、などといった処理もシンプルに書けるようになりました。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;// Package Import: Microsoft.Extensions.Configuration.Json
var app = ConsoleApp.Create()
    .ConfigureDefaultConfiguration()
    .ConfigureServices((configuration, services) =&amp;gt;
    {
        // Package Import: Microsoft.Extensions.Options.ConfigurationExtensions
        services.Configure&amp;lt;PositionOptions&amp;gt;(configuration.GetSection(&amp;quot;Position&amp;quot;));
    });

app.Add&amp;lt;MyCommand&amp;gt;();
app.Run(args);

// inject options
public class MyCommand(IOptions&amp;lt;PositionOptions&amp;gt; options)
{
    public void Echo(string msg)
    {
        ConsoleApp.Log($&amp;quot;Binded Option: {options.Value.Title} {options.Value.Name}&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Microsoft.Extensions.Hosting&lt;/code&gt;でビルドしたい場合は、&lt;code&gt;ToConsoleAppBuilder&lt;/code&gt;が、これも&lt;code&gt;Microsoft.Externsions.Hosting&lt;/code&gt;を参照すると追加されるようになっています。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;// Package Import: Microsoft.Extensions.Hosting
var app = Host.CreateApplicationBuilder()
    .ToConsoleAppBuilder();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、今回から設定されている&lt;code&gt;IServiceProvider&lt;/code&gt;は&lt;code&gt;Run&lt;/code&gt;または&lt;code&gt;RunAsync&lt;/code&gt;終了後に自動的にDisposeするようになりました。&lt;/p&gt;
&lt;h2&gt;RegisterCommands from Attribute&lt;/h2&gt;
&lt;p&gt;コマンドの追加は&lt;code&gt;Add&lt;/code&gt;または&lt;code&gt;Add&amp;lt;T&amp;gt;&lt;/code&gt;が必要でしたが、クラスに属性を付与することで自動的に追加される機能をいれました。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;[RegisterCommands]
public class Foo
{
    public void Baz(int x)
    {
        Console.Write(x);
    }
}

[RegisterCommands(&amp;quot;bar&amp;quot;)]
public class Bar
{
    public void Baz(int x)
    {
        Console.Write(x);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これらは自動で追加されています。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;var app = ConsoleApp.Create();

// Commands:
//   baz
//   bar baz
app.Run(args);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これらとは別に追加で&lt;code&gt;Add&lt;/code&gt;, &lt;code&gt;Add&amp;lt;T&amp;gt;&lt;/code&gt;することも可能です。&lt;/p&gt;
&lt;p&gt;なお、実装の当初予定では任意の属性を使えるようにする予定だったのですが、&lt;code&gt;IncrementalGenerator&lt;/code&gt;のAPIの都合上難しくて、固定の&lt;code&gt;RegisterCommands&lt;/code&gt;属性のみを対象としています。また、継承することもできません……。なので独自の処理用属性がある場合は、組み合わせてもらう必要があります。例えば以下のように。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;[RegisterCommands, Batch(&amp;quot;0 10 * * *&amp;quot;)]
public class MyCommands
{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この辺は&lt;a href="https://qiita.com/omt_teruki/items/dae315c7e86722fe05e6"&gt;ConsoleAppFrameworkとAWS CDKで爆速バッチ開発&lt;/a&gt;を読んで、うーん、v5を使ってもらいたい！なんとかしたい！と思って色々考えたのですが、この辺が現状の限界でした……。名前変換オフりたいのもわかるー、とか今回の更新内容はこの記事での利用例を参考にさせていただきました、ありがとうございます！&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;v5のリリース以降もフィルターを外部アセンブリに定義できるようになったり、Incremental Generatorの実装を見直して高速化するなど、Improvmentは続いています！非常に良いフレームワークに仕上がってきました！&lt;/p&gt;
&lt;p&gt;ところで&lt;a href="https://github.com/dotnet/command-line-api/"&gt;System.CommandLine&lt;/a&gt;、現状うまくいってないから&lt;a href="https://github.com/dotnet/command-line-api/issues/2338"&gt;Resettting System.CommandLine&lt;/a&gt;だ！と言ったのが今年の3月。例によって想像通り進捗は無です。知ってた。そうなると思ってた。何も期待しないほうがいいし、普通にConsoleAppFramework使っていくで良いでしょう。&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Mon, 16 Dec 2024 00:00:00 +0900</pubDate>
      <a10:updated>2024-12-16T00:00:00+09:00</a10:updated>
    </item>
    <item>
      <guid isPermaLink="true">https://neue.cc/2024/12/06_MessagePack_v3.html</guid>
      <link>https://neue.cc/2024/12/06_MessagePack_v3.html</link>
      <title>SourceGenerator対応のMessagePack for C# v3リリースと今後について</title>
      <description>&lt;h1 data-pagefind-sort="date:2024-12-06" data-pagefind-meta="published:2024-12-06"&gt;&lt;a href="https://neue.cc/2024/12/06_MessagePack_v3.html"&gt;SourceGenerator対応のMessagePack for C# v3リリースと今後について&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2024-12-06&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;先月&lt;a href="https://github.com/MessagePack-CSharp/MessagePack-CSharp"&gt;MessagePack for C#プロジェクト&lt;/a&gt;は &lt;a href="https://dotnetfoundation.org/"&gt;.NET Foundation&lt;/a&gt;に参加しました！より安定した視点で利用していただけるという一助になればいいと思っています。&lt;/p&gt;
&lt;p&gt;そして、長く開発を続けていたメジャーバージョンアップ、v3がリリースされました。コア部分はv2とはほぼ変わらずですが、Source Generatorを全面的に導入しています。引き続きIL動的生成も存在するため、IL動的生成とSource Generatorのハイブリッドなシリアライザーとなります。v3にはSource GeneratorとAnalyzerがビルトインで同梱されていて、今までのコードはv3でコンパイルするだけで自動的にSource Generator化されます。v2 -&amp;gt; v3アップデートでSource Generator対応するために追加でユーザーがコードを記述する必要はありません！&lt;/p&gt;
&lt;p&gt;挙動を詳しく見ていきましょう。例えば、&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;[MessagePackObject]
public class MyTestClass
{
    [Key(0)]
    public int MyProperty { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;というコードを書くと、自動的に以下のコードがSource Generatorによって内部的に生成されます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;partial class GeneratedMessagePackResolver
{
    internal sealed class MyTestClassFormatter : IMessagePackFormatter&amp;lt;MyTestClass&amp;gt;
    {
        public void Serialize(ref MessagePackWriter writer, MyTestClass value, MessagePackSerializerOptions options)
        {
            if (value == null)
            {
                writer.WriteNil();
                return;
            }

            writer.WriteArrayHeader(1);
            writer.Write(value.MyProperty);
        }

        public MyTestClass Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
        {
            if (reader.TryReadNil())
            {
                return null;
            }

            options.Security.DepthStep(ref reader);
            var length = reader.ReadArrayHeader();
            var ____result = new MyTestClass();

            for (int i = 0; i &amp;lt; length; i++)
            {
                switch (i)
                {
                    case 0:
                        ____result.MyProperty = reader.ReadInt32();
                        break;
                    default:
                        reader.Skip();
                        break;
                }
            }

            reader.Depth--;
            return ____result;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、このGeneratedMessagePackResolverはデフォルトのオプション(StandardResolverなど)に最初から登録されているため、&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;public static readonly IFormatterResolver[] DefaultResolvers = [
    BuiltinResolver.Instance,
    AttributeFormatterResolver.Instance,
    SourceGeneratedFormatterResolver.Instance, // here
    ImmutableCollection.ImmutableCollectionResolver.Instance,
    CompositeResolver.Create(ExpandoObjectFormatter.Instance),
    DynamicGenericResolver.Instance, // only enable for RuntimeFeature.IsDynamicCodeSupported
    DynamicUnionResolver.Instance];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ユーザーコードのアセンブリに含まれているシリアライズ対象クラスは、Source Generatorによって生成されたコードが優先的に使われることになります。GeneratedMessagePackResolverは既定の名前空間や名前を変えたり、生成フォーマッターをmapベースに変更するなど、幾つかのカスタマイズポイントも用意されています。より詳しくは新しいドキュメントを見てください。また、v2 -&amp;gt; v3の変更箇所の詳細を知りたい人は&lt;a href="https://github.com/MessagePack-CSharp/MessagePack-CSharp/blob/develop/doc/migrating_v2-v3.md"&gt;Migration Guide v2 -&amp;gt; v3&lt;/a&gt;をチェックしてください。&lt;/p&gt;
&lt;p&gt;Unityにおいては導入方法が大きく変わりました。コアライブラリは .NET 版と共通になりNuGetからのインストールが必要となります。そのうえでUPMでUnity用の追加コードをダウンロードする必要があります。詳しくは&lt;a href="https://github.com/MessagePack-CSharp/MessagePack-CSharp/#unity-support"&gt;MessagePack-CSharp#unity-support&lt;/a&gt;のセクションを確認してください。&lt;/p&gt;
&lt;p&gt;.unitypackageの提供は廃止されています。また、IL2CPP対応のために要求していたmpcはなくなりました。完全にSource Generatorに移行されます。そのため、Unityのサポートバージョンは &lt;code&gt;2022.3.12f1&lt;/code&gt; からとなります。Source Generatorに関してはNuGetForUnityでのコアライブラリインストール時に自動的に有効化されるため、追加の作業は必要ありません。&lt;/p&gt;
&lt;h2&gt;History and Next&lt;/h2&gt;
&lt;p&gt;MessagePack for C#のオリジナル(v1)は私(Yoshifumi Kawai/@neuecc)によって、2017年にリリースしました。当時開発していたゲームのパフォーマンス問題を解決するために、2016年時点で存在していた(バイナリ)シリアライザーでは需要を満たせなかったため、パフォーマンスを最重要視したバイナリシリアライザーとして作成しました。合わせて、同じくネットワークシステムとして作成したgRPCベースのRPCフレームワーク&lt;a href="https://github.com/Cysharp/MagicOnion"&gt;MagicOnion&lt;/a&gt;もリリースしています。&lt;/p&gt;
&lt;p&gt;v1リリース当時は&lt;code&gt;byte[]&lt;/code&gt;のみを対象としていましたが、&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;や&lt;code&gt;IBufferWriter&amp;lt;T&amp;gt;&lt;/code&gt;など、.NETには次々と新しいI/O系のAPIが追加されていったため、v2ではそれらに焦点を当てた新しいデザインが導入されました。この実装はMicrosoftのEngineerである&lt;a href="https://github.com/AArnott"&gt;Andrew Arnott / @AArnott&lt;/a&gt;氏によって主導され、リリースしています。&lt;/p&gt;
&lt;p&gt;以降、共同のメンテナンス体制として、そして私の個人リポジトリ(neuecc/MessagePack-CSharp)からオーガナイゼーション(MessagePack-CSharp/MessagePack-CSharp)して今に至ります。Visual Studio内部での利用や&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/signalr/messagepackhubprotocol"&gt;SignalRのバイナリープロトコル&lt;/a&gt;、Blazor Serverのプロトコルなど大きなMicrosoftのプロダクトでも使用され、GitHubでのスター数は.NETのバイナリーシリアライザーとしては最も大きなスターを集めています。&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-migration-guide/"&gt;.NET 9で廃止されたBinaryFormatter&lt;/a&gt;の移行先の一つとしても推奨されています。&lt;/p&gt;
&lt;p&gt;v3ではSource Generatorに対応することで、より高いパフォーマンスと柔軟性、AOT対応への第一段階に踏み出すことができました。&lt;/p&gt;
&lt;p&gt;MessagePack for C#プロジェクトは大きな成功を収めたと考えていますが、しかし現在、AArnott氏は個人の新しいMessagePackプロジェクトの開発を開始しています。私もその間、&lt;a href="https://github.com/Cysharp/MemoryPack"&gt;MemoryPack&lt;/a&gt;という異なるフォーマットのシリアライザーをリリースしています。そのため、MessagePack for C#の今後と、その特性について、ある程度説明する必要があると思います。&lt;/p&gt;
&lt;p&gt;引き続きメンテナンス体制は2人だと考えていますが、アクティブな活動に関しては、再び私が担うことになるかもしれません。私はMessagePackとMemoryPackとでは異なる性質を持ったフォーマットであるため、どちらも重要であるという認識で動いています。オリジナルの実装であるMessagePack for C#も気に入ってますし、現在においても決して引けを取ることのないものだと思っています。&lt;/p&gt;
&lt;p&gt;AArnott氏の別のMessagePackシリアライザーとは根本的な哲学が若干異なります。その点で、私はそれはより良く改善されたシリアライザーではなく、別の個性のシリアライザーだと認識しています。そこで、違いについて説明させてください。&lt;/p&gt;
&lt;h2&gt;Binary spec, default settings and performance&lt;/h2&gt;
&lt;p&gt;シリアライザーのパフォーマンスに重要なのは、「仕様と実装」の両方です。例えばテキストフォーマットのJSONよりもバイナリフォーマットのほうが一般的には速いでしょう。しかし、よくできたJSONシリアライザーは、中途半端な実装のバイナリシリアライザーよりも高速です（私はそれを&lt;a href="https://github.com/neuecc/Utf8Json"&gt;Utf8Json&lt;/a&gt;というシリアライザーを作成することで実証したことがあります）。なので、仕様も大事だし、実装も大事です。どちらも兼ねることができれば、それがベストなパフォーマンスのシリアライザーとなります。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://msgpack.org/"&gt;MessagePackのバイナリ仕様&lt;/a&gt;は &amp;quot;It's like JSON. but fast and small.&amp;quot; を標語にしている通り、JSONのバイナリ化としてあらわされています。ところが、MessagePack for C#のデフォルトは必ずしもJSON likeを狙っているわけではありません。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;[MessagePackObject]
public class MsgPackSchema
{
    [Key(0)]
    public bool Compact { get; set; }
    [Key(1)]
    public int Schema { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このクラスをシリアライズした場合は、JSONで表現すると&lt;code&gt;[true, 0]&lt;/code&gt;のようになります。これはオブジェクトをarrayベースでシリアライズしているからで、mapベースでシリアライズすると&lt;code&gt;{&amp;quot;Compact&amp;quot;:true,&amp;quot;Schema&amp;quot;:0}&lt;/code&gt;のような表現になります。&lt;/p&gt;
&lt;p&gt;arrayベースの利点は見た通りに、バイナリ容量として、よりコンパクトになります。容量がコンパクトなことは処理量が少なくなるためシリアライズの速度にも良い影響を与えます。また、デシリアライズにおいては、文字列を比較してデシリアライズするプロパティを探索する必要がなくなるため、より高速なデシリアライズ速度が期待できます。&lt;/p&gt;
&lt;p&gt;なお、arrayベースのシリアライズはMessagePackの仕様策定者である Sadayuki Furuhashi 氏によるリファレンス実装であるmsgpack-javaなどでも採用されているため、決して異端のやり方というわけではありません。&lt;/p&gt;
&lt;p&gt;MessagePack-CSharpではJSONライクなmapベースでシリアライズしたい場合は&lt;code&gt;[MessagePackObject(true)]&lt;/code&gt;と記述することができます。また、Source Generatorの場合はResolver単位でオーバーライドして強制的にmapベースにすることも可能です。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;[MessagePackObject(keyAsPropertyName: true)]
public class MsgPackSchema
{
    public bool Compact { get; set; }
    public int Schema { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;mapの利点は、柔軟なスキーマエボリューションの実現と、他言語との疎通する際にコミュニケーションが取りやすいこと、バイナリそのものの自己記述性が高いことです。デメリットは容量とパフォーマンスへの悪影響、特にオブジェクトの配列においては一要素毎にプロパティ名が含まれることになってしまい、かなりの無駄となります。&lt;/p&gt;
&lt;p&gt;デフォルトをarrayにしているのは、コンパクトさとパフォーマンスの追求のためです。私はMessagePackをJSON likeの前に、高いパフォーマンスを実現可能なバイナリ仕様として考えました。もちろん、mapも重要なので、その上で比較的簡単にmapモードを実現するために属性に&lt;code&gt;(true)&lt;/code&gt;を追加するだけで可能にしました。&lt;/p&gt;
&lt;p&gt;arrayモードの場合はKey属性を全てのプロパティに付与する必要があります。これは、例えばProtocol Buffersなどでも数値タグを必要とするように、プロパティ名そのものをキーとするわけではなければ、必須だと考えています。もちろん、連番で自動採番させることも可能ですが、バイナリフォーマットのキーを暗黙的に処理するのはリスクが大きすぎる(順番を弄ったりするだけでバイナリ互換性が壊れることになる)と判断しています。つまり、明示的がデフォルト、ということです。大きなプロジェクト開発ではシニアメンバーからジュニアメンバーまでコードを触ることになるでしょう、全てを理解している人だけがコードを触るわけではありません。なので、暗黙的な挙動は避けるべきで、明示的にすべきだという強い意志で、この設計を選んでいます。&lt;/p&gt;
&lt;p&gt;ただしKeyを全てのプロパティに付与する作業はとても苦痛です(私はMessagePack-CSharp開発以前には、DataContractやprotobuf-netで辛い思いをしました)。そこで、Analyzer + Code Fixによって、自動的に付与する機能を用意しました。これにより明示的であることの苦痛は和らげられ、良いとこどりができているのだと考えています。&lt;/p&gt;
&lt;p&gt;別のMessagePackシリアライザーのデフォルトはmapのようです。これは&lt;a href="https://github.com/eiriktsarpalis/PolyType"&gt;PolyType&lt;/a&gt;というSource Generatorベースのライブラリ作成のための抽象化ライブラリがベースとしているためでもあり、また、そちらのほうを好んでいるという明示的な判断でもあるようです。&lt;/p&gt;
&lt;p&gt;「デフォルト」はライブラリで一つしか選べません。どちらのモードで処理することができたとしても、「デフォルト」はただ一つです。改めて言うと、私はバイナリフォーマットとしての「コンパクトとパフォーマンス」を好み、優先しています。&lt;/p&gt;
&lt;p&gt;皆さんはPolyTypeについて初めて知ったかもしれません。私はPolyTypeはあまり好意的には考えていません。ちょっとしたものを作るには非常に便利だとは思いますが、ベストなパフォーマンスを狙ったり、ベストなアイディアを表現するには、抽象層であることの制限が大きすぎると考えています。なので、MessagePack for C#で採用することはありませんし、他の何かを作る際にも採用することはないでしょう。&lt;/p&gt;
&lt;h2&gt;Unity(multiplatform) Support&lt;/h2&gt;
&lt;p&gt;MessagePack for C#ではv1の時代からゲームエンジンUnityの1st classのサポートを実行してきました。これは私が&lt;a href="https://en.wikipedia.org/wiki/Cygames"&gt;Cygames&lt;/a&gt;という日本のゲーム会社の関連会社(&lt;a href="https://cysharp.com/"&gt;Cysharp&lt;/a&gt;)のCEOを務めていて、ビデオゲームインダストリーと関係性が深いという都合もあります。自分たちで実際にUnityで動くものを作り、使ってきました。もちろん、サーバーサイドやデスクトップアプリケーションでも使っています。&lt;/p&gt;
&lt;p&gt;UnityにはIL2CPPという独自のAOTシステムがあり、特にiOSなどモバイルプラットフォームでのリリースには必須なのですが、それもSource Generatorが存在しなかった時代から、mpcというRoslynを使ったコードジェネレートツールを作り、提供してきました。数百のモバイルゲームでMessagePackが使われているのは、これら私の熱心なサポートのお陰といっても過言ではないでしょう。v3ではついにSource Generatorベースになったことにより、ワークフローが大きく簡易化されることとなります！&lt;/p&gt;
&lt;p&gt;一般的に、.NETコミュニティにおいてはUnityサポートはかなり軽視されていました。また、外から見ているとMicrosoftやMicrosoftの従業員もそのようで、自社のプラットフォーム以外への関心は薄そうです。こうした態度は、あまり好ましいとは思っていませんし、せっかくの .NET の可能性を狭めていることにもなっています。Xamarinがうまく成長軌道に乗らなかったのも、そのようなMicrosoft自体の冷たい視線のせいだとも思っています。&lt;/p&gt;
&lt;p&gt;私は、私の作るライブラリはなるべくUnityにもしっかり対応できるように気を付けて作っています（最新は新しいReactive Extensionsライブラリーである&lt;a href="https://github.com/Cysharp/R3"&gt;Cysharp/R3&lt;/a&gt;）。別のMessagePackシリアライザーに関しては、あまりしっかりした対応はされなさそうですが……。&lt;/p&gt;
&lt;h2&gt;Beyond v3&lt;/h2&gt;
&lt;p&gt;v3のNative AOT Supportは完全ではありません。Source Generatorにするだけでは完全なNative AOT対応とはならないのは難しいところです。これはUnityのAOTであるIL2CPPでは完璧に動作しているだけに、正直不可解なことでもあり、また、Microsoftのよくない癖が出ているな、とも思っています。つまり、完璧な対応をするために、複雑なものを提供している。それが現在のNative AOTです。複雑怪奇な属性やフローは、理解できるところもありますが、もう少し簡略化すべきだったと思います。まぁ、もう修正されることもないのでしょうが……。&lt;/p&gt;
&lt;p&gt;パフォーマンス面でもv1からv2で退化してしまった点もあるので、最新の知見を元に、実装面での改善を施す必要があります。特にReadOnlySequenceの利用幅が大きいことは、かなりの制約を生み出していて、不満があります。&lt;/p&gt;
&lt;p&gt;.NET 9でPipeReader/PipeWriterが標準化されたことによる、より良い非同期APIや、パフォーマンスを両立したストリーミング対応というのも、大きなトピックとなるかもしれません。&lt;/p&gt;
&lt;p&gt;MessagePack for C#は広く使われているが故に、破壊的変更はしづらいし、互換性の維持は最重要トピックスです。しかし、世の中が変わっていく以上、進化しないことを選んだら、それは滅びる道でしかありません。やれることはまだまだあると思っていますので、.NETにおける最先端の、最高のバイナリシリアライザーであり続けたいと思っています（&lt;a href="https://github.com/Cysharp/MemoryPack"&gt;MemoryPack&lt;/a&gt;もね……！)&lt;/p&gt;
&lt;p&gt;まずは、v3のSource Generatorをぜひ試してみてください。皆の力でより良いものを作っていけるというのも、OSSの良さだと思っています。&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Fri, 06 Dec 2024 00:00:00 +0900</pubDate>
      <a10:updated>2024-12-06T00:00:00+09:00</a10:updated>
    </item>
    <item>
      <guid isPermaLink="true">https://neue.cc/2024/12/03_SharpFuzz.html</guid>
      <link>https://neue.cc/2024/12/03_SharpFuzz.html</link>
      <title>Fuzzing in .NET: Introducing SharpFuzz</title>
      <description>&lt;h1 data-pagefind-sort="date:2024-12-03" data-pagefind-meta="published:2024-12-03"&gt;&lt;a href="https://neue.cc/2024/12/03_SharpFuzz.html"&gt;Fuzzing in .NET: Introducing SharpFuzz&lt;/a&gt;&lt;/h1&gt;
&lt;ul class="date"&gt;&lt;li&gt;2024-12-03&lt;/li&gt;&lt;/ul&gt;
&lt;div class="entry_body"&gt;&lt;p&gt;この記事は&lt;a href="https://qiita.com/advent-calendar/2024/csharplang"&gt;C# Advent Calendar 2024&lt;/a&gt;に参加しています。また、先月開催された&lt;a href="https://dotnetnew.connpass.com/event/335955/"&gt;dotnet new&lt;/a&gt;というイベントでの発表のフォローアップ、のつもりだったのですがコロナ感染につき登壇断念……。というわけで、セッション資料はないので普通にブログ記事とします！&lt;/p&gt;
&lt;h2&gt;dotnet/runtime と Fuzzing&lt;/h2&gt;
&lt;p&gt;今年に入ってからdotnet/runtimeにFuzzingテストが追加されています。&lt;a href="https://github.com/dotnet/runtime/tree/main/src/libraries/Fuzzing"&gt;dotnet/runtime/Fuzzing&lt;/a&gt;。というわけで、実はfuzzingは非常に最近のトピックスなのです……！&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%B8%E3%83%B3%E3%82%B0"&gt;ファジング&lt;/a&gt;とはなんなのか、ザックリとはランダムな入力値を大量に投げつけることによって不具合や脆弱性を発見するためのテストツールです。エッジケースのテスト、やはりどうしても抜けちゃいがちだし、ましてや脆弱性になりうる絶妙な不正データを人為的に作るのも難しいので、ここはツール頼みで行きましょう。&lt;/p&gt;
&lt;p&gt;Goでは1.18(2022年)から標準でgo fuzzコマンドとして追加されたらしいので、
&lt;a href="https://future-architect.github.io/articles/20220214a/"&gt;Go1.18から追加されたFuzzingとは&lt;/a&gt;のような解説記事を読むのもイメージを掴みやすいです。&lt;/p&gt;
&lt;p&gt;さて、dotnet/runtimeのFuzzingでは現状&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AssemblyNameInfoFuzzer&lt;/li&gt;
&lt;li&gt;Base64Fuzzer&lt;/li&gt;
&lt;li&gt;Base64UrlFuzzer&lt;/li&gt;
&lt;li&gt;HttpHeadersFuzzer&lt;/li&gt;
&lt;li&gt;JsonDocumentFuzzer&lt;/li&gt;
&lt;li&gt;NrbfDecoderFuzzer&lt;/li&gt;
&lt;li&gt;SearchValuesByteCharFuzzer&lt;/li&gt;
&lt;li&gt;SearchValuesStringFuzzer&lt;/li&gt;
&lt;li&gt;TextEncodingFuzzer&lt;/li&gt;
&lt;li&gt;TypeNameFuzzer&lt;/li&gt;
&lt;li&gt;UTF8Fuzzer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;というのものが用意されてます。わかるようなわからないような。だいたいデータのパース系によく使われるものなので、その通りのところに用意されています。一番わかりやすいJsonDocumentFuzzerを見てみましょう。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;internal sealed class JsonDocumentFuzzer : IFuzzer
{
    public string[] TargetAssemblies { get; } = [&amp;quot;System.Text.Json&amp;quot;];
    public string[] TargetCoreLibPrefixes =&amp;gt; [];
    public string Dictionary =&amp;gt; &amp;quot;json.dict&amp;quot;;

    // fuzzerからのランダムなバイト列が入力
    public void FuzzTarget(ReadOnlySpan&amp;lt;byte&amp;gt; bytes)
    {
        if (bytes.IsEmpty)
        {
            return;
        }

        // The first byte is used to select various options.
        // The rest of the input is used as the UTF-8 JSON payload.
        byte optionsByte = bytes[0];
        bytes = bytes.Slice(1);

        var options = new JsonDocumentOptions
        {
            AllowTrailingCommas = (optionsByte &amp;amp; 1) != 0,
            CommentHandling = (optionsByte &amp;amp; 2) != 0 ? JsonCommentHandling.Skip : JsonCommentHandling.Disallow,
        };

        using var poisonAfter = PooledBoundedMemory&amp;lt;byte&amp;gt;.Rent(bytes, PoisonPagePlacement.After);

        try
        {
            // それをParseに投げて、もし不正な例外が来たらなんかバグっていたということで
            JsonDocument.Parse(poisonAfter.Memory, options);
        }
        catch (JsonException) { }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ようは想定外のデータ入力で&lt;code&gt;JsonDocument.Parse&lt;/code&gt;が失敗しないことを祈る、といったものですね。正常に認識しているinvalidな値なら&lt;code&gt;JsonException&lt;/code&gt;をthrowするはずですが、&lt;code&gt;ArgumentException&lt;/code&gt;とか&lt;code&gt;StackOverflowException&lt;/code&gt;とかが出てきちゃった場合は認識できていない不正パターンなので、ちゃんとしたハンドリングが必要になってきます。&lt;/p&gt;
&lt;p&gt;では、これを参考にやっていきましょう、とはなりません。えー。まず、dotnet/runtimeのFuzzingではSharpFuzz, libFuzzer, そしてOneFuzzが使用されていると書いてあるのですが、OneFuzzはMicrosoft内部ツールなので外部では使用できません。正確には&lt;a href="https://www.publickey1.jp/blog/20/project_onefuzzwindowsmicorosoft_edge.html"&gt;2020年にオープンソース公開&lt;/a&gt;したものの、&lt;a href="https://github.com/microsoft/onefuzz"&gt;2023年にはクローズドに戻している&lt;/a&gt;状態です。まぁ事情は色々ある。しょーがない。&lt;/p&gt;
&lt;p&gt;というわけで、これはMicrosoft内部で動かすためのOneFuzzや、dotnet/runtimeで動かすために調整してある&lt;code&gt;IFuzzer&lt;/code&gt;といったフレームワーク部分が含まれているので、小規模な自分たちのコードをfuzzingするにあたっては、不要ですし、ぶっちゃけあまり参考にはなりません！解散！&lt;/p&gt;
&lt;h2&gt;Introducing SharpFuzz&lt;/h2&gt;
&lt;p&gt;そんなわけでdotnet/runtimeのFuzzingでも使われている&lt;a href="https://github.com/Metalnem/sharpfuzz"&gt;Metalnem/sharpfuzz: AFL-based fuzz testing for .NET&lt;/a&gt;を直接使っていきます。sharpfuzzは&lt;a href="https://lcamtuf.coredump.cx/afl/"&gt;afl-fuzz&lt;/a&gt;と連動して動くように作られている .NETライブラリです。3rd Partyライブラリですが作者はMicrosoftの人です（dotnet/runtimeで採用されている理由でもあるでしょう）。ReadMeのTrophiesでは色々なもののバグを見つけてやったぜ、と書いてあります。AngleSharpとかGoogle.ProtobufとかGraphQL-ParserとかMarkdigとかMessagePack for C#とImageSharpとか。まぁ、やはり用途としてはパーサーのバグを見つけるのには適切、という感じです。&lt;/p&gt;
&lt;p&gt;AFL(American Fuzzy Lop)ってなに？ということなのですが、そもそもファジングの「ランダムな入力値を大量に投げつける」行為は、完全なランダムデータを投げつけていくわけではありません。完全ランダムだとあまりにも時間がかかりすぎるため、脆弱性発見において実用的とは言えない。そこでAFLはシード値からのミューテーションと、カバレッジをトレースしながら効率よくデータを生成していきます。Wikipediaから引用すると&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;テスト対象のプログラム（テスト項目）のソースコードをインストルメント化することにより、afl-fuzzは、ソフトウェアのどのブロックが特定のテスト刺激で実行されたかを後で確認できる。そのため、AFLはグレーボックステストに使用することができる。遺伝的手法による検査データの生成に関連して、ファザーはテストデータをより適切に生成できるため、このメソッドを使用しない他のファザーよりも、処理中に以前は使用されていなかったコードブロックが実行される。その結果、コードカバレッジは比較的短い時間で比較的高い結果が得られる。この方法は、生成されたデータ内の構造を独立して（つまり、事前の情報なしで）生成することができる。このプロパティは、テストカバレッジの高いテストコーパス（テストケースのコレクション）を生成するためにも使用される。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;というわけでdotnet testのようにテストコードを渡したら全自動でやってくれる、というほど甘くはなくて、多少の下準備が必要になってきます。SharpFuzzは一連の処理をある程度やってくれるようにはなっていますが、そもそもに実行までに二段階の処理が必要になっています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sharpfazzコマンド(dotnet tool)でdllにトレースポイントを注入する&lt;/li&gt;
&lt;li&gt;その注入されたdll(とexe)をネイティブのfuzzing実行プロセス(afl-fuzzなど)に渡す&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;dllにトレースポイントを注入はお馴染みの&lt;a href="https://github.com/jbevain/cecil"&gt;Cecil&lt;/a&gt;でビルド済みのDLLのILを弄ってトレースポイントを仕込みます。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/user-attachments/assets/c3b43b60-8526-44cd-8482-6f1185206b65" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;これは注入済みのdllですが、Trace.SharedMemとかTrace.PrevLocationとか、分岐点に対して明らかに注入している様が見えます。そうしたトレースポイントとの通信や実行データ生成などは外部プロセスが行うので、SharpFuzzというライブラリは、それ自体は実行ツールではなくて、それらとの橋渡しをするためのシステムということです。&lt;/p&gt;
&lt;p&gt;ではやっていきましょう！色々なシステムが絡んでくる分、ちょっとややこしく面倒くさいのと、ReadMeの例をそのままやると罠が多いので、少しアレンジしていきます。&lt;/p&gt;
&lt;p&gt;まずはRequirementsですが、実行機であるAFLがWindowsでは動きません(Linux, macOSでは動く)。なのでWSL上で動かしましょうという話になってくるのですが、それはあんまりにもやりづらいので、&lt;a href="https://llvm.org/docs/LibFuzzer.html"&gt;libFuzzer&lt;/a&gt;というLLVMが開発しているAFL互換のFuzzingツールを使っていくことにします。これはWindowsでビルドできます。&lt;/p&gt;
&lt;p&gt;自分でビルドする必要はなく、SharpFuzzの作者が連携して使うことを意識して用意してくれている&lt;a href="https://github.com/Metalnem/libfuzzer-dotnet/releases"&gt;libfuzzer-dotnetのReleasesページ&lt;/a&gt;から、バイナリを直接落としてきましょう。&lt;code&gt;libfuzzer-dotnet-windows.exe&lt;/code&gt;です。&lt;/p&gt;
&lt;p&gt;次に、IL書き換えを行うツール&lt;code&gt;SharpFuzz.CommandLine&lt;/code&gt;を .NET toolで入れていきましょう。これはglobalでいいかな、と思います。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code&gt;dotnet tool install --global SharpFuzz.CommandLine
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;次に、今回は&lt;a href="https://github.com/kevin-montrose/Jil"&gt;Jil&lt;/a&gt;という、今はもうあまり使われることもないJsonシリアライザーをターゲットとしてやっていこうということなので、JilとSharpFuzzをインストールします。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code&gt;dotnet add package Jil --version 2.15.4
dotnet add package SharpFuzz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここで注意が必要なのは、Jilの最新バージョンはSharpFuzzにより発見されたバグが修正されているので、最新版を入れるとチュートリアルにはなりません！というわけでここは必ずバージョン下げて入れましょう。&lt;/p&gt;
&lt;p&gt;新規のConsoleApplicationで、コードは以下のようにします。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;using Jil;
using SharpFuzz;

// 実行機としてlibFuzzerを使う(引数はReadOnlySpan&amp;lt;byte&amp;gt;)
Fuzzer.LibFuzzer.Run(span =&amp;gt;
{
    try
    {
        using var stream = new MemoryStream(span.ToArray());
        using var reader = new StreamReader(stream);
        JSON.DeserializeDynamic(reader); // このメソッドが正しく動作してくれるかをテスト
    }
    catch (Jil.DeserializationException)
    {
        // Jil.DeserializationExceptionは既知の例外（正しくハンドリングできてる）なので握り潰し
        // それ以外の例外が発生したらルート側にthrowされて問題が検知される
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今度はベースになるテストデータを用意します。名前とかはなんでもいいんですが、&lt;code&gt;Testcases&lt;/code&gt;フォルダに&lt;code&gt;Test.json&lt;/code&gt;を追加しました。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/user-attachments/assets/606cede7-9a20-4efe-8e58-642330ced8d5" alt="image" /&gt;&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-json"&gt;{&amp;quot;menu&amp;quot;:{&amp;quot;id&amp;quot;:1,&amp;quot;val&amp;quot;:&amp;quot;X&amp;quot;,&amp;quot;pop&amp;quot;:{&amp;quot;a&amp;quot;:[{&amp;quot;click&amp;quot;:&amp;quot;Open()&amp;quot;},{&amp;quot;click&amp;quot;:&amp;quot;Close()&amp;quot;}]}}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このデータを元にしてfuzzerは値を変形させていくことになります。&lt;/p&gt;
&lt;p&gt;では実行しましょう！実行するためには、ビルドしてILポストプロセスしてlibFuzzer経由で動かす……。という一連の定型の流れが必要になるため、作者の用意してくれているPowerShellスクリプト&lt;a href="https://raw.githubusercontent.com/Metalnem/sharpfuzz/master/scripts/fuzz-libfuzzer.ps1"&gt;fuzz-libfuzzer.ps1&lt;/a&gt;をダウンロードしてきて使いましょう。&lt;/p&gt;
&lt;p&gt;とりあえず&lt;code&gt;fuzz-libfuzzer.ps1&lt;/code&gt;と&lt;code&gt;libfuzzer-dotnet-windows.exe&lt;/code&gt;をcsprojと同じディレクトリに配置して、以下のコマンドを実行します。&lt;code&gt;ConsoleApp24.csproj&lt;/code&gt;の部分だけ適当に変えてください。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-cmd"&gt;PowerShell -ExecutionPolicy Bypass ./fuzz-libfuzzer.ps1 -libFuzzer &amp;quot;./libfuzzer-dotnet-windows.exe&amp;quot; -project &amp;quot;ConsoleApp24.csproj&amp;quot; -corpus &amp;quot;Testcases&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;動かすと、見つかった場合はいい感じに止まってくれます。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/user-attachments/assets/1ce45aa1-2d50-46f2-8f86-947db39406d6" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;なお、見つからなかった場合は無限に探し続けるので、なんとなくもう見つかりそうにないなあ、と思ったら途中で自分でとめる(Ctrl+C)必要があります。&lt;/p&gt;
&lt;p&gt;Testcasesには途中の残骸と、クラッシュした場合は&lt;code&gt;crash-id&lt;/code&gt;でクラッシュ時のデータが拾えます。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://github.com/user-attachments/assets/d90f5bb1-4509-41b6-a139-16789a5a501c" alt="image" /&gt;&lt;/p&gt;
&lt;p&gt;今回見つかったクラッシュデータは&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-json"&gt;{&amp;quot;menu&amp;quot;:{&amp;quot;id&amp;quot;:1,&amp;quot;val&amp;quot;:&amp;quot;X&amp;quot;,&amp;quot;popid&amp;quot;:1,&amp;quot;val&amp;quot;:&amp;quot;X&amp;quot;,&amp;quot;pop&amp;quot;:{&amp;quot;a&amp;quot;:[{&amp;quot;click&amp;quot;:&amp;quot;Open()&amp;quot;},{&amp;quot;c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;でした。実際このデータを使って再現できます。&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-csharp"&gt;using Jil;

//  クラッシュファイルのプロパティでデータはCopy to Output Directoryしてしまう
//  &amp;lt;None Update=&amp;quot;crash-c57462e70fb60e86e8c41cd18b70624bd1e89822&amp;quot;&amp;gt;
//    &amp;lt;CopyToOutputDirectory&amp;gt;Always&amp;lt;/CopyToOutputDirectory&amp;gt;
//  &amp;lt;/None&amp;gt;
var crash = File.ReadAllBytes(&amp;quot;crash-c57462e70fb60e86e8c41cd18b70624bd1e89822&amp;quot;);
var span = crash.AsSpan();

// Fuzzing時と同じコード
using var stream = new MemoryStream(span.ToArray());
using var reader = new StreamReader(stream);
JSON.DeserializeDynamic(reader);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上！完璧！便利！一度手順を理解してしまえば、そこまで難しいことではないので、是非ハンズオンでやってみることをお薦めします。なお、ps1のスクリプトは実行対象自身へのインジェクトは除外されるようになっているので、小規模な自分のコードでfuzzingを試してみたいと思った場合は、対象コードはexeとは異なるプロジェクトに分離しておく必要があります。&lt;/p&gt;
&lt;p&gt;ところで、AFLにはdictionaryという仕組みがあり、既知のキーワード集がある場合は生成速度を大幅に上昇させることが可能です。例えば&lt;a href="https://github.com/AFLplusplus/AFLplusplus/blob/stable/dictionaries/json.dict"&gt;json.dict&lt;/a&gt;を使う場合は&lt;/p&gt;
&lt;pre data-pagefind-ignore="all"&gt;&lt;code class="language-cmd"&gt;PowerShell -ExecutionPolicy Bypass ./fuzz-libfuzzer.ps1 -libFuzzer &amp;quot;./libfuzzer-dotnet-windows.exe&amp;quot; -project &amp;quot;ConsoleApp24.csproj&amp;quot; -corpus &amp;quot;Testcases&amp;quot; -dict ./json.dict
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;のように指定します。JSONとかYAMLとかXMLとかZipとか、一般的な形式は&lt;a href="https://github.com/AFLplusplus/AFLplusplus/tree/stable/dictionaries"&gt;AFLplusplus/dictionaries&lt;/a&gt;などに沢山転がっています。独自に作ることも可能で、例えばdotnet/runtimeのFuzzingではBinaryFormatterのテストが置いてありますが、これは&lt;a href="https://learn.microsoft.com/ja-jp/dotnet/standard/serialization/binaryformatter-migration-guide/read-nrbf-payloads"&gt;NRBF(.NET Remoting Binary Format)&lt;/a&gt;の辞書、&lt;a href="https://github.com/dotnet/runtime/blob/main/src/libraries/Fuzzing/DotnetFuzzing/Dictionaries/nrbfdecoder.dict"&gt;nrbfdecoder.dict&lt;/a&gt;を用意しているようでした。&lt;/p&gt;
&lt;p&gt;もちろん、なしでも動かすことはできますが、用意できそうなら用意しておくとよいでしょう。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/Cysharp/MemoryPack"&gt;MemoryPack&lt;/a&gt;でも実際バグ見つかってたりするので、この手のライブラリを作る人だったら覚えておいて損はないです。シリアライザーに限らずパーサーに関わるものだったらネットワークプロトコルでも、なんでも適用可能です。ただし現状、入力が&lt;code&gt;byte[]&lt;/code&gt;に制限されているので、応用性自体はあるようで、なかったりはします。これがintとか受け入れてくれると、様々なメソッドに対してカジュアルに使えて、より便利な気もしますが……(実際go fuzzは&lt;code&gt;byte[]&lt;/code&gt;だけじゃなくて基本的なプリミティブの生成に対応している)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;byte[]&lt;/code&gt;列から適当に切り出してintとして使う、といったような処理だと、ミューテーションやカバレッジの関係上、適切な値を取得しにくいので、あまりうまくやれません。libFuzzerでは&lt;a href="https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md"&gt;Structure-Aware Fuzzing with libFuzzer&lt;/a&gt;といったような手法が考案されていて、protocol buffersの構造を与えるとか、gRPCの構造を与えるとかでうまく活用している事例はあるようです。この辺はSharpFuzzの対応次第となります(いつかやりたい、とは書いてありましたが、現実的にいつ来るかというと、あまり期待しないほうが良いでしょう)&lt;/p&gt;
&lt;p&gt;Rustにも&lt;a href="https://github.com/rust-fuzz/cargo-fuzz"&gt;cargo fuzz&lt;/a&gt;といったcrateがあり、それなりに使われているようです。&lt;/p&gt;
&lt;p&gt;Fuzzingは適用範囲が限定的であることと下準備の手間などがあり、一般的なアプリケーション開発者においては、あまりメジャーなテスト手法ではないというのが現状だと思いますが、使えるところはないようで意外とあるとも思うので、ぜひぜひ試してみてください。&lt;/p&gt;
&lt;/div&gt;</description>
      <pubDate>Tue, 03 Dec 2024 00:00:00 +0900</pubDate>
      <a10:updated>2024-12-03T00:00:00+09:00</a10:updated>
    </item>
  </channel>
</rss>