⚔️ C#クエスト ― LINQの試練 🐉
🌅 プロローグ — 久しぶりの再会
長いあいだ C# から離れていた。
VB6 と .NET を行き来していた頃の記憶はまだ残っているけれど、
本格的に C# と向き合っていたわけではない。
それでも――
当時の C# の“手触り”は覚えている。
素朴で、伸びしろの多い、どこか初々しい言語だった。
ところが久しぶりに再会してみると、
その姿はまるで別物だった。
ラムダ式、LINQ、非同期、パターンマッチング、
拡張メソッド、null 許容参照型……
知っているはずの言語なのに、
知らない表情がいくつも増えていた。
「そうか。C# も、この20年でしっかり進化したんだな。」
驚きと懐かしさと、少しの戸惑い。
でも、かつて触っていた記憶と、今目の前にある姿が
一本の線でつながったようにも感じた。
ここから、また歩き始めればいい。
そんな気持ちで、キーボードに手を置いた。
――これが、“C#クエスト”の始まりだった。
第1章:亡霊 — VB6の影と .NET の衝撃
2000年代前半、現場は VB6 全盛の時代だった。
業務アプリもツール類も VB6。
EXE ひとつで配れて、ちょっとした変更なら即反映。
“動けばヨシ”が普通で、
クラス設計なんて贅沢品のように扱われていた。
そんな“お気楽な時代”のど真ん中に、
突如として .NET Framework が現れた。
後継と言われて出てきた VB.NET は、
名前こそ VB でも中身はまるで別物。
それまでの VB 文化を根こそぎ破壊する 超破壊的アップデート で、
現場の VB ユーザーからは総スカンを食らった。
一方で、期待されて登場した新言語 C#。
名前が C++ に似ていたせいで「ガチ系か?」と思われたが、
中身は Java に酷似した構文。
当時ちょうど勢いに乗っていた Java の後追い感もあり、
登場時点ではそれほど期待されていなかった。
そこへ持ち込まれたのが “現実問題”。
うちの現場には 数百万ステップ級の VB6 アプリケーション があった。
巨大化したコードベースを前にして、
.NET への移行は「やる」「やらない」の判断すら難しい。
結局、移行を決断するまでに 十数年が経過し、
移行そのものにもさらに 数年の歳月が必要だった。
その間、私の頭の中はずっと VB6 の亡霊 に支配されていた。
.NET はソースが無駄に大きく感じたし、
実行には .NET Framework のインストールが必須で、
IDE は重く、もっさりと動いた。
「なんでこんなに大げさなんだろう?」
そう思うことも少なくなかった。
結果として、私は C# と適度な距離を置いたまま、
長い時間を過ごすことになった。
そうして迎えた “再会の日”。
しばらく離れていた C# に「久しぶり」と手を伸ばしたら、
そこにあったのは見知らぬ未来だった。
画面にふと目をやると、
そこには LINQ が鎮座していた。
「あれ? こんなだったっけ……?」
この瞬間、私は気づいた。
C# が止まっていたのではない。止まっていたのは自分の方だったのだ。
ここから、
“LINQ の試練” が始まる。
第2章:再会 — 初期LINQを思い出す
LINQ は、私にとって最初から特別な存在だったわけではない。
むしろ登場したころは、どこか扱いに困る “器用貧乏な新入り” だった。
SQLと似ていた初期LINQ
C# 3.0(.NET 3.5)で LINQ が世に出たとき、
たしかにその書き方は魅力的に見えた。
from x in items
where x.Age > 20
select x.Name
SQL っぽい。
だけど SQL と比べると、どこか惜しい。
- JOIN は妙に冗長
- GROUP BY はクセが強い
- 匿名型は便利だけど取り扱いに悩む
- “SQL風” なのに SQL ほど自由ではない
そんな微妙な距離感があった。
データ取得後しか活かせなかった制約
当時の LINQ を語るときに欠かせないのが、これだ。
DB のデータは、いったん全部リストや配列に読み込まないと LINQ が使えない。
つまり、
- SQLで抽出
- → List<T> に積む
- → ようやく LINQ のターン
という流れ。
業務システムの現場では、
- 桁の大きいデータが普通
- 全件は要らない
- まずはサーバ側で絞りたい
- ネットワーク越しは遅い
- メモリには積みたくない
──という“当たり前の事情”がある。
だから、結局のところ当時の結論はこうなりやすかった。
「それなら最初から SQL で絞ればいいじゃないか」
初期の LINQ はこう見られていた。
- DataTable の .Select() の強化版
- コレクション操作には便利
- でも業務アプリの主役ではない
- “あれば助かるけど、なくても困らない”
私の中でも、長らく
LINQ = SQL風で少し便利な補助機能
というイメージのままだった。
C# から少し距離を置いていたあの頃、
LINQ に対する印象は完全にそこで止まっていた。
こうして初期の LINQ は、
“SQLライクに書けるちょっと便利な構文” という位置づけに落ち着いていた。
――少なくとも、当時の私はそう思っていた。
第3章:進化 — 新たなる技・メソッド型LINQ
久しぶりに C# の世界へ戻ってきて、
まず圧倒されたのは LINQ の“姿”が完全に変わっていたことだった。
画面に並ぶ構文は、かつて触っていた LINQ とはまるで別物。
メソッドチェイン × ラムダ式 × 無名型。
「……誰?」
「これ、本当に LINQ?」
昔は SQL を意識した“書きやすい構文”という印象だった。
しかし、目の前の LINQ はもっと機能的で、もっと攻撃的で、
完全に 新しい武器 へと進化していた。
🔧 ジェネリック(防具)+ 拡張メソッド(師匠)+ 無名型(舞台装置)
LINQ の華やかな登場の裏には、三人の“影の立役者”がいた。
1. ジェネリック ― 安心して戦える防具
var list = new List<int>(); // int 以外は入らない安心の“防具”
ジェネリックがなければ、LINQ の表現力を安全に活かすことはできない。
防具があるから武器を振るえる ── まさにその関係だった。
2. 拡張メソッド ― 新しい技を教えてくれる師匠
LINQ の裏では、Where や Select といった“技”が拡張メソッドとして動いている。
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, bool> predicate)
{
...
}
既存クラスに自然な “技” を授けたことで、
LINQ のメソッドチェインはスムーズに流れるようになった。
3. 無名型 ― ラムダ式を本気で活かす舞台
var items = list.Select(x => new { x.Id, x.Name });
データ形状に合わせた“即席の型”を自然に作れることで、
LINQ の柔軟性は一気に跳ね上がった。
🔗 メソッド型 LINQ の衝撃
こうして積み重なった結果、目の前にいたのは、もはや誰が見ても“別次元”の LINQ だった。
var names = list
.Where(x => x.Age > 20)
.Select(x => x.Name)
.OrderBy(x => x);
最初の感想はただ一つ。
「ここまで進化したのか……」
昔の「SQL風の補助機能」の影はほとんどなく、
今の LINQ は データを流れとして読む 完成されたパイプラインだった。
list
→ Where
→ Select
→ OrderBy
→ ToList()
この直感的な読みやすさは、もはや C# の“思考の型”そのものだ。
🎯 LINQの進化は三つの力で成立していた
- ジェネリック が安全性という防具を与え
- 拡張メソッド が技を授け
- 無名型 が柔軟に戦える舞台をつくった
この三位一体が揃ったことで、
LINQ は「SQL風記法」から
“C# の世界観に最適化されたデータ操作の武器” へと進化した。
第4章:相棒 — VSとC#の進化
LINQ に驚かされたのは言うまでもないが、
よく考えてみれば、あの複雑さを素手で書くのはほぼ無理だ。
- メソッドチェイン
- ラムダ式
- 無名型
- 拡張メソッド
これらを インテリセンス無しで 手入力し、
都度ドキュメントを引きながら引数を確認していたら、
それだけで一日が終わりかねない。
久しぶりに Visual Studio を立ち上げて、最初に思ったのはこうだ。
「あれ、こんなに“教えてくれる子”だったっけ?」
インテリセンスがなければ、そもそも書けない世界
昔の C# でもインテリセンス自体はあった。
けれど今のそれは、もう 別物と言っていい。
- メソッド名を数文字打てば候補が出る
- チェインの続きも提案してくれる
- ラムダ式の引数の型まで推論してくれる
- カーソルを置けば引数の説明がポップアップする
これがあるからこそ、次のようなコードを
「まぁ書いてみるか」と思える。
var result = list
.Where(x => x.Age > 20)
.Select(x => x.Name)
.OrderBy(x => x);
もしインテリセンスがなければ、
- メソッド名のスペルを毎回思い出し
- 引数の順番を MSDN で都度確認し
- 拡張メソッドかインスタンスメソッドかで迷い
- そもそも何が呼べるのか分からず
コードの“入力量”も、“引数確認にかかる時間”も跳ね上がるだろう。
「インテリセンスが無ければ、今の LINQ スタイルは広まらなかっただろうな。」
そう素直に思った。
VSは「重いIDE」から「支えてくれる相棒」に変わっていた
VB6 のころに感じた .NET/Visual Studio の印象は、
- ソースが大きくなる
- ランタイムのインストールが面倒
- IDE が重い
という、何かと大げさで重たい存在だった。
ところが今の Visual Studio は、
確かに昔より高機能になっているのに、
体感としてはむしろ 作業が軽くなっている。
- 使っていない
usingを自動で整理してくれる - 型名を書くだけで
usingを追加してくれる - 変数名のリネームが一括で安全にできる
- メソッド抽出やローカル関数化もワンクリック
昔なら、
「一度書いたら、怖くてもう触りたくない」
というようなコードも、
今は “ちょっと試しに直してみるか” と手を出せる。
道具がここまで支えてくれると、
「C# が多少複雑になっていても、まぁ何とかなるか」
と思えてくるから不思議だ。
引数を覚えなくていい世界
もう一つ、インテリセンスのおかげで
地味にありがたいのが 「引数を覚えなくていい」 ことだ。
-
OrderByとThenByの違い -
SelectManyの引数の形 -
GroupByのキーと要素の指定方法 -
DistinctByがいつの間にか生えていること
昔なら全部ドキュメントとにらめっこだった。
今は、メソッド名を打ち始めれば候補が出て、
カーソルを持っていけば その場で引数一覧が出る。
「覚えていなくても、何とかなる。」
これが、浦島太郎エンジニアにとっては本当にありがたい。
道具の進化が、「まだやってみようかな」を支えてくれる
C# そのものは、
昔と比べものにならないくらい高度になった。
- LINQ
- ラムダ式
- 拡張メソッド
- 非同期処理
- パターンマッチング
- null 許容参照型
どれも、昔の感覚で見ると “一段上の世界” に感じる。
それでも今の Visual Studio とインテリセンスがあれば、
- とりあえず書いてみる
- 補完と引数ヒントを見ながら組み立てる
- 間違えたらリファクタリングで直す
というサイクルを、そこまで怖がらずに回せる。
「道具がここまで進化しているなら、もう少し C# を触ってみてもいいかもしれない。」
そう思わせてくれるだけの力が、今の VS にはある。
第5章:試練 — xUnitとの出会い
C# の世界に戻ってきて驚いたのは、
言語やIDEだけではなかった。
テストの世界も、知らないうちにまったく別の方向へ進化していた。
かつては NUnit と MSTest が“二大勢力”だったけれど、
久しぶりに触ってみた xUnit は、まさに別物だった。
並列実行の衝撃とログのクセ
最初に戸惑ったのは **xUnit の「並列実行がデフォルト」**という仕様だ。
テストを実行したら――
いきなり全部のテストが並列で動き始める。
「お、おま……勝手に走るな!」
というのが正直な初見の感想だ。
もちろん理屈は分かる。
- 1テスト=独立
- 共有状態を持たない
- 副作用がない
- だから並列にしても問題ない
テスト設計の“理想”を先に突きつけてくる感じだ。
でも、久しぶりの C# でテストを書こうとしていた私は、
この“正しすぎる仕様”にしばらく振り回された。
並列実行された結果、すぐに困るのが ログの混線 だ。
- このログ、どのテストのもの?
- どこで出力された?
- そもそも時系列が成立しない?
- コンソールに流したら“事故”確定
xUnit 自体は「ログはテストごとに持ってね」という思想なので、
ログをきちんと使いたい場合は 一工夫が必要になる。
ITestOutputHelper- テストクラスインスタンスごとのログ出し
- テストケースごとにログを紐づける
- DI っぽく受け取る
- 静的ログは事故の元
慣れてしまえば合理的なのだが、
最初は「なんで普通に Log クラスで書けないの?」と思ってしまう。
xUnit「――だってそれ、副作用でしょ?」
私「はい(正論で殴られた)。」
というやり取りをした気分になる。
テストしやすい設計へ
xUnit は、昔のテストフレームワークと比べると
“制限”が多いように感じる。
- テストメソッドに順番なし(順序依存NG)
- テストクラスを使い回さない
- static 状態は持つな
- 副作用は即アウト
- ログもテストごとに分離
最初は「なんて不便なんだ」と思った。
でも触っているうちに分かってきたのは、
xUnit は「テストを書きやすくする」ではなく、「テストしやすい構造を強要してくる」フレームワークだ。
ということだ。
つまり、
- 役割が曖昧
- 副作用がある
- 状態を持ちすぎ
- テスト対象が肥大化
- 依存しすぎる
こういう “テストしづらいコード”を許してくれない。
これは厳しいけれど、慣れてくるとむしろ心地よい。
xUnit に触れて一番響いたのは、
次のシンプルな事実だった。
テストしやすいコードは、モジュールが“一役”に徹している。
つまり、
- 状態を持ち回さない
- 副作用を分離する
- 入力 → 処理 → 出力 が一貫している
- 依存先も少ない
- 振る舞いが読みやすい
- テストが書きやすい=変更に強い
という状態だ。
言われてみれば当たり前のことだが、
xUnit はこの“当たり前”を
テストを書くたびに突きつけてくる。
- 「この関数、テストしづらい?」
- → それはモジュールが一役に徹していない証拠
- 「このモジュール、副作用持ってる?」
- → 設計をやり直すべきサイン
つまり、xUnit に出会ったことで、
テストを書くことが、設計のレビューになる。
という体験をした。
久しぶりに触った xUnit は、
当初は不便でクセがあった。
でも、いざ向き合ってみると
それがすべて “正しいコード”への道しるべだった。
良い設計は、良いテストで測れる。
良いテストは、良い設計を求めてくる。
これは、昔の環境では気づかなかった感覚だ。
そして、私は少しだけ思った。
「昔より C# の世界で生きるの、悪くないな。」
第6章:共闘 — 「ライバル」から「相棒」へ
ここまでの旅を振り返ると、
C# との関係は大きく変わっていた。
再会したときは、
まるで かつては肩を並べていた旧友に、一人だけ先へ行かれてしまったような気持ちだった。
知らない技、知らない文法、進化の速度についていけず、
“完全に別世界の住人” に見えた瞬間すらあった。
しかし、ジェネリック、拡張メソッド、無名型、ラムダ式、非同期、
そして xUnit の並列テスト……。
ひとつひとつ試練を越えるうちに、
あることに気づき始めた。
C# は敵ではない。
こちらを鍛えるために技を見せつけていたのだ。
自分が離れていたあいだも、あいつは休まず修行を続けていただけ。
その“積み重ねの差”が、最初はただの脅威に見えていたのだ。
しかし技ひとつひとつと向き合ううちに、
その背後にある思想が見えてくる。
- 凝集度を保つための仕掛け
- 副作用を外に追い出すための機能
- 読みやすさを貫くための記法
- テストしやすさを前提にした文化
- 「良いコードを書かせる」ための進化の方向性
どれも 派手さのための強化ではなく、筋の通った理由がある。
やがてこう思うようになった。
これは挑発ではなく、同じ土俵に引き上げるための挑戦状だったのか。
手強いが、頼れる相棒へ
これまでの試練で、ようやく並んで走れるようになった。
そしてふと気づく。
もう“敵”ではない。
でも“師匠”でもない。
これはもう、“相棒”だ。
少しでも気を抜くと置いていかれるし、
雑に扱えばすぐ痛い目を見る。
それでも、こちらがきちんと向き合いさえすれば、
これほど心強い相棒はいない。
逃げるでもなく、
盲目的に崇拝するでもなく、
ちょうどよい距離で互いに高め合っていける存在。
「よし、もう一クエストだけ行くか。」
そう思わせてくれる相手が、
いまの C# だった。
第7章:旅路 — これからも続くC#との冒険
未来のC#はどうなっていくのだろう
20年前、MS の技術者は今の C# を
どこまで想像できていたのだろうか。
おそらく、方向性は見えていた。
けれど、ここまで豊かで柔らかく、
開発者に寄り添う言語になるとは
誰も具体的には描けなかったはずだ。
技術はいつも、
“小さな改良の積み重ね” の結果として、
突然、まったく別の姿になる。
今回、久しぶりに C# の世界を歩き直して、
その事実をあらためて感じた。
そして、旅はこれからも続く
この物語は、
C# に再び触れた私の
短い “回想録” にすぎない。
でも、技術者としての旅は終わらない。
- また何か新しい書き方が生まれ
- 新しい道具が現れ
- 新しい「なんだこれ?」に出会い
- そしてまた、驚いたり笑ったりしながら
- コードを書く日々が続いていく
何度置いていかれても、
戻ってきたときにまた楽しく迎えてくれるなら。
C# の世界は、
そんな優しい場所になっていた。
最後にひとつだけ、あなたに問いかけたい。
あなたにとって C# は、もう相棒だろうか?
それとも、まだライバルだろうか?
この答えは、あなたがこれからどんな C# の旅を歩むかによって、少しずつ変わっていくのだと思う。
Discussion
LINQ to SQL は初期から 使えた様な……?
LINT to Objects の話をしておられます?
記憶を遡って書いているので、厳密に分けてなくて、そのあたりはあいまいですね。
イメージしてるのは、LINQ to Objectsのほうですかね。
LINQ to SQLでも、テーブルやクエリーを一旦読み込んでそれに対してLINQを掛ける?ものだと理解しています。貧弱なCPU/メモリ環境だったので、やたら重たかった記憶がうっすらと。
初動でフィットしなくて深堀しなかったから使い方ミスってたかもしれませんが。
調べたら、LINQ to SQLを勘違いしてました、昔から。
SQLServerは使ってなかったので、LINQ to SQLは実質使えてなかったです。
なので、ADOで読み込んでからLINQ使う感じしかイメージしていなかったです。
申し訳ないです。 他DB向けだと LINQ to Entities がありましたね。
というか EntityFramework の語源は多分こっちですね。
誤解があるといけないのですが、このあたりの機能は実は Visual Studio の機能ではなく、
VSCodeや一部の高機能なテキストエディタ(vimとか)でも可能です。
今のC#のコンパイラ(言語サーバー)は「Roslyn」というものになっており、
ふだんのC#のコーディングに関係するところ(書き味や体験)はVSだけのものではありません。
VSじゃないとできないのは高度なデバッグ機能とか、画面のデザイナーとかぐらいですね。
「C#書くならVisual Studio」というのもわりと過去の話かな、と思います!