c#/.netがやっていること 第二版

216
C# や .NET Framework やややややややや ややや やや やや ++C++; // 未未未未未 C

Upload: -

Post on 28-May-2015

24.941 views

Category:

Technology


0 download

DESCRIPTION

2014/6/28 CLR/H in Tokyo 第3回 にて登壇

TRANSCRIPT

Page 1: C#/.NETがやっていること 第二版

C# や .NET Frameworkがやっていること

第二版

岩永 信之

++C++; // 未確認飛行 C

Page 2: C#/.NETがやっていること 第二版

今日の内容

• C# や .NET Framework がやっていること• どうしてそういう機能がある• ( 自前で実装するなら ) どうやればできる• ( 自前でやる上で ) 気を付ける必要がある点

Page 3: C#/.NETがやっていること 第二版

注意

• ベースは 3 月に Boost 勉強会でやった内容• 通常の 3 倍速で 1 時間で話した

• 本来、 1 時間で話す内容じゃない• かなりの前提知識を仮定

• 3 時間話します• いくつか追記• 前提をだいぶ緩めたので背景説明など• .NET vNext がらみ追加• 5 月の非同期勉強会での内容も少しマージ

同じような内容でも回数重ねてこなれてきてたり

Page 4: C#/.NETがやっていること 第二版

目次• 1 時間目• メモリ管理• マルチ タスク管理

• 2 時間目• メタデータ

• 3 時間目• C# 言語機能

1

2

3

Page 5: C#/.NETがやっていること 第二版

1 時間目• メモリ管理• ヒープ自動管理• 境界チェック• ドメイン分離

• マルチ タスク管理• スレッドと非同期処理

1

2

3

Page 6: C#/.NETがやっていること 第二版

“Managed”

• .NET Framework といえば Managed• .NET がいろいろ管理

• メモリ リーク防止• セキュリティ ホール防止

• v1 リリース後、一番進歩が激しいのは非同期• 効率的なタスク管理• I/O-bound な非同期処理

Page 7: C#/.NETがやっていること 第二版

Garbage Collectionメモリ リークをなくすために

Page 8: C#/.NETがやっていること 第二版

メモリ管理 (1)

• スタック• 一番上に積む・一番上から取り出す

• ローカル変数用• 管理は楽• スコープがはっきりしてないと使えない

int x = 1;int y = 2;int z = 4;int sum = x + y + z;

1 1

2

load 1 load 2

3

add

3

4

load 4

7

add

Page 9: C#/.NETがやっていること 第二版

メモリ管理 (2)

• ヒープ• 連結リスト構造で任意の位置にメモリ確保

• 動的なメモリ利用できる• 管理が大変

• 消し忘れ ( メモリ リーク ) があり得る

使用中かどうかのフラグ

☑□ □ ☑

次のヒープの位置

前のヒープの位置

… …

Page 10: C#/.NETがやっていること 第二版

値型と参照型

• 値型• int とか byte とか• struct とか enum と

かスタック ヒープ

他の型

他の型

スタック上や、他の型の中に直接埋め込まれる

• 参照型• string とか• class とか delegate

とかスタック ヒープ

スタック上や他の型の中には参照情報だけある

ヒープ上に置かれる

Page 11: C#/.NETがやっていること 第二版

ヒープ管理

• 怠ると• 徐々にアプリが重くなる• 長時間稼働させると突然の死

• その割に大変• ( 自動管理がない頃 )

プログラミングの苦労の半分くらい占める• 本当にやりたいことに注力できてない

• 自動化したい不要なオブジェクトの回収 = ゴミ を集め (garbage

collection)

Page 12: C#/.NETがやっていること 第二版

代表的な手法

• 参照カウント• 生成・コピー代入のたびにカウントを +1• 変数がスコープ外れるたびに - 1• カウントが 0 になったらメモリ削除

• Mark & Sweep• ルート†からたどれるオブジェクトに印を付ける

• 印のついてないオブジェクトを削除

† スタックや静的フィールド中のオブジェクトなど参照の起点となる場所

mark

sweep

Page 13: C#/.NETがやっていること 第二版

Mark & Sweep

• ルートから参照をたどる

Mark

ヒープルート

Sweep

使用中オブジェクト

未使用オブジェクト

Page 14: C#/.NETがやっていること 第二版

Compaction

• GC 時にオブジェクトの位置を移動

使用中オブジェクト

未使用オブジェクト

隙間をなくすように移動

後ろが丸ごと空くので次のメモリ確保が楽

Page 15: C#/.NETがやっていること 第二版

Generation

• 移動したオブジェクトはしばらくノータッチ

Gen1 Gen0

Gen0

しばらくここはノータッチ

この範囲でだけメモリ確保・

GC

オブジェクトの寿命統計的に• 短いものはとことん短く• 長いものはとことん長い

一度 GC かかったオブジェクトはしばらく放置

Page 16: C#/.NETがやっていること 第二版

.NET Framework の GC

• .NET Framework の GC は Mark & Sweep• Compaction あり• 世代別 (3世代、 Gen0~ 2)• Background スレッドで並列実行

Page 17: C#/.NETがやっていること 第二版

Mark & Sweep と参照カウント•比較• 一長一短ある

Mark & Sweep 参照カウント

メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す× カウント用の領域が追

加で必要

変数のコピー ○ ポインターのコピー × ポインターのコピーに加え、参照数のカウント アップ

メモリ解放 × Mark や Compactionに時間がかかる

× 負担が 1 か所に集中

○ カウントが 0 になった時に delete するだけ

× 循環参照が苦手

Page 18: C#/.NETがやっていること 第二版

Throughput

• トータルの性能 (throughput) は Mark & Sweep の方がいい

Mark & Sweep 参照カウント

メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す× カウント用の領域が追

加で必要

変数のコピー ○ ポインターのコピー × ポインターのコピーに加え、参照数のカウント アップ

メモリ解放 × Mark や Compactionに時間がかかる

× 負担が 1 か所に集中

○ カウントが 0 になった時に delete するだけ

× 循環参照が苦手まとめてやる方がバラバラにやるよりスループットは

いい

頻度が高い操作なのでここの負担が大きいと全体の性能落ちる

特に、スレッド安全を求めるときついたかがインクリメントでも、 atomic性保証するとそこそこの負担

参照局所性も高くなってキャッシュが効きやすい

Page 19: C#/.NETがやっていること 第二版

短所の軽減

•短所も、まったく打つ手がないわけじゃない

Mark & Sweep 参照カウント

メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す× カウント用の領域が追

加で必要

変数のコピー ○ ポインターのコピー × ポインターのコピーに加え、参照数のカウント アップ

メモリ解放 × Mark や Compactionに時間がかかる

× 負担が 1 か所に集中

○ カウントが 0 になった時に delete するだけ

× 循環参照が苦手

.NET 4以降、 Background スレッ

ド実行してる C++ 11 的には「move semantics活用してね」

Page 20: C#/.NETがやっていること 第二版

自前メモリ管理との混在

• スコープが短いならスタックにとればいいのに• C# は class は必ずヒープ、 struct はスタックに

なる• 使い分けれない

• Mark & Sweep 管理領域内のポインターを管理外に渡すときには注意が必要• Compaction でメモリ移動しないように「ピン止め」が必要• 結構ガベージ コレクションのパフォーマンス落とす

Page 21: C#/.NETがやっていること 第二版

おまけ : 他の言語

• C++: 主に参照カウント方式のライブラリ利用• 型がはっきりしない言語では Mark & Sweep しにくい

• できなくはないけども「保守的」になる ( 効率落とす )• 数値を無差別にポインター扱いして Mark する

• Python とか : 参照カウントと Mark & Sweep の併用

• ○ 循環参照問題避けつつ、負担を 1 か所に集中させない• × 性能的には悪いとこどり

• Go: 割当先を自動判別• スコープ内で完結してたらスタックに、さもなくばヒープに• これはこれでスタックの浪費激しそうなんだけども…

• なので、可変長スタック持ってる

Page 22: C#/.NETがやっていること 第二版

注意 : GC あってもリーク

• 長時間生きているオブジェクト†が参照持ったままになると、 GC対象になれない

† ワーストケースはアプリ稼働中ずっと生きているオブジェクト

class View : UserControl{    public View(Model model)    {        model.PropertyChanged += (sender, e) =>        {            //  データの表示を更新 };    }}

よく起こり得る例 ビューは画面遷移で消える

モデルはずっと生きてる

イベント購読で、モデルがビューの参照持

つ解除 (-=) しないとメモリ リーク

Page 23: C#/.NETがやっていること 第二版

ポイント

• メモリ管理は大変なので自動化• Mark & Sweep• いろいろ賢いアルゴリズム搭載してるし、ほとん

どの場合、任せた方がいい

• GC あってもメモリ リークは起こり得るので注意

Page 24: C#/.NETがやっていること 第二版

境界チェック意図しないメモリ領域にはアクセスさせない

Page 25: C#/.NETがやっていること 第二版

配列の境界チェック

• .NET の配列は厳しい• 常に境界チェックしてる

• 要は buffer overrun† 防止• ちなみに JIT最適化で不要な境界チェック消してる

( 明らかに境界を侵さないものはチェック不要 )• for (var i; i < a.Length; ++i) とか• foreach (var item in a) とか

配列 a

OutOfRange

OutOfRange

a[-1] a[length+1]

† クラッシュやセキュリティ ホールの温床

Page 26: C#/.NETがやっていること 第二版

unsafe

• とはいえ、 C# にもポインターあるんだけども

var x = new[] { 1, 2, 3, 4, 5 };unsafe{ fixed(int* px = &x[0]) { Console.WriteLine(px[100]); }}

メモリ移動の防止(ピン止め)

範囲チェックなし

安全でないコードこの中でだけポインター利用可能

buffer overrun やりたい放題

Page 27: C#/.NETがやっていること 第二版

unsafe でも制限付き

• class ( 参照型 ) はアドレス取れない• 仮想メソッド テーブル (vtable) とか入ってるし• 親クラス側のレイアウト変更の影響受けるし

class Class{ public int x; public int y;}

var c = new Class();fixed (void* p = &c) { }  // errorfixed (void* p = &c.x) { } // OKfixed (void* p = &c.y) { } // OK

ピン止め必須 • 値型のメンバーのアドレスは取れる

• オブジェクト自体のアドレスは取れない

Page 28: C#/.NETがやっていること 第二版

unsafe でも制限付き

• struct ( 値型 ) はアドレス取れる• ただし、メンバーが全部値型の時のみ†• ↑「 unmanaged 型」と呼ぶ

struct UnmanagedStruct{ public int x; public int y;}

var u = new UnmanagedStruct();void* p1 = &u;  // OKvoid* p2 = &u.x; // OKvoid* p3 = &u.y; // OK

メンバーが全部値型

無制限にアドレス取れる

† 正確には、再帰的に全子要素が値型、かつ、型パラメーターも値型

Page 29: C#/.NETがやっていること 第二版

unsafe でも制限付き

• struct ( 値型 ) であっても制限付き• メンバーに 1 つでも参照型を含むとダメ• ↑「managed 型」と呼ぶ

struct ManagedStruct{ public int x; public string y;}

var u = new ManagedStruct();void* p1 = &u;  // errorvoid* p2 = &u.x; // OKvoid* p3 = &u.y; // error

メンバーに参照型が 1

本体のアドレス取れない値型のメンバーのところだけはアドレス取れる

Page 30: C#/.NETがやっていること 第二版

ポイント

• unsafe• 危険なことは基本認めない

• 面倒な追加の構文を要求• コンパイル オプションでも /unsafe の明記必須

• fixed• ガベージ コレクションとの兼ね合い

• Compaction阻害になるので注意

•「 Unmanaged 型」に限る• 実装依存な部分 (vtable とか)や、

型の定義側の変更が利用側に極力影響しないように

Page 31: C#/.NETがやっていること 第二版

AppDomain実行環境の分離セキュリティ保証

Page 32: C#/.NETがやっていること 第二版

コンポーネントの連携

• 他のアプリの機能を自分のアプリから呼びたい• Word とか Excel とかのドキュメントを出力

•サーバー上に自分のアプリを配置したい• IIS 上に( ASP.NET)• SQL Server 上に

• ( 必ずしも ) 信用できない• セキュリティ保証• 参照先のクラッシュにアプリ /サーバーが巻き込まれないように

• コンポーネントのバージョン アップ

Page 33: C#/.NETがやっていること 第二版

AppDomain

•単一プロセス内で、分離された複数の実行領域 (domain) を提供

•「分離」• plugin: 動的なロード / アンロード• security: AppDomain ごとに異なる権限付与• isolation: 別メモリ空間

AppDomain 1

AppDomain 2

プロセス

必ずしも信用できないコードを安全に、動的に呼び出し

Page 34: C#/.NETがやっていること 第二版

ドメイン間には壁がある

•別メモリ空間に分離されてる

AppDomain 1 AppDomain 2

互いに独立

Page 35: C#/.NETがやっていること 第二版

ドメイン間の通信

• マーシャリング (marshaling)• marshal (司令官、案内係 ) の命令通りにしか壁を超えれない

AppDomain 1 AppDomain 2

• 司令がダメといったら通れない• 司令の指示通りの形式にいったんシリアライズしないといけない

Page 36: C#/.NETがやっていること 第二版

AppDomain 1 AppDomain 2

ドメイン間の通信

•ダメな例

これを B に渡したいとして

あるオブジェクト

Page 37: C#/.NETがやっていること 第二版

AppDomain 1 AppDomain 2

ドメイン間の通信

•ダメな例

shallow copy なんてしようもんなら

A側のメモリへの参照が残る

Page 38: C#/.NETがやっていること 第二版

AppDomain 1 AppDomain 2

ドメイン間の通信

• よい例

serializedeserializ

e

{ {1, 2}, {3, 4}, {5, 6}}

この辺りがマーシャリング

一度シリアライズ 必ず deep copy

Page 39: C#/.NETがやっていること 第二版

.NET のマーシャリング

• 2種類• marshal by ref

• 参照を渡すんだけど、メモリ領域は直接触れない• メソッド越しにしか操作しちゃいけない• 「このメソッドを呼んでくれ」っていうメッセージだけ

がドメインを超えてわたって、実際の実行は相手側ドメインで ( プロキシ実行 )

• marshal by value• 値をシリアライズして相手側に渡す• 規定では、 BinarySeralizer を使用

private フィールドまで含めてリフレクションで内部状態を取得してシリアライズ

Page 40: C#/.NETがやっていること 第二版

.NET のマーシャリング (文字列 )•文字列は特殊扱い

(文字コード変換が不要な場合 )• marshal by value みたいにシリアライズを経な

い• marshal by reference みたいにプロキシ実行し

ない• 参照を渡して、メモリを直接読んでもらう

• immutable かつ range-check 付きに作ってある• 変更不可なので参照を渡しても安全

• COM (ネイティブ ) に対して文字列を渡す時すらこの方式

Page 41: C#/.NETがやっていること 第二版

AppDomain 1 AppDomain 2

その他のコスト

• マーシャリング以外にもいくらかコストが• 例えばライブラリの読み込み

Library X Library X

別ドメインに全く同じライブラリを読み込んでも、それぞれ別イメージが作られる

Global Assembly Cache (GAC) にある DLL だけは別GAC 中のは同じメモリ イメージが共有される

Page 42: C#/.NETがやっていること 第二版

おまけ : 他の言語

• COM なんかはマーシャリングやってる• というか「 COM の頃から」やってる• .NET の前身なので

• さもなくば、プロセス分離してプロセス間通信• OS特権がないと他プロセスのメモリにはアクセ

スできない• 安全だけどかなり高負荷

• RPC† とか一時期流行ったけども廃れた

† ただの関数呼び出しに見えるコードで、内部的にプロセス間通信する知らないうちに性能落とすとか、リモート側でのエラーが処理しにくいとか

Page 43: C#/.NETがやっていること 第二版

ポイント

• プラグインとかやるなら「分離」が必須• とはいえ、プロセス分けると負担が大きすぎる

• プロセス内で分離を保証する仕組み• AppDomain• マーシャリング

• それなりにコストがかかるけども、セキュリティには変えられない

Page 44: C#/.NETがやっていること 第二版

スレッド応答性のよいプログラムを書くためにOS やアプリ全体をフリーズさせないために

Page 45: C#/.NETがやっていること 第二版

マルチタスク

• コンピューター内で複数のタスクが同時に動作• CPU コア数に制限されないタスク 1 タスク 2 タスク 3 …

タスクの動作期間

実際に CPU を使って動いている期間

1 つの CPU コアを複数のタスクがシェアして

る問題は• どうやって他のタスクに CPU を譲るか• 誰がどうスケジューリングするか

Page 46: C#/.NETがやっていること 第二版

2種類のマルチタスク

† preemptive: 専買権を持つ、横取りする※cooperative

プリエンプティブ†

• ハードウェア タイマーを使って強制割り込み• OS が特権的にスレッド切り替えを行う• ○利点 : 公平 ( どんなタスクも等しく OS に制御奪われる )• ×欠点 : 高負荷 (切り替えコストと使用リソース量が多い )

協調的※

• 各タスクが責任を持って終了する• 1 つのタスクが終わるまで次のタスクは始まらない• ○利点 : 低負荷• ×欠点 : 不公平 (1 タスクの裏切りが、全体をフリーズさせる )

なのでスレッドはこっち

これが致命的

ただ、問題はこれ

Page 47: C#/.NETがやっていること 第二版

問題 : スレッドは高コスト†

•細々としたタスクを大量にこなすには向かない

for (int i = 0; i < 1000; i++){ var t = new Thread(Worker); t.Start();}

大量の処理をスレッド実行

リソース消費大切り替え頻発

† スレッドごとに数 MB のメモリを確保したりスレッド切り替えに OS特権が必要だったり文脈が切り替わっちゃうのでキャッシュ ミスしたり

Page 48: C#/.NETがやっていること 第二版

解決策 : スレッド プール

• スレッドを可能な限り使いまわす仕組み• プリエンプティブなスレッド数本の上に• 協調的なタスク キューを用意

スレッド プール

キュータスク

1タスク

2

数本※のスレッドだけ用意

空いているスレッドを探して実行(長時間空かない時だけ新規スレッド作

成)

新規タスク

タスクは一度キューに溜め

※ 理想的には CPU のコア数分だけ

Page 49: C#/.NETがやっていること 第二版

スレッド プールの性能向上

• Work Stealing Queue• lock-free 実装†なローカル キュー• できる限りスレッド切り替えが起きない作り

ローカルキュー 1

ローカルキュー 2

スレッド 1 スレッド 2

グローバルキュー

①スレッドごとにキューを持つまず自分用のキューからタスク実行

②ローカル キューが空のとき、他のスレッドからタスクを奪取

† 今回は詳細割愛

Page 50: C#/.NETがやっていること 第二版

スレッド プールの性能向上

• Work Stealing Queue• lock-free 実装†なローカル キュー• できる限りスレッド切り替えが起きない作り

ローカルキュー 1

ローカルキュー 2

スレッド 1 スレッド 2

グローバルキュー

①スレッドごとにキューを持つまず自分用のキューからタスク実行

②ローカル キューが空のとき、他のスレッドからタスクを奪取

ポイント• スレッドは高コスト• Thread クラスはこっち

• スレッド プールの利用推奨• Task クラスはこっち

† 今回は詳細割愛

Page 51: C#/.NETがやっていること 第二版

問題 : CPU の外の世界は遅い

• 実行速度が全然違う

ALU

メイン・メモリ

レジ

スタ

ーCPU 周辺機器

数千~下手すると数万、数億倍遅い

>>>

Page 52: C#/.NETがやっていること 第二版

2種類の負荷

• CPU-bound (CPU が性能を縛る )• マルチコア CPU の性能を最大限引き出したい• UI スレッドを止めたくない

• I/O-bound (I/O※ が性能を縛る )• ハードウェア割り込み待つだけ• CPU は使わない• スレッドも必要ない

※ Input/Output: 外部ハードウェアとのやり取り ( 入出力 )

Page 53: C#/.NETがやっていること 第二版

I/O完了待ち

• I/O-bound な処理にスレッドは不要

あるスレッド

要求

応答

この間何もしないのにスレッドを確保し続けるのはもったいない

Page 54: C#/.NETがやっていること 第二版

解決策 : I/O完了ポート※

• スレッドを確保せず I/O を待つ仕組み• コールバックを登録して、割り込みを待つ• コールバック処理はスレッド プールで

スレッド プール

タスク1

タスク2…

※ I/O completion port

あるスレッドアプリ

I/O完了ポート

ハードウェア

I/O開始 I/O完了

コールバック登録

コールバック登録後、すぐにスレッド上での

処理を終了

割り込み信号

Page 55: C#/.NETがやっていること 第二版

I/O完了ポート※

• スレッドを確保せず I/O を待つ仕組み• コールバックを登録して、割り込みを待つ• コールバック処理はスレッド プールで

スレッド プール

タスク1

タスク2…

※ I/O completion port

あるスレッドアプリ

I/O完了ポート

ハードウェア

I/O開始 I/O完了

コールバック登録

コールバック登録後、すぐにスレッド上での

処理を終了

割り込み信号

ポイント• I/O-bound な処理にスレッドを使っちゃダ

メ• I/O 用の非同期メソッドが用意されてる

( 内部的に I/O完了ポートを利用 )

Page 56: C#/.NETがやっていること 第二版

問題 : スレッド安全保証

• スレッド安全なコードは高コスト

• いっそ、単一スレッド動作を前提に• メッセージ ポンプ

MSG msg;while (PeekMessage(&msg, (HWND)NULL, 0, 0, PM_REMOVE)){    TranslateMessage(&msg);    DispatchMessage(&msg);}

こんな感じのループが単一スレッドで動作

他のスレッドからメッセージをキュー越しに受け取って処理

Page 57: C#/.NETがやっていること 第二版

典型例 : UI スレッド

• GUI はシングル スレッド動作 (UI スレッド )†• ユーザーからの入力受け付け• 画面の更新

UI スレッドユーザー

からの入力

OK

グラフィック

更新

他のスレッド

処理中は応答不可

他のスレッドからは更新不可

† C#/.NET に限らず、だいたいの言語・環境で GUI はシングル スレッド動作

Page 58: C#/.NETがやっていること 第二版

矛盾

単一スレッドからしかUI更新でき

ないそのスレッドを止めると UI フリー

シングル スレッド推奨

マルチ スレッド推奨

OK

Page 59: C#/.NETがやっていること 第二版

解決策 : メッセージ配送

1. スレッド プールで重たい処理2. UI スレッドに処理を戻してから UI更新

UI スレッド

OK

更新

他のスレッド

重たい処理

Dispatcher.Invoke

Task.Run

※ dispatcher: 配送者

渡す役割を担うのがディスパッチャー※

他の GUI フレームワークだと event queue とか handler とかいう名前で提供されたりするものの、やってることは一緒

いったん UI スレッドにメッセージを渡

Page 60: C#/.NETがやっていること 第二版

おまけ : 他の言語

• スクリプト言語の類はだいたいスレッド機能持ってない• GUI を前提にしていないか• GUI がフリーズするほどの処理を書くことを前提

にしていないか

• C++ とかは最近 (11 で ) ようやく標準化• それまでは、スレッドは結構環境依存

Page 61: C#/.NETがやっていること 第二版

ポイント

•応答性と全体のパフォーマンスとの兼ね合い• スレッド プール

• I/O待ちのためにスレッドは使っちゃダメ• I/O完了ポート

• スレッド安全保証はそれなりに高コスト• シングル スレッドでメッセージ ポンプ• メッセージ配送

Taskクラス

Page 62: C#/.NETがやっていること 第二版
Page 63: C#/.NETがやっていること 第二版

2 時間目• メタデータ• 動的リンク• JIT• PCL

1

2

3

Page 64: C#/.NETがやっていること 第二版

メタデータとは

• 実行可能ファイル中には本来不要なデータ

• プログラムを作るためのデータ• 実行に必要なデータよりもメタ (高次 )

var x = p.X;var y = p.Y;var z = p.Z;

var pp = (byte*)&p;var x = *((int*)pp);var y = *((int*)(pp + 4));var z = *((int*)(pp + 8));

プロパティ名とか 単にプログラムを動かすだけな

ら相対アドレスだけわかればいい

.NET では、こういうメタデータを実行可能ファイルに残す

Page 65: C#/.NETがやっていること 第二版

.NET のメタデータ

• .NET のメタデータ (≒ 型情報 )• DLL にどういう型が含まれるか• どういう型がどういうメンバーを持っているか• 外部のどういう DLL を参照しているか• バージョン情報

• DLL にメタデータを残すことで• プログラミング言語をまたげる• 動的リンクでのバージョン管理ができる

Page 66: C#/.NETがやっていること 第二版

おまけ : 昔と今

• COM の頃• メタデータを自分で書いてた

( 自動化するツールあるけど )• .tlb/.olb ファイル

• .NET Framework• .NET 自体がメタデータの規格を持ってる• C# とかをコンパイルするだけで作られる

Page 67: C#/.NETがやっていること 第二版

おまけ : C++/CX

• C++ Component Extensions• マイクロソフトの C++拡張

C++/CX

素の標準C++ コード

COM コード

メタデータ(winmd)

コンパイル

C++ 内で完結して使う分にはオーバーヘッドな

COM を呼べる言語なら何からでも呼べる

.NET のメタデータと互換

.NET から簡単に呼べる

Page 68: C#/.NETがやっていること 第二版

動的リンク実行可能ファイルにメタデータが含まれていることで

Page 69: C#/.NETがやっていること 第二版

ライブラリ共有

• 前提 : ライブラリは共有する

アプリ A

アプリ B

ライブラリ X

ライブラリ Y

version 1

version 1

Page 70: C#/.NETがやっていること 第二版

動的リンク

• ライブラリ単体での差し替え• セキュリティ ホールや、致命的なバグの修正

アプリ A

アプリ B

ライブラリ X

ライブラリ Y

version 2

version 1

更新不要差し替え

アプリ実行時にライブラリをリンクしなおしてる = 動的リンク

Page 71: C#/.NETがやっていること 第二版

いまどきの事情

• ライブラリはアプリのパッケージに同梱• 動的なリンクいるの?

アプリ A ライブラリ X

ライブラリ Y アプリ B

ライブラリ X

ライブラリ Y

アプリ Aパッケージ アプリ Bパッケージ

別バイナリ・別々に配布別バージョンでも困らない

とはいえ…

Page 72: C#/.NETがやっていること 第二版

差分ダウンロード※ Windows ストア アプリはこういう仕組み持ってる

アプリ A ライブラリ X

ライブラリ Y

アプリ Aパッケージ version 1

version 1アプリ A ライブラリ X

ライブラリ Y

アプリ Aパッケージ version 2

version 2

version 1 version 1

version 1 version 1

ライブラリ X

差分

version 2

ダウンロード

アプリ A ver.1インストール機

バージョンアップ時

更新ここだけ新しい

Page 73: C#/.NETがやっていること 第二版

アプリ B

ライブラリ X

ライブラリ Y

アプリ間でライブラリ共有※ Windows ストア アプリはこういう仕組み持って

アプリ A ライブラリ X

ライブラリ Y

アプリ Aパッケージ

ライブラリ Y

アプリ Bパッケージ

差分

アプリ Aインストール機アプリ B

アプリ B インストール時

X 、 Y は同じものを共有(ハード リンク作るだけ)

version 1

version 1 version 1

version 1同じバージョン

Page 74: C#/.NETがやっていること 第二版

パッケージ管理※ ASP.NET vNext はこういう仕組み持ってる

開発機 アプリ サーバー パッケージ リポジトリ

例http://nuget.orghttp://myget.org

project.json

*.cs

"dependencies": { "Library.A": "", "Library.B": ""}

var c = new Configuration();c.AddJsonFile("config.json");a.UseServices(services => …);

ソースコードだけをアップロード

サーバー上で編集可能

ライブラリの不足・更新はクラウドから取得

Page 75: C#/.NETがやっていること 第二版

ポイント

• 動的にリンク• 部分更新・差分ダウンロード• ライブラリ共有• パッケージ管理

• リンクに必要な情報 ( メタデータ ) を実行可能ファイルに残す• メタデータも規格化されてる• C#/.NET の場合はコンパイルするだけで作られる

Page 76: C#/.NETがやっていること 第二版

JIT(Just-in-Time compile)実際の動的リンクの挙動

Page 77: C#/.NETがやっていること 第二版

中間コードと JIT コンパイル

• .NET Framework の中間言語

• 高級言語のコンパイラーを作る人と、 CPU ごとの最適化する人の分業化• セキュリティ チェックしやすくなったり• 動的リンク時に、コード修正の影響を JIT で吸収

高級言語(C# など )

中間言語(IL)

ネイティブコード

ビルド時にコンパイル

Just-in-Timeコンパイル

JIT である必要ないLLVM とかでも中間言語介してコンパイルして

ストア審査ではじくとか他にも手段はある

Page 78: C#/.NETがやっていること 第二版

• (C# で ) こんな型があったとして

• 整数のフィールドを 3 つ持つ

public struct Point{ public int X; public int Y; public int Z;}

Page 79: C#/.NETがやっていること 第二版

• こんなメソッドを書いたとする

• フィールドの掛け算

static int GetVolume(Point p){ return p.X * p.Y * p.Z;}

Page 80: C#/.NETがやっていること 第二版

IL

• C# コンパイル結果の IL.method private hidebysig static int32 GetVolue(valuetype Point p) cil managed{ .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld int32 Point::X IL_0006: ldarg.0 IL_0007: ldfld int32 Point::Y IL_000c: mul IL_000d: ldarg.0 IL_000e: ldfld int32 Point::Z IL_0013: mul IL_0014: ret}

型とかフィールドの名前がそのまま

残ってる

型情報メタデータ

Page 81: C#/.NETがやっていること 第二版

ネイティブ コード

• JIT 結果 (x64 の場合 )

push ebp mov ebp,esp cmp dword ptr ds:[5011058h],0 je 00FE2A01 call 74B7AEA8 mov eax,dword ptr [ebp+8] lea edx,[ebp+8] imul eax,dword ptr [edx+4] lea edx,[ebp+8] imul eax,dword ptr [edx+8] pop ebp ret 0Ch

4 とか 8 とかの数値に

型情報は残らない

Page 82: C#/.NETがやっていること 第二版

メモリ レイアウト

• この 4 とか 8 の意味

public struct Point{

public int X;public int Y;public int Z;

}

Point

X

Y

Z

4バイト

8バイト

※レイアウトがどうなるかは環境依存

Page 83: C#/.NETがやっていること 第二版

メモリ レイアウト

• この 4 とか 8 の意味

public struct Point{ public int X; public int Y; public int Z;}

Point

X

Y

Z

4バイト

8バイト

※レイアウトがどうなるかは環境依存

IL の時点までは名前で参照してる

ネイティブ コードはレイアウトを見て数値で参照してる

Page 84: C#/.NETがやっていること 第二版

数値でのフィールド参照

• C# で擬似的に書くとstatic int GetVolume(Point p){ return p.X * p.Y * p.Z;}

var pp = (byte*)&p;var x = *((int*)pp);var y = *((int*)(pp + 4));var z = *((int*)(pp + 8));return x * y * z;

4 とか 8 とかの数値に

※これ、一応 C# として有効なコード (unsafe)

Page 85: C#/.NETがやっていること 第二版

変更してみる

• 大して影響しなさそうなほんの些細な変更をしてみる

public struct Point{ public int X; public int Y; public int Z;}

public struct Point{ public int X; public int Z; public int Y;}

フィールドの順序変更

Page 86: C#/.NETがやっていること 第二版

その結果起きること

• メモリ レイアウトが変わる※

Point

X

Y

Z

Point

X

Z

Y

※ この例(フィールド変更)以外でも、仮想メソッド テーブルとかいろいろレイアウトが変わるものがある

Page 87: C#/.NETがやっていること 第二版

IL レベルでの影響

•影響なし

IL_0000: ldarg.0IL_0001: ldfld int32 Point::XIL_0006: ldarg.0IL_0007: ldfld int32 Point::YIL_000c: mulIL_000d: ldarg.0IL_000e: ldfld int32 Point::ZIL_0013: mulIL_0014: ret

名前で参照してるんだから特に影響ないJIT が吸収してくれる

Page 88: C#/.NETがやっていること 第二版

ネイティブ レベルでの影響

• ここで影響が出るpush ebp mov ebp,esp cmp dword ptr ds:[5011058h],0 je 00FE2A01 call 74B7AEA8 mov eax,dword ptr [ebp+8] lea edx,[ebp+8] imul eax,dword ptr [edx+4] lea edx,[ebp+8] imul eax,dword ptr [edx+8] pop ebp ret 0Ch

8

4更新が必要

利用側の再コンパイルが必要

ライブラリ側だけの差し替えじゃダメ

Page 89: C#/.NETがやっていること 第二版

ただし…

• この役割に焦点を当てるなら…• 毎回毎回 JIT する必要ない• 全部が全部 IL な必要ない

Page 90: C#/.NETがやっていること 第二版

Ngen

• Ngen.exe• Native Image Generator• IL を事前にネイティブ化するためのツール• 自前管理が必要

• アプリのインストーラー※とかを作って明示的に呼び出し

• 参照しているライブラリが更新された時には呼びなおす必要あり •かなり面倒なのでアプリを

Ngen することはめったにない• .NET 自体が標準ライブラリ

の高速化のために使ってる※ 要するに、 JIT の負担を起動時じゃなくてインストール時に前倒しする

Page 91: C#/.NETがやっていること 第二版

Auto-Ngen

• .NET Framework 4.5以降なら• Ngen が Windowsサービスとして常に動いてる

• アイドル時に動作• 利用頻度の高いものを自動的に Ngen

• デスクトップ アプリの場合は GAC アセンブリのみ• Windows ストア アプリの場合はすべてのアセンブリ

•よく使うアプリの起動はだいぶ早くなる•インストール直後の起動は相変わらず遅

Page 92: C#/.NETがやっていること 第二版

MDIL (ネイティブのおさらい )•おさらい : ネイティブ コードだと

push ebp mov ebp,esp cmp dword ptr ds:[5011058h],0 je 00FE2A01 call 74B7AEA8 mov eax,dword ptr [ebp+8] lea edx,[ebp+8] imul eax,dword ptr [edx+4] lea edx,[ebp+8] imul eax,dword ptr [edx+8] pop ebp ret 0Ch

参照しているライブラリのレイアウトが変わった時に再コンパイ

ルが必要

Page 93: C#/.NETがやっていること 第二版

MDIL (部分的にネイティブ化 )• じゃあ、こんな形式があればいいんじゃ?

push ebp mov ebp,esp cmp dword ptr ds:[5011058h],0 je 00FE2A01 call 74B7AEA8 mov eax,dword ptr [ebp+8] lea edx,[ebp+8] imul eax,dword ptr [edx+4] lea edx,[ebp+8] imul eax,dword ptr [edx+8] pop ebp ret 0Ch

int32 Point::Xint32

Point::Yint32

Point::Z

ほぼネイティブ レイアウトのとこ

ろだけ抽象的に型情報を残しておく

MDIL: Machine Dependent Intermediate Language

Page 94: C#/.NETがやっていること 第二版

MDIL (Compile in the Cloud)• ストア サーバー上で MDIL 化までやっておく

C#コード

IL

MDIL

ネイティブ コード

C# コンパイラー

MDIL コンパイラー

リンカー

開発環境で IL にコンパイル

Windows ストア サーバー上で IL を MDIL 化

Windows Phone 実機上ではレイアウトの解決 ( リンク ) だけ行う

インストール直後の起動も高速

※ Windows Phone アプリはこういう仕組み持ってる

Page 95: C#/.NETがやっていること 第二版

ポイント

• JIT の主な利点• 動的リンクしやすい• ( 後述の、動的コード生成しやすいとかもある )

• ただし、常に Just-In-Time な必要ない• インストール時 → Ngen• サービスで定期的に → Auto-Ngen• ストア サーバー上で → Compile in the Cloud

Page 96: C#/.NETがやっていること 第二版

vNext は JIT だけじゃない

• (詳細は後述 )選べる実行形態• 事前に完全ネイティブ化 → .NET Native• ソースコード配置 → Cloud Mode

Page 97: C#/.NETがやっていること 第二版

リフレクション動的コード生成

Page 98: C#/.NETがやっていること 第二版

メタデータ利用

• メタデータ ( プログラム生成に必要な情報 ) を持っているということは• 動的にプログラム コードを生成できる• 自己反映的動作 ( リフレクション )

Page 99: C#/.NETがやっていること 第二版

コード生成 API

高級言語(C#)

構文木

IL

ネイティブ コード

parse

emit

JIT

.NET Compiler Service†

式ツリー

(System.Linq.Expressions)

ILGenerator

† 旧称 ( コードネーム )Roslyn動的なコンパイルが可能

Page 100: C#/.NETがやっていること 第二版

C# コードから

• .NET Compiler Service の例

session.Execute<Func<Point, int>>("p => p.X * p.Y * p.Z")

C#ソースコードをコンパイル

C#→[parse]→ 構文木→ [emit]→IL→[JIT]→Native

Page 101: C#/.NETがやっていること 第二版

構文木から

• System.Linq.Expressions

Expression.Lambda<Func<Point, int>>(Expression.Multiply(

Expression.Multiply(Expression.Field(p, x),Expression.Field(p, y)),

Expression.Field(p, z)),

Page 102: C#/.NETがやっていること 第二版

IL を直接生成

• ILGenerator

var gen = m.GetILGenerator();gen.Emit(OpCodes.Ldarg_0);gen.Emit(OpCodes.Ldfld, x);gen.Emit(OpCodes.Ldarg_0);gen.Emit(OpCodes.Ldfld, y);gen.Emit(OpCodes.Mul);gen.Emit(OpCodes.Ldarg_0);gen.Emit(OpCodes.Ldfld, z);gen.Emit(OpCodes.Mul);gen.Emit(OpCodes.Ret);

Page 103: C#/.NETがやっていること 第二版

コード生成にかかる時間

• 本当はあんまり動的にやりたくない

• 当たり前だけど parse は遅い• なので、 .NET はソースコード配布じゃなくて IL配布• できる限りコンパイル時生成したい

かかった時間 [ ミリ秒 ]

ILGenerator(JIT)

39.89

Expressions(emit→JIT)

67.94

Compiler Service(parse→emit→JIT)

4314

倍遅い

2桁遅い

ある環境で、あるコードの生成結果の例

Page 104: C#/.NETがやっていること 第二版

ポイント

• メタデータを持ってる = 動的コード生成できる• C#ソースコードから : .NET Compiler Service• 式ツリーから : System.Linq.Expressions• IL 直接生成 : ILGenerator

遅い・楽

速い・面倒

※ .NET Native( 後述 ) だと動的コード実行がインタープリター方式になっちゃってかなり遅いので注意

Page 105: C#/.NETがやっていること 第二版

vNextJIT以外の実行方法.NET vNext

• .NET Native

• Cloude Mode

Page 106: C#/.NETがやっていること 第二版

JIT は便利なんだけど

利点•動的リンク・部分更新しやすい•セキュリティ検証しやすい•(ソースコード配布よりは)高速

欠点•.NET Frameworkのランタイム インストール必須•(ネイティブ配布よりは)低速•(ソースコード配布よりは)デバッグ・修正が面倒

用途によっては…

Page 107: C#/.NETがやっていること 第二版

例えば携帯デバイス向け

利点•動的リンク・部分更新しやすい•セキュリティ検証しやすい•(ソースコード配布よりは)高速

欠点•.NET Frameworkのランタイム インストール必須•(ネイティブ配布よりは)低速•(ソースコード配布よりは)デバッグ・修正が面倒

ストア サーバー上でやればいい

ランタイム分のストレージ容量使いたくない

性能的に結構困る

ストア サーバー上でネイティブ化

.NET Native

Page 108: C#/.NETがやっていること 第二版

.NET Native: 事前ネイティブ化• .NET アプリを事前にネイティブ化• 実用上はストア サーバー上でネイティブ化

• セキュリティ保証• アップロード時には IL• 審査付き

• クライアント デバイス的には低コスト• JIT のコストなし• ランタイムのインストール不要

• Visual Studio 上で直接ネイティブ化も可能• 通常版の .NET との差で問題が起きないかの確認• パフォーマンスの確認

Page 109: C#/.NETがやっていること 第二版

.NET Native: 最適化

•「事前に JIT相当の処理」以上の最適化も• シリアライズなどのコードを事前に展開

• 型情報を見たいからリフレクションを使ってただけで、実際にはビルド時に型が確定してることが多い

• 必要な分だけ静的リンク• ライブラリをまたいだ最適化が可能• 不要コードは残さないので実行可能ファイルのサイズは膨らまない

Page 110: C#/.NETがやっていること 第二版

.NET Native: リフレクション• リフレクションも使える

ただし…• XAML {Binding} など、推論が効く部分は自動判

定してくれる• それ以外は、自分で「この型はリフレクションで

使ってる」という情報を書かないとダメ(Runtime Directive ファイル )• 動的コード生成は無理

• 式ツリーなどはインタープリター方式になるので遅い

Page 111: C#/.NETがやっていること 第二版

例えばサーバー向け

利点•動的リンク・部分更新しやすい•セキュリティ検証しやすい•(ソースコード配布よりは)高速

欠点•.NET Frameworkのランタイム インストール必須•(ネイティブ配布よりは)低速•(ソースコード配布よりは)デバッグ・修正が面倒

アプリ稼働時間が長くて、最初の 1 回だけのコストは無視できる (低速でもいい )

開発機とサーバー機でバージョンが違って困ることが

サーバー上で確認しつつその場でソースコードを修正したい

ソースコード配置自動パッケージ管理

Cloud Modeアプリごとに別バージョンを使えない

Page 112: C#/.NETがやっていること 第二版

Cloud Mode:

•ソースコード配置• .NET Compiler Service (Roslyn) を利用

• メモリ上で全部コンパイル ( 一時ファイルを残さない )

• ソースコード書き替えて、ブラウザー更新するだけ

• side-by-side インストール• (サーバーにインストールするんじゃなく )

アプリごとに別 .NET ランタイムを使える• アプリごとにバージョンを変えれる• 開発時と同じバージョンを使える

Page 113: C#/.NETがやっていること 第二版

Cloud Mode: パッケージ管理• 自動パッケージ管理

開発機 アプリ サーバー パッケージ リポジトリ

例http://nuget.orghttp://myget.org

project.json

*.cs

"dependencies": { "Library.A": "", "Library.B": ""}

var c = new Configuration();c.AddJsonFile("config.json");a.UseServices(services => …);

ソースコードとパッケージ利用情報だけをアップ

ロード

サーバー上で編集可能

.NET ランタイムや、ライブラリの不足・更新

はクラウドから取得

利用するパッケージの情報ファイル

Page 114: C#/.NETがやっていること 第二版

ポイント

•選べる実行方法• IL配布 (JIT)

• デスクトップなら割とこれが便利• ネイティブ配布 (.NET Native)

• 携帯デバイス向け• ソースコード配置 (Cloud Mode)

• サーバー向け• 特に共有ホスティングなクラウド サーバー

Page 115: C#/.NETがやっていること 第二版

Portable Class Library複数の「標準ライブラリ」いろんな実行環境の共通部分

Page 116: C#/.NETがやっていること 第二版

問題 : 複数の標準ライブラリ

• マイクロソフト製品だけでも…

デスクトップクライアント アプリ

Phone/ タブレットクライアント アプリ

サーバー アプリ

共通部分

この共通部分だけを「標準」にすべき?

.NET NativeCloud Mode

Page 117: C#/.NETがやっていること 第二版

問題 : 複数の標準ライブラリ

• まして今、 Xamarin (Mono)• Xamarin.iOS 、 Xamarin.Android

WindowsデスクトップWindows

タブレット /Phone

Linuxサーバー

この共通部分だけを「標準」にすべき?

Windowsサーバー

iOS

Android

Page 118: C#/.NETがやっていること 第二版

.NET Framework と Mono

C#

VB

F#

C#

CLR

Monoランタイム

.NET Fullプロファイル

.NET Coreプロファイル

.NET Full(サブセッ

ト )iOS向け

プロファイル

Android向けプロファイル

.NETFramework

Mono

コンパイラー 実行環境 ライブラリ

実行環境にはかなりの互換性がある 標準で使える

ライブラリが違う

Page 119: C#/.NETがやっていること 第二版

どこでも使えそうに見えても…•例えばファイル システム• 最近の Windows はいろんなプロパティを持って

る• 画像ファイルなんかだと :サムネイル画像、撮影日時、画像サイズ、タグ

• 検索インデックスも張ってる

• ストア アプリだとファイル システムすら制限下• ユーザーの許可がないファイルには触れない

こういうものも標準に含めたいか?

一番制限がきつい環境に合わせて標準ライブラリを作るべき?

Page 120: C#/.NETがやっていること 第二版

汎用 VS 特定環境どこでも動く• 最

大公約数

• 動作保証に時間がかかる

特定環境で動く• 高

機能

• 早く提供できる

Page 121: C#/.NETがやっていること 第二版

Portable Class Library

• 実行環境ごとに別の「標準ライブラリ」メタデータを用意• ライブラリ側でどの

実行環境をターゲットにするか選ぶ

実行環境ごとに別メタデータを提供

ターゲット選択

Page 122: C#/.NETがやっていること 第二版

Portable Class Library

•例 : 2環境

共通部分

Page 123: C#/.NETがやっていること 第二版

Portable Class Library

•例 : 3環境

共通部分

Page 124: C#/.NETがやっていること 第二版

.NET vNext

• ぶっちゃけ 3系統別実装 (主用途も別 )• JIT• .NET Native• Cloud Mode

• 1 つの“標準”ライブラリで保守するのは大変• どうしても事前ネイティブ化しにくい…• サーバーに GUI コンポーネント要るの?…• フットプリント的に余計なもの載せれない…

既存品とはいえ、ちゃんと次世代でも更新あり• 64ビット対応強化• SIMD演算対応

new!

new!

PCL みたいな仕組み必須

Page 125: C#/.NETがやっていること 第二版

ポイント

•複数の“標準”• 用途によって

• デスクトップ、携帯デバイス、クラウド• 実装によって

• JIT 、事前ネイティブ化、ソースコード配置• 他 OSへのポーティング

ME

SE

EE

v2.0

v3.0

v4.0

× 包含関係では不十分 ○ 複数の“標準”の共通部分管理

Page 126: C#/.NETがやっていること 第二版
Page 127: C#/.NETがやっていること 第二版

3 時間目• C# 言語機能• generics• iterator• LINQ• dynamic• async/await

• .NET Compiler Platform

1

2

3

Page 128: C#/.NETがやっていること 第二版

C# の歴史

C# 1.0• Man

aged

C# 2.0• Generi

cs• Iterato

r

C# 3.0• LINQ

C# 4.0• Dynamic

C# 5.0• Async

※VB 7~ 11 の歴史でもある

Page 129: C#/.NETがやっていること 第二版

ジェネリックC# 2.0

C++ で言うところの template

C++ template と .NET Generics の違い

Page 130: C#/.NETがやっていること 第二版

型違いの処理

• 型だけ違う処理、コピペで書いていませんか

int Max(int x, int y){  return x > y ? x : y;}

class IntStack{    void Push(int item)  { … }    int Pop()  { … }}

double Max(double x, double y){  return x > y ? x : y;}

class DoubleStack{    void Push(double item)  { … }    double Pop()  { … }}

Page 131: C#/.NETがやっていること 第二版

ジェネリック†

• 型をパラメーター化

† generics。MS翻訳ルール的に、語尾 s は取るんですって複数形の s扱いで。単複の区別のない言語に訳すとき

class Stack<T>{    void Push(T item)  { … }    T Pop()  { … }}

T Max<T>(T x, T y)    where T : IComparable<T>{    return x.CompareTo(y) > 0 ? x : y;}

int Max(int x, int y){  return x > y ? x : y;}

class IntStack{    void Push(int item)  { … }    int Pop()  { … }}

Page 132: C#/.NETがやっていること 第二版

型の直行化

• 要素の型と、処理・要素管理は分けましょう

要素の型

処理

intbytedoublestring… Max

MinAverage…

管理方式

ListLinkedListStack…

Page 133: C#/.NETがやっていること 第二版

型の直行化

• 要素の型と、処理・要素管理は分けましょう

要素の型

処理

intbytedoublestring… Max

MinAverage…

管理方式

ListLinkedListStack…

Lパターン Mパターン

Nパターン

分けて作らないと L×M×N 通りのパターン

分けて作ると L+M+N 通り

Page 134: C#/.NETがやっていること 第二版

2.0 からなので…

• 1.0 の頃の名残がちらほら…

• 今となっては

GroupCollectionMatchCollectionAttributeCollectionStringCollection…

IEnumerable<Group>IEnumerable<Match>IEnumerable<Attribute>IEnumerable<string>…

IReadOnlyCollection<Group>IReadOnlyCollection<Match>IReadOnlyCollection<Attribute>IReadOnlyCollection<string>…

もしくは

1 つの型で済む

† System.dll に実在する型。ごくごく一部の抜粋。

Page 135: C#/.NETがやっていること 第二版

.NET のジェネリック

• IL レベル対応• メタデータがちゃんと残る

• リフレクションで厳密な型をとれる・区別できる• ジェネリック用命令持ってる

• JIT 時に展開• キャストとかの不要コードが消える

• 値型のボックス化も消える• 値型の展開はかなりパフォーマンスに寄与

Page 136: C#/.NETがやっていること 第二版

おまけ : C++ の template

• コンパイル時に展開• 超高機能なマクロみたいなもの• ○ 実行時のパフォーマンスいい• ○ かなり自由が効く• × 実行可能ファイル サイズが肥大化しがち• × コンパイル エラーが出た時結構悲惨

Page 137: C#/.NETがやっていること 第二版

おまけ : Java のジェネリック

• 型消去• コンパイル時に実は型が消えてる

• 全部 object扱い• ○? Java 1.0 の頃から bytecode命令増えてない• × 実行時にリフレクションで情報とれない• × キャストが挟まる• × 全然違う型にキャストできちゃう ( 実行時例

外 )

Page 138: C#/.NETがやっていること 第二版

いろいろ窮屈な面も

• .NET のジェネリックでは、インターフェイス制約かけないとメソッドすら呼べないstatic Type Max<Type>(Type a, Type b){ return a.CompareTo(b) > 0 ? a : b;}

static Type Max<Type>(Type a, Type b) where Type : IComparable{ return a.CompareTo(b) > 0 ? a : b;}

コンパイル エラーそんなメソッド知らな

い正しくは

インターフェイス制約

IComparable.CompareTo

Page 139: C#/.NETがやっていること 第二版

いろいろ窮屈な面も

•特に困るのが演算子使えないこと• 実体は静的メソッドなので

static T Sum<T>(T[] array){

T sum = default(T);foreach (var x in array) sum += x;return sum;

} コンパイル エラー演算子定義されてない

Page 140: C#/.NETがやっていること 第二版

いろいろ窮屈な面も

• dynamic でごまかせなくはないけども…• パフォーマンス出ないので最後の手段

static T Sum<T>(T[] array){

dynamic sum = default(T);foreach (var x in array) sum += x;return sum;

} 実行時コード生成で+演算子が呼ばれる

Page 141: C#/.NETがやっていること 第二版

ポイント

• 型のパラメーター化• 似て非なるコピペ コードの解消• 型の直行化

• {int, byte, string, …} × {List, Stack, Queue, …}

• .NET のジェネリックは IL レベル対応• メタデータあり

• 実行時にリフレクションで情報とれる• JIT 時に展開

• キャストなどの不要なコードは挟まらない

Page 142: C#/.NETがやっていること 第二版

イテレーターC# 2.0

イテレーター生成用の構文

Page 143: C#/.NETがやっていること 第二版

データの列挙

• プログラム中、データの列挙は非常に多い• データを使う側はすごく楽

• 作る側、加工する側は?

foreach (var x in data){    Console.WriteLine(x);}

Page 144: C#/.NETがやっていること 第二版

データの列挙の例

• substring の列挙• データを作る側と使う側を分けないなら

static void WriteSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            Console.WriteLine(s.Substring(i, len));}

いつも Console.Write したいわけじゃないsubstring を使いたいたびに同じコード書くの?

Page 145: C#/.NETがやっていること 第二版

データの列挙の例

• substring の列挙、分けたいなら• ↓こんな感じのクラスを書けばいいんだけど…class GetSubstringEnumerator{    public string Current { get; private set; }    string _s; int _len; int _i;     public GetSubstringEnumerator(string s)    {        _s = s; _len = s.Length; _i = 0;    }     public bool MoveNext()    {        for (; _len >= 1; _len--, _i = 0)            for (; _i <= _s.Length - _len; )            {                Current = _s.Substring(_i, _len);                _i++;                return true;            }        return false;    }}

こういうクラスをイテレーター (iterator) とか列挙子 (enumerator) って言

う作るの結構面倒

Page 146: C#/.NETがやっていること 第二版

イテレーターの例

• substring の列挙

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

foreach (var x in GetSubstrings("abcd"))    Console.WriteLine(x);

実装側

使う側

イテレーター ブロック†(= yield return を持つ関数ブロッ

ク )イテレーター クラスを自動生成

Page 147: C#/.NETがやっていること 第二版

内部実装 (全体像 )

• クラス生成

class SubstringEnumerable : IEnumerator<string>, IEnumerable<string>{    readonly string _s;    int _len;    int _i;    int _state = 0;     public SubstringEnumerable(string s) { _s = s; }     public string Current { get; private set; }     public bool MoveNext()    {        if (_state == 1) goto STATE1;        if (_state == -1) goto END;        _state = 1;        _len = _s.Length;    LOOP1BEGIN: ;        if (!(_len >= 1)) goto LOOP1END;        _i = 0;    LOOP2BEGIN: ;        if (!(_i <= _s.Length - _len)) goto LOOP2END;        _state = 1;        Current = _s.Substring(_i, _len);        return true;    STATE1: ;        _i++;        goto LOOP2BEGIN;    LOOP2END: ;        _len--;        goto LOOP1BEGIN;    LOOP1END: ;        _state = -1;    END: ;        return false;    }     public void Reset() { throw new NotImplementedException(); }    public void Dispose() { }    object IEnumerator.Current { get { return Current; } }     public IEnumerator<string> GetEnumerator()    {        if(_state == 0) return this;        else return new SubstringEnumerable(_s).GetEnumerator();    }     IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }}

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

Page 148: C#/.NETがやっていること 第二版

内部実装 ( ローカル変数 )

• ローカル変数 → フィールド

class SubstringEnumerable : IEnumerator<string>, IEnumerable<string>{    readonly string _s;    int _len;    int _i;    int _state = 0;     public SubstringEnumerable(string s) { _s = s; }     public string Current { get; private set; }     public bool MoveNext()    {        if (_state == 1) goto STATE1;        if (_state == -1) goto END;        _state = 1;        _len = _s.Length;    LOOP1BEGIN: ;        if (!(_len >= 1)) goto LOOP1END;        _i = 0;    LOOP2BEGIN: ;        if (!(_i <= _s.Length - _len)) goto LOOP2END;        _state = 1;        Current = _s.Substring(_i, _len);        return true;    STATE1: ;        _i++;        goto LOOP2BEGIN;    LOOP2END: ;        _len--;        goto LOOP1BEGIN;    LOOP1END: ;        _state = -1;    END: ;        return false;    }     public void Reset() { throw new NotImplementedException(); }    public void Dispose() { }    object IEnumerator.Current { get { return Current; } }     public IEnumerator<string> GetEnumerator()    {        if(_state == 0) return this;        else return new SubstringEnumerable(_s).GetEnumerator();    }     IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }}

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

Page 149: C#/.NETがやっていること 第二版

内部実装 (yield return)

• yield return → 状態記録、 return 、 case ラベル• 中断と再開         if (_state == 1) goto STATE1;

        if (_state == -1) goto END;        _state = 1;        _len = _s.Length;    LOOP1BEGIN: ;        if (!(_len >= 1)) goto LOOP1END;        _i = 0;    LOOP2BEGIN: ;        if (!(_i <= _s.Length - _len)) goto LOOP2END;        _state = 1;        Current = _s.Substring(_i, _len);        return true;    STATE1: ;        _i++;        goto LOOP2BEGIN;    LOOP2END: ;        _len--;        goto LOOP1BEGIN;    LOOP1END: ;        _state = -1;    END: ;        return false;    }     public void Reset() { throw new NotImplementedException(); }    public void Dispose() { }    object IEnumerator.Current { get { return Current; } }     public IEnumerator<string> GetEnumerator()    {        if(_state == 0) return this;        else return new SubstringEnumerable(_s).GetEnumerator();    }     IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }}

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

Page 150: C#/.NETがやっていること 第二版

switch(_state){case 0:    for (_len = _s.Length; _len >= 1; _len--)        for (_i = 0; _i <= _s.Length - _len; _i++)        {            _state = 1;            Current = _s.Substring(_i, _len);            return true;            case 1:;        }    _state = -1;}

内部実装 (yield return)

• yield return の部分、意味合いとしては†

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

yield return以外の場所はほぼ同じ

yield return x;

_state = 1;Current = x;return true;case 1:;

† for ループ内にラベル張れないからさっきみたいな複雑なコードになるけども

switch で囲う

Page 151: C#/.NETがやっていること 第二版

内部実装 ( 中断と再開 )

• yield return の部分、意味合いとしては• 中断と再開

yield return x;

_state = 1;Current = x;return true;case 1:;

状態の記録

現在の値の保持

復帰用のラベル

Page 152: C#/.NETがやっていること 第二版

おまけ : 他の言語だと

•最近の言語は結構似た機能†持ってる• スタック丸ごとキャプチャしてしまうもの

• 中断時に記録、再開時に復元• 式を継続渡しスタイル (CPS) に変換してしまうも

の• スレッド使う ( さすがに性能的に論外だけども )

• 割と機械的な置き換えなので、マクロでも

† 他の言語だとジェネレーター (generator) って呼ばれることが多い

#define BEGIN_ITERATOR\    switch(_state)\ {\    case 0:

#define YIELD(STATE, VALUE)\    _state = STATE;\    _current = VALUE;\    return true;\    case STATE:;

#define END_ITERATOR\    _state = -1;\    default:;\ }\    return false;

Page 153: C#/.NETがやっていること 第二版

おまけ : C++ 1y

• C++ にもジェネレーターが載るかも• C++ 17 に向けて標準化案出てる• C++/CX向けの実装を元に MS が提案

• async/await のついで

sequence<int> range(int low, int high) resumable{ for(int i = low; i <= high; ++i) { yield i; }}

Page 154: C#/.NETがやっていること 第二版

ちなみに

• コーディング面接で有名な某社の社員さん曰く、

• 例に使ったの substring列挙も割とよく出てくるパターン• 中断と再開って大事

アルゴリズムの問題はかなりの割合、イテレーターを使うとあっさり書ける

「」

Page 155: C#/.NETがやっていること 第二版

ポイント

• データ列挙は多い• データを作る側と使う側をきっちり分離しよ

うと思うと結構面倒• 特に、作る側

•作る側を簡単化する仕組みがイテレーターstatic IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

Page 156: C#/.NETがやっていること 第二版

LINQ(Language Integrated Query)C# 3.0

データ処理の直行化

Page 157: C#/.NETがやっていること 第二版

LINQ

• データ処理を言語統合• ほとんどはライブラリで実現

• System.Linq名前空間

var source = new[] { 1, 2, 3, 4, 5 };var filtered = source    .Where(x => x <= 3)  // 1, 2, 3    .Select(x => x * x); // 1, 4, 9

Enumerable クラスのWhere メソッドと Select メ

ソッドが呼ばれるだけ

条件を満たす要素だけ残す

要素ごとに処理をかける

Page 158: C#/.NETがやっていること 第二版

データ処理の直行化

• データの入力、加工、出力は分けましょう

ユーザー入力からConsole.Read…

配列から{ 1, 2, 3, … }

データベースからSELECT x FROM t

ファイルからFile.Read…

1, 2, 3変換x => x * x1, 4, 9

1, 2, 3選択x => x < 31, 2

1, 2, 3グループ化x => x % 2{1, 3},

{2}コンソールにConsole.Write…

配列にToArray()

データベースにINSERT INTO t

ファイルにFile. Write…

入力 加工 出力

Page 159: C#/.NETがやっていること 第二版

データ処理の直行化

•掛け算を足し算に

ユーザー入力からConsole.Read…

配列から{ 1, 2, 3, … }

データベースからSELECT x FROM t

ファイルからFile.Read…

1, 2, 3変換x => x * x1, 4, 9

1, 2, 3選択x => x < 31, 2

1, 2, 3グループ化x => x % 2{1, 3},

{2}コンソールにConsole.Write…

配列にToArray()

データベースにINSERT INTO t

ファイルにFile. Write…

入力 加工 出力

Lパターン Mパターン Nパターン

分けて作らないと L×M×N 通りのパターン

分けて作ると L+M+N 通り

Page 160: C#/.NETがやっていること 第二版

yield return で実装できる

•列挙子から別の列挙子を作る処理

• ここまでは C# 2.0• これに加えて、 C# 3.0 では…

IEnumerable<R> Select<S, R>(    IEnumerable<S> source,    Func<S, R> selector){    foreach (var x in source)        yield return selector(x);}

IEnumerable<T> Where<T( IEnumerable<T> source,  Func<T, bool> predicate){    foreach (var x in source)        if (predicate(x))            yield return x;}

列挙子を引数にとって列挙子を返す

yield があれば実装簡単

Page 161: C#/.NETがやっていること 第二版

C# 3.0

• ラムダ式•匿名型•拡張メソッド

Page 162: C#/.NETがやっていること 第二版

ラムダ式

•匿名関数を簡単に書ける

• =>演算子• goes to (~になる )演算子• 左が引数で、右が関数本体• 型推論も効いてる

source.Select(x => x * x);(シーケンスの全要素を二乗 )

Page 163: C#/.NETがやっていること 第二版

そもそも : 匿名関数

• メソッドの自動生成

IEnumerable<int> Triple(IEnumerable<int> input){    return input.Select(x => 3 * x);}

IEnumerable<int> Triple(IEnumerable<int> input){    return input.Select(X);} int X(int x) { return 3 * x; }

Page 164: C#/.NETがやっていること 第二版

ローカル変数のキャプチャ

• 実はクラス生成IEnumerable<int> Multiply(IEnumerable<int> input){    var a = int.Parse(Console.ReadLine());    return input.Select(x => a * x);}

IEnumerable<int> Multiply(IEnumerable<int> input){    var _ = new Anonymous();    _.a = int.Parse(Console.ReadLine());    return input.Select(_.X);}

class Anonymous{    public int a;    public int X(int x)  {  return a * x;  }}

ローカル変数がフィールドに昇格

Page 165: C#/.NETがやっていること 第二版

匿名型

• 1 か所でしか使わないような型は作らなくていい

• immutable なクラスを自動生成• GetHashCode 、等値比較、 ToString を完備

source.GroupBy(p => new { p.X, p.Y });(X と Y でグループ化 )

class Anonymous{    public int X { get; private set; }    public int Y { get; private set; }    public Anonymous(int x, int y) { X = x; Y = y; }}

Page 166: C#/.NETがやっていること 第二版

拡張メソッド

• 静的メソッドを後置き記法で書ける

• 単に語順を変えるだけ

source.Select(x => x * x);

System.Linq.Enumerable.Select( source, x => x * x);

同じ意味

Page 167: C#/.NETがやっていること 第二版

語順を変えるだけで

• 語順のインパクト意外と大きい

var result = data    .Where(x => x < 3)    .Select(x => x * x)    .GroupBy(x => x % 2)    .Select(g => g.Sum());

var result1 =    Select(    GroupBy(    Select(    Where(        data,        x => x < 3),        x => x * x),        x => x % 2),        g => Sum(g));

逆順 処理順

( ) が遠い( ) が近い

普通の静的メソッド 拡張メソッド

Page 168: C#/.NETがやっていること 第二版

静的メソッド

• “static” と言ってもいろいろ

static bool globalFlag;いつどこで誰が書き換えるか

わからないのがダメ

静的フィールド (書き換え可能 )

const int Max = 100;static readonly TimeSpan interval = TimeSpan.FromSeconds(1);

定数 or 読み取り専用静的フィールド

書き換え禁止すれば無害

static int Clip(int x) { return Math.Max(x, Max); }

静的メソッド

無害なものにしか触れない限り、無害

Page 169: C#/.NETがやっていること 第二版

静的メソッド

• “static” と言ってもいろいろ

static bool globalFlag;いつどこで誰が書き換えるか

わからないのがダメ

静的フィールド (書き換え可能 )

const int Max = 100;static readonly TimeSpan interval = TimeSpan.FromSeconds(1);

定数 or 読み取り専用静的フィールド

書き換え禁止すれば無害

static int Clip(int x) { return Math.Max(x, Max); }

静的メソッド

無害なものにしか触れない限り、無害

純粋関数 (pure function)書き換えが起こらないメソッド = 同じ引数を与えたら常に同じ結果しか返らない = テストしやすくていい

Page 170: C#/.NETがやっていること 第二版

ポイント

• C# 3.0• ラムダ式• 匿名型• 拡張メソッド

•小さくて、汎用的な機能の集まり• LINQ以外でも有用

LINQ ( データ処理用構文・ライブラリ ) に関連して入った

機能

Page 171: C#/.NETがやっていること 第二版

dynamic 型C# 4.0

ダック タイピング用の型

Page 172: C#/.NETがやっていること 第二版

おさらい : メタデータ

• プロパティ名とかは、本来、実行時に要らない

IL_0000: ldarg.0IL_0001: ldfld int32 T::XIL_0006: ldarg.0IL_0007: ldfld int32 T::YIL_000c: mulIL_000d: ldarg.0IL_000e: ldfld int32 T::ZIL_0013: mulIL_0014: ret

push ebp mov ebp,esp cmp dword ptr ds:[5011058h],0 je 00FE2A01 call 74B7AEA8 mov eax,dword ptr [ebp+8] lea edx,[ebp+8] imul eax,dword ptr [edx+4] lea edx,[ebp+8] imul eax,dword ptr [edx+8] pop ebp ret 0Ch

IL の状態 実行時 (ネイティブ化した状態 )

名前が残ってる名前が消えて、レイアウト情報だけになってる

Page 173: C#/.NETがやっていること 第二版

ダック タイピング†

• 同じ名前のメンバーを持っていれば同じ型扱いできないか

† アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」という論法からきた比喩表現

class Point{    public int X { get; set; }    public int Y { get; set; }     public void Add(Point p)    {        X += p.X;        Y += p.Y;    }}

Page 174: C#/.NETがやっていること 第二版

ダック タイピング†

• 同じ名前のメンバーを持っていれば同じ型扱いできないか

† アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」という論法からきた比喩表現

class Point{    public int X { get; set; }    public int Y { get; set; }     public void Add(Point p)    {        X += p.X;        Y += p.Y;    }}

?

X と Y というプロパティ ( またはフィールド ) を持っている任意

の型を使いたい

こういう処理にはリフレクション( メタデータの実行時利用 ) が必要

Page 175: C#/.NETがやっていること 第二版

dynamic 型

•ダック タイピング用の型

class Point{    public int X { get; set; }    public int Y { get; set; }     public void Add(dynamic p)    {        X += p.X;        Y += p.Y;    }}

X と Y というプロパティ ( またはフィールド ) を持っている任意

の型を使える

もちろん、内部的にはリフレクション使ってる

Page 176: C#/.NETがやっていること 第二版

dynamic 型

• 結構用途が限られる• そもそも C# でダック タイピングしたい状況が稀

• ほぼ、外部との連携用• COM との連携• 動的言語との連携• JSON みたいなスキーマレスなデータ読み書き

• その他、例えばできること• 多重ディスパッチ

• できないこと• メタプログラミング• C# のスクリプト的実行

Page 177: C#/.NETがやっていること 第二版

例 : 動的言語との連携

• DLR (Dynamic Language Runtime)• 例 : IronPython

var py = IronPython.Hosting.Python.CreateEngine();dynamic p = py.Execute("['a', 'b', 1, 2]"); for (var i = 0; i < 4; i++)    Console.WriteLine(p[i]);

Python コード

Page 178: C#/.NETがやっていること 第二版

例 : 多重ディスパッチ

• 静的な型に対してclass Base { }class A : Base { }class B : Base { }

Page 179: C#/.NETがやっていること 第二版

例 :多重ディスパッチ

• x, y の両方の型で動的に分岐• 仮想メソッドでは (素直には ) できない

static class Extensions{    public static string Dispatch(this Base x, Base y)    {        return (string)X((dynamic)x, (dynamic)y);    }    static string X(A x, A y) { return "A - A"; }    static string X(A x, B y) { return "A - B"; }    static string X(Base x, Base y) { return "others"; }}

動的な呼び出し

Page 180: C#/.NETがやっていること 第二版

例 :多重ディスパッチ

•呼び出し例

static void Main(){    Dispatch(new A(), new A()); // A - A    Dispatch(new A(), new B()); // A - B    Dispatch(new B(), new B()); // others    Dispatch(new B(), new A()); // others}

static void Dispatch(Base x, Base y){    Console.WriteLine(x.Dispatch(y));}

Page 181: C#/.NETがやっていること 第二版

dynamic 型の内部実装

• コード生成結果

dynamic X(dynamic x){    return x.X;}

object X(object x){    if (_site1 == null)    {        _site1 = CallSite<Func<CallSite, object, object>>.Create(            Binder.GetMember(CSharpBinderFlags.None, "X", typeof(Program),            new CSharpArgumentInfo[]            {                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)            }));    }    return _site1.Target(_site1, x);}

実際にはobject

動的コード生成用の情報

これが本体

Page 182: C#/.NETがやっていること 第二版

CallSite.Target の中身

• メソッドを動的コード生成してる• 生成したメソッドはキャッシュして持っておく

(inline method cache)

static object _anonymous(CallSite site, object x){    return site.Target を更新する処理}

_site1.Target の初期状態

Page 183: C#/.NETがやっていること 第二版

CallSite.Target の中身

• メソッドを動的コード生成してる• 生成したメソッドはキャッシュして持っておく

(inline method cache)

• 同じ型に対して何度も呼ぶ分には高性能

static object _anonymous(CallSite site, object x){    if (x is Point) return ((Point)x).X;    else return site.Target を更新する処理}

メソッド X に Point 型のインスタンスを渡した後

1 行追加単なる型判定+キャス

Page 184: C#/.NETがやっていること 第二版

CallSite.Target の中身

• メソッドを動的コード生成してる• 生成したメソッドはキャッシュして持っておく

(inline method cache)

static object _anonymous(CallSite site, object x){    if (x is Point) return ((Point)x).X;    if (x is Vector3D) return ((Vector3D)x).X;    else return site.Target を更新する処理}

さらに、メソッド X に Vector3d 型のインスタンスを渡した後

もう 1 行追加

ちなみに、最近はスクリプト言語でも、内部的に型を作って、 inline method

cacheで高速化してる

Page 185: C#/.NETがやっていること 第二版

“heavy” dynamic

• 用途によっては高性能なものの…• 多くの場合、過剰スペック• できるけど過剰スペックな例

• JSON みたいなスキーマレスなデータ読み書き

• もっと” light-weight” な dynamic が必要

p.X;p.M();

p.Get("X");p.Invoke("M");

例 : 規約ベースの置き換え

一時期、「 p.$x を p["x"] と解釈しよう」

という提案はあったけども、結局立ち消え中

Page 186: C#/.NETがやっていること 第二版

できないこと

• メンバー名がコンパイル時に既知でないとダメ

• なのでメタプログラミングには使えない• そのためにはもっと低レイヤーな API 使う

• IL Emit• Expression Tree• Roslyn

dynamic x = p;dynamic y = q;y.X = x.X;y.Y = x.Y;

コンパイル時に既知

Page 187: C#/.NETがやっていること 第二版

ポイント

• dynamic 型• C# でダック タイピング

• 主に外部との連携用• inline method cache

• 用途があえば結構高性能• 用途によっては過剰スペック

• メタプログラミング用ではない

Page 188: C#/.NETがやっていること 第二版

async/awaitC# 5.0

非同期処理

Page 189: C#/.NETがやっていること 第二版

スレッド関連おさらい

• 非同期処理• マルチタスク• UI スレッドを止めない• I/O ( 外の世界との入出力待ち )

• Task クラスを使いましょう• スレッド プール• I/O完了ポート

割と避けれないことなんだけども…

Page 190: C#/.NETがやっていること 第二版

非同期処理しんどい

•普通に非同期処理やったらコールバック地獄• begin/end地獄• イベント地獄• then地獄( ContinueWith地獄)

x.BeginX(args, state, ar =>{ var result = x.EndX(); …});

x.XCompleted += (s, arg) =>{ var result = arg.Result; …});x.XAsync();

x.XAsync() .ContinueWith(t => { var result = t.Result; … };非同期呼び出しが 1 回だか

らこの程度で済んでる

Page 191: C#/.NETがやっていること 第二版

•複数の確認ダイアログ表示

面倒な非同期処理の例

確認 1チェック

確認 2チェック

確認 3チェック

No

No

Yes

Yes

Yes

確認フロー

結果表示

No

ゲームでアイテムを合成します

レア アイテムですよ?

合成強化済みですよ?

もう強化限界ですよ?

ユーザーからの入力待ちも非同期処理

Page 192: C#/.NETがやっていること 第二版

同期処理if (Check1.IsChecked){

var result = Dialog.ShowDialog(" 確認 1", "1 つ目の確認作業 ");if (!result) return false;

} if (Check2.IsChecked){

var result = Dialog.ShowDialog(" 確認 2", "2 つ目の確認作業 ");if (!result) return false;

} if (Check3.IsChecked){

var result = Dialog.ShowDialog(" 確認 3", "3 つ目の確認作業 ");if (!result) return false;

} return true;

Page 193: C#/.NETがやっていること 第二版

非同期処理 (旧 )•画面に収まるように

フォント サイズ調整• 4pt• ほんの 84 行ほど

•ちなみに• 部分部分を関数化して多

少は整理できる• ダイアログ 3 つだからま

だこの程度で済む

if (Check1.IsChecked){

Dialog.BeginShowDialog(" 確認 1", "1 つ目の確認作業 ", result =>{

if (!result){

onComplete(false);return;

if (.Check2.IsChecked){

Dialog.BeginShowDialog(" 確認 2", "2 つ目の確認作業 ", result2 =>{

if (!result2){

onComplete(false);return;

if (Check3.IsChecked){

Dialog.BeginShowDialog(" 確認 3", "3 つ目の確認作業 ", result3 =>{

onComplete(result3);});

}else

onComplete(true);});

}else if (Check3.IsChecked){

Dialog.BeginShowDialog(" 確認 3", "3 つ目の確認作業 ", result3 =>{

onComplete(result3);});

}else

onComplete(true);});

}else if (Check2.IsChecked){

Dialog.BeginShowDialog(" 確認 2", "2 つ目の確認作業 ", result =>{

if (!result){

onComplete(false);return;

if (Check3.IsChecked){

Dialog.BeginShowDialog(" 確認 3", "3 つ目の確認作業 ", result3 =>{

onComplete(result);});

}else

onComplete(true);});

}else if (Check3.IsChecked){

Dialog.BeginShowDialog(" 確認 3", "3 つ目の確認作業 ", result3 =>{

onComplete(result3);});

}else

onComplete(true); 

Page 194: C#/.NETがやっていること 第二版

非同期処理( C# 5.0)if (this.Check1.IsChecked ?? false){

var result = await Dialog.ShowDialogAsync(" 確認 1", "1 つ目の確認作業 ");if (!result) return false;

} if (this.Check2.IsChecked ?? false){

var result = await Dialog.ShowDialogAsync(" 確認 2", "2 つ目の確認作業 ");if (!result) return false;

} if (this.Check3.IsChecked ?? false){

var result = await Dialog.ShowDialogAsync(" 確認 3", "3 つ目の確認作業 ");if (!result) return false;

} return true; • 同期処理と比べて await演算子が増えた

だけ• ダイアログの数が増えても平気

Page 195: C#/.NETがやっていること 第二版

イテレーターと似た仕組み

• イテレーター ( ジェネレーター ) があれば、割と単純なラッパーで非同期処理も可能• 要は、「中断と再開」

• 例えば、 TypeScript では• 現状でも await の MS社内実装は持ってる• いまだと、生成される JavaScript が悲惨すぎて大変• EcmaScript にジェネレーターが実装されてから、

TypeScript に await を追加する予定

Page 196: C#/.NETがやっていること 第二版

参考 : C# のイテレーター(再 )• 中断と再開

class MethodEnumerator : IEnumerator<int>{    public int Current { get; private set; }    private int _state = 0;     public bool MoveNext()    {        switch (_state)        {            case 0:             Current = 1;            _state = 1;            return true;            case 1:             Current = 2;            _state = 2;            return true;            case 2:             default:            return false;        }    }}

IEnumerable<int> Method(){    yield return 1;

    yield return 2;

}

Current = 1;_state = 1;return true;case 1:

状態の記録

中断

再開用のラベル

Page 197: C#/.NETがやっていること 第二版

基本的な考え方

•概念としては イテレーター +継続呼び出し

async Task<int> Method(){    var x = await task1;    var y = await task2;}

_state = 1;if (!task1.IsCompleted){    task1.ContinueWith(a);    return;}case 1:var x = task1.Result;

中断

状態の記録

結果の受け取り

再開用のラベル

非同期処理が終わったら続きから呼び出してもらう

Page 198: C#/.NETがやっていること 第二版

実際の展開結果

• 実際はもう少し複雑• Awaiter というものを介する (Awaitableパター

ン )_state = 1;var awaiter1 = task1.GetAwaiter();if (!awaiter1.IsCompleted){    awaiter1.OnCompleted(a);    return;}case 1:var x = awaiter1.GetResult();

• Awaiter を自作することで await の挙動を変更可能• Task以外も await可能• GetAwaiter は拡張メソッドでも OK

Page 199: C#/.NETがやっていること 第二版

Awaitable

• Awaitable なクラスは自作可能• 実装次第でいろいろ• Windowd Runtime

• IAsyncOperation インターフェイスとかを await できる

• Task の Awaitable 実装• UI スレッドへのディスパッチを内部的にやってくれる• そうしたくない場合の実装もある

• ConfigureAwait

Page 200: C#/.NETがやっていること 第二版

おまけ : C++ 1y

• C++ にも await が載るかも• C++ 17 に向けて標準化案出てる• C++/CX向けの実装を元に MS が提案

• 前述の通り、ついでにジェネレーターも

future<void> f(stream str) async{ shared_ptr<vector> buf = ...; int count = await str.read(512, buf); return count + 11;}

Page 201: C#/.NETがやっていること 第二版

おまけ : immutable

•並列処理といえば immutable だけども• 書き換えが起こらないなら複数のスレッドで共有

しても安全

• この用途だと、 C++ の const は不十分• あれは参照渡しを安全に行う用であって• 呼び出し元の側では const とは限らず、書き換わ

る可能性あり• あと、 mutable修飾子を付ければ const なオブ

ジェクトすら書き換えれる

Page 202: C#/.NETがやっていること 第二版

ポイント

• 非同期処理は、避けれないんだけども面倒• async/await で多少マシに• 実装的にはイテレーターと同様、中断と再開

Page 203: C#/.NETがやっていること 第二版

.NETCompiler PlatformCompiler as a Service

コンパイラーの内部データを活用

Page 204: C#/.NETがやっていること 第二版

.NET Compiler Platform†

• C#/VB コンパイラーを再設計・再実装• .NET 実装

• C# 実装の C# コンパイラー• VB 実装の VB コンパイラー

• コンパイラーの内部データを誰でも自由に使える

† コードネーム“ Roslyn” って呼ばれてたやつの正式名称

コンパイラーのサービス化、プラットフォーム化

Page 205: C#/.NETがやっていること 第二版

用途

• C# スクリプティング• C# で設定ファイル書きたい ( むしろ XML がい

や )• アプリの再起動なしでロジック更新

•ソースコード配置• .NET vNext, Cloud Mode

• コード解析• コードのインデックス化†• サーバー ビルド、テスト• IDE 連携

現状の最優先事項

† .NET の参照ソースコードが実際やってるhttp://referencesource.microsoft.com/

Page 206: C#/.NETがやっていること 第二版

構文ハイライト

• C# は文脈キーワードだらけstatic IEnumerable<async> async(){

var var = "var";var yield = new async();yield return yield;Func<string, Task<int>> async = async x =>{

await Task.Delay(100);return int.Parse(x);

};var await = async(var).GetAwaiter().GetResult();

}

メソッド名

クラス名

変数 キーワード

Page 207: C#/.NETがやっていること 第二版

リアルタイム エラー検出

•エラー検出タイミングがビルド時とか遅い• Visual Studio は常時やってる

Page 208: C#/.NETがやっていること 第二版

コード補完

• タイピングめんどくさい•文法やライブラリ、いちいち覚えたくない

Page 209: C#/.NETがやっていること 第二版

リファクタリング

•最初からきれいなコード書くのめんどくさい• でもほっときたくない

Page 210: C#/.NETがやっていること 第二版

ということで

• 今、コンパイラーに求められる要件• ソースコードのどこからどこまで(何行何列目)

が何かという情報がとれる• 文脈に応じてソースコードを生成したり、書き替

えたりできる• リアルタイム処理を必要とするので、パフォーマ

ンスも求められる

Page 211: C#/.NETがやっていること 第二版

実はこれまで

• コンパイラー (パーサー ) を 2 重開発してた• コンパイル用• IDE 用• まして、サード パーティ製コード解析プラグイン

も含めると、 3 重開発

• Java (Eclipse とか IntelliJ とか ) でも• (去年ぐらいによく言われてたけども)「 Eclipse が対応するまで Java 8 プレビュー試せないや」

Page 212: C#/.NETがやっていること 第二版

おまけ : 他の環境 (Clang)

• Clang はその辺りをゴールの 1 つに掲げてる• IDE で使う前提• リファクタリングに使いやすいデータ構造の構文木• インクリメンタル コンパイルとかもしやすい

Page 213: C#/.NETがやっていること 第二版

IDE 連携まで考えると

•簡単そうに見える文法ですら、実装コストかなり高い• 2 重開発はそれだけ大変• だから IDE を信用しない / できない人も多い

• IDE対応待ってられない /待ちたくない

• .NET Compiler Platform はそれを解消• C# に新機能を足しやすくなる• C# 6.0

Page 214: C#/.NETがやっていること 第二版

C# 6.0 (予定 ) の例

public class Point(int x, int y){

public int X { get } = x;public int Y { get } = y;

}

Primary Constructor / Property Expressions

while ((var line = stream.ReadLine()) != null)

line ...

if ((var x = obj as Point) != null)x ...

Declaration Expressions

immutable な型を作りやすく

「式」で書けることの幅が広がる

Page 215: C#/.NETがやっていること 第二版

ポイント

• 実行可能ファイルを作るだけがコンパイラーの仕事じゃない• スクリプト実行• コード解析

•特に、 IDE 連携• しかも、リアルタイム処理

• C#/VB コンパイラーの再設計= .NET Compiler Platform

このあたりまで考えるとちょっとした言語機能

を足すのも大変

Page 216: C#/.NETがやっていること 第二版