アイスランドクローネ日記帳

音楽のこと、旅行のこと、ふと思ったこと、全く思っていないこと等を書きます。

プログラミング .NET Framework 第4版 / Jeffrey Richter

1年以上更新をサボってしまいました。びっくりですね。

さて最近、.NET Frameworkの本を読んでいます。
普段C#や.NETは特に使っていないので全く知識がないまま過ごしていたんですが、
やっぱり少しは知っておくべきかなと思って読んでみました。
が、内容が多すぎて消化不良を起こしているので、
自分用のメモとして忘れたくないことをいくつか書いておきます。
この辺に馴染みのある人には当たり前すぎる内容も多いと思いますがあくまで自分用です。
理解が間違っている可能性もあります。

1年ぶりの更新がただの自分用メモというがっかり感!


1. CLRとIL
C#を初めとするいくつかの言語をコンパイルするとマネージモジュールができる。
マネージモジュールは以下から成る。
IL : CLR上のアセンブリ言語みたいなもの
メタデータ : シンボル情報みたいなもの
CLRヘッダ : エントリポイントやCLRバージョンなど
PE32(+)ヘッダ : ビルド日時など

CLRのJITコンパイラがILをコンパイルしながら実行する。
2回目の関数呼び出し時はコンパイル済みのコードを直接実行する。
JITコンパイラは型チェックやパラメータ数チェックまでやってくれる。
NGen.exeを使えば機械語までコンパイルしたものをファイル保存することも一応できる。

感想
思ってた以上にJavaですね。
中間言語って重いもっさりしたイメージがあったけど、
むしろ直接機械語にコンパイルするより速くなる可能性もある、っていうのは意外だった。
夢が広がる。
普通のコンパイラよりJITコンパイラの方がその時その時の詳細な情報を使って
より最適化した機械語を生成できる、という理屈らしい。


2. モジュールのメタデータ
定義テーブル:モジュール内で定義されている型、メソッド、フィールド、パラメータ、プロパティ、イベントの情報。
参照テーブル:モジュールから参照されるアセンブリ、モジュール(DLLのファイル名)、型、フィールド、メソッド、プロパティ、イベントの名前。

他のDLLを参照するモジュールをビルドする時は、
このメタデータを使って実際に型名とかが合っているかをコンパイラが確認するので、
そのDLL(本体またはマニフェスト)が実際にないとコンパイルエラーになる。


3. アセンブリ
1つ以上のマネージモジュールと(あれば)リソースファイルの集まり。
代表モジュールがマニフェスト(ファイルの型のリスト)を持つ。
Visual Studioはマルチファイルアセンブリ生成をサポートしていない。
ファイルを分けて段階的にダウンロードしたい場合や、
別の言語でコンパイルしたものをまとめて1つのグループにしたいときに有効。

感想
話がややこしい割にメリットが少なそうだし、
Visual Studioがサポートしていないと知ってやる気をなくす。


4. アンマネージドとアンセーフ
似た概念が多くて分かりにくい。
アンマネージドコード:ILにならずに直接機械語になるコード。C++はマネージドとアンマネージドを混在できる。
アンセーフコード:メモリを直接触れるコード。unsafeキーワードで記述し、/unsafe設定でコンパイルすればC#でも可。

感想
アンセーフって響きが怖いからやめてほしい。
ポインタ便利じゃないですか。


5. FCL(Framework Class Library)
基本型、Direct X関係、XML関係、データベース関係などMicrosoftの無数の汎用ライブラリ。


6. CTS(Common Type System)とCLS(Common Language Specification)
CTS:言語共通の型システム。全ての型がSystem.Objectから継承されるだとか、publicやprivateなどの情報も持つだとか、が定められている。
CLS:言語間の情報のやり取りにも使えるようなCTSの最小サブセット。


感想
あらゆる言語が共通のシステムで動くなんて素晴らしいけど、
結局言語の規格をガチガチに固めているだけなんじゃないかという気もする。
複数言語が一緒に使える便利さがピンとこない。やってみないと分からないんだろうな。


7. カルチャ
言語。デフォルトはNeutral。
カルチャ依存のリソースはまとめて各カルチャでサテライトアセンブリを生成するといい。
その場合、リソースの集まりなのでコンパイラではなく、AL.exeで生成する。


8.プライベート配置とグローバル配置
プライベート配置:exeと同じディレクトリに置くこと
グローバル配置:複数のアプリで使えるように共通ディレクトリに置くこと

グローバル配置はディジタル署名された「厳密名を持つアセンブリ」である必要があり、
バージョン情報、カルチャ、公開キーのハッシュ値のトークンと合わせてアセンブリを識別する。
グローバル配置されるアセンブリはGAC(Global Assembly Cache)に置く。


9.遅延署名
デバッグのために署名しないでおくこと。ずるい。


10. is演算子とas演算子
is演算子:キャストできるかどうかブールで返す
as演算子:普通のキャストをする。できない場合はnullを返し、例外を投げない。


11. プリミティブ型
intはSystem.Int32のことであり、longはSystem.Int64のことである。
charは16ビットUnicode値である。
こういう微妙にCっぽくてCじゃないのやめてほしい。
decimalって128ビットの10進の浮動小数点なのか。


12. checked演算とunchecked演算
オーバーフローチェックをしたければchecked演算。
特定の場所にコーディングしてもいいし、コンパイラの設定にしてもいい。
もちろん遅くなる。


13. 参照型(class)と値型(struct)
classはヒープに、structはたとえnewしてもstackに積まれる。
structは継承できないし、nullを持たない。
nullを持たせたければnullable typesにするという手もあるけど間違えそう。
structは油断するとbox化されてヒープに積まれてしまう。
Object型にキャストするときなど。
box化が予想外のところで起きると意外な動作になることがあるので、
structのメンバはできればreadonlyにしたい。

感想
値型こわすぎる。ボックス化がカオス。
でも全部readonlyにするのも使い勝手悪そう。
ByteやInt32などの基本型もimmutableです、って書いてあるけどよく分からない。
代入するとインスタンスを丸ごと置き換えるってこと?


14. フィールドのレイアウト
structのメンバは宣言順にメモリに配置され、classはコンパイラが勝手にメモリ配置を最適化する。
ただし、属性の設定でこの辺は変えられる。
LayoutKind.Autoで自動配置に変えたり、
LayoutKind.Sequentialで指定配置に変えたりできる。
LayoutKind.ExplicitでFieldOffset(i)を使えば、オフセット値を指定できるので共用体みたいなこともできる。


感想
C#で共用体できるなんて思わなかった。素敵。


15. Equals()メソッド
Equalsメソッドをオーバーライドするときは、
反射的、対称的、推移的、一貫的にすること。

感想
当たり前のことなのにこう書くとグラフ理論みたいでワクワクする。
そもそもEquals()メソッドなんてオーバーライドしないな…。


16. var型とdynamic型
コーディングを省略してコンパイラに型を決めさせるのがvar型。
実行時に動的に型が決まるのがdynamic型。
dynamic型にキャストはできるけど、var型にはできない。
var型はメソッド内のローカル変数にのみ使えるが、dynamic型は引数や戻り値などいろいろ使える。


感想
C系の言語だとvarを使うほど型に対してゆるい気持ちにはならないよなぁ。
dynamicは素直に解決できないときに便利なんだろう。


17. 継承や仮想関数について
この辺は後から宣言を変えたらソリューションで全ビルドしないと危険。
DLLの独立性は過信するとこわいな。


18. const
const変数は参照する側がコンパイル時に値を埋め込んでしまうので、
あとから値を変えてビルドしなおしても、参照する側をビルドかけないと反映されない。
あくまで変数として残しておきたいならreadonlyが無難。

感想
こわすぎる。
C++の気持ちでconst至上主義になると危険。


19. コンストラクタ
初期値を持つメンバ変数がたくさんあって、コンストラクタもたくさんあるときは、
基本コンストラクタでメンバ変数を初期化して、
他のコンストラクタは:this()と継承すると初期化コードの爆発を防げる。
あと、値型のコンストラクタはnewしないと呼ばれないから危険。

感想
:this()のコンストラクタとかマニアックだなー。


20. キャストのオーバーライド
キャストすら自前で定義できる。


21. 戻り値の型が違う関数のオーバーロード
CLRでは定義されているけど、C#では定義されていない。
キャストのオーバーライドで内部的に使用されている。

感想
C#で使えないようになっていてよかった。混乱する。
たまーに欲しくなるけど。


22. 拡張メソッド
引数の型の前にthisを付けると、そのクラスに定義されていないメソッドを別クラスで定義できる。

感想
ややこしいからやめてほしい。


23. 名前付き引数
いいね!
特にbool引数は呼び出し側を見てもさっぱりわからないから。


24. メソッドにパラメータを参照渡しする
refやoutを付けると参照渡しになる。
もともと引数の型が参照型だったら関係ないでしょと思ったら違った。
さらに1段上の参照になる模様。
参照型の参照渡しは気持ち悪い。

refとoutは出力ILが同じでもキーワードとコンパイル条件が異なる。
正しい言語設計だと思います。


25. 可変個引数
params指定でできる。これは便利!
ただし引数用にヒープに配列を生成するので遅い。
StringライブラリのConcat関数では、
1,2,3個の関数は専用関数がオーバーロードされている。

感想
速度を気にして冗長な関数を定義するのは正しいけど悔しい。


26. const関数
CLR対応の言語ではconst関数は書けない。残念。
ポインタだけconstでも参照先の値が変わったら意味ないから
そもそも本質的に無意味でしょという指摘は確かに正しい。
C++で誰もが思う矛盾。


27. プロパティ
最初これは便利だと思ったけど、
ちょっと考えるとシンプルさに欠けてて嫌になってくる。
代入文くらいはシンプルに眺めたい。
と、思っていたけど、この本の著者はそれ以上のバッシングっぷりでおもしろい。
デバッガから覗いただけで実行されるとかおもしろい。
設定でデバッガからの呼び出しは無効にもできる。


28. 匿名型
var c = new {A=a, B=b};
しかるべきときに思いつけば便利なんだろうな。


29. ジェネリック型
苦手なテンプレート。
ArrayListよりListがいいらしい。
ArrayListはObjectの列だから、値型を入れると毎回box化されてしまう。
型の名前に<>がたくさん出てきて汚いと思ったらusingで名前を付けましょう。

いろんな型で呼び出すとコードが爆発的に増える。
が、参照型の場合、結局どれもただのポインタなので、
共通の1つのメソッドでやってくれる。
これはすごい。


30. インターフェース
JavaJavaしている。

31. String
逐語的文字列(verbatim strings)は@"A\B"のように書く。
\がエスケープ文字にならない。
Stringは不変なので、編集しまくる場合はStringBuilderを用いる。

32. enumとビットフィールド
enum名がToString()で文字列として使える!これは嬉しい。
けど、ソースコードというシンタックスが文字列というセマンティクスに直結していいのだろうか。


33. ジャグ配列
これ便利だなー。


34. 配列を扱うfor文
for (Int32 index = 0; index < a.Length; index++)
の「a.Length」はプロパティだからメソッド呼び出しに相当するけど、
JITコンパイラが一時変数に格納してくれるから自分でローカル変数用意するより高速。
for文の条件式にsize()みたいな関数があるとついローカル変数用意してたからこれは意外だった。


35. アンセーフ配列
unsafeブロックでは、高速化のためにスタック上に1次元ゼロ始まり配列を生成できる。
unsafe { Char * stackalloc Char[width]; }


36. デリゲート
関数ポインタみたいなものなのかなと思ってたけど、
デリゲート宣言した関数ごとに専用のクラスをコンパイラが生成する。
_invocationList のフィールドがデリゲートチェインを表す。
これで複数のコールバックメソッドを連続して呼び出すことができる。便利。


38. カスタム属性
普通の属性すら使いこなせないのにカスタムとは。
普通の関数内で、this.GetType().IsDefined(typeof(カスタム属性名), true/false)
でカスタム属性が定義されているかどうかの判断や分岐ができる。


39. null許容型
Boolean hasValueフィールドを持つstruct。
モチベーションは分かるけど複雑になり過ぎる気がする!
Booleanのnullが3値論理の中間の真偽値になるのがおもしろい。
falseでもいい気がするけど。


40. null合体演算子(??)
null許容型や参照型に使える。


41. 独自Exceptionクラスは広く浅く定義する
たくさん継承させるとなんだかよく分からない基底例外クラスが増える。
あとから派生例外クラスを作ったときに、既存の処理メソッドが有効かどうか判断しづらい。


42. 独自Exceptionクラスはシリアル化可能(ISerializable)であるべき
アプリケーション境界を越えたりログに残したりするため。


43. あらゆる例外を処理しようとするのは間違い。
普通に1行かいただけで、場合によっては10種類以上の例外が発生しうる。
生産性を考えると、全ての例外に対処しようとするのは間違い。
例えばOutOfMemoryExceptionなんかは割といつでもどこでも起こり得るので、
あらゆるところにcatch文を書くのは現実的でない。
CLRが未処理例外発生時にアプリケーションを落としてくれるから、
他への被害を昔ほど深刻に考えなくていい。
デバッグ中であれば未処理例外からいろいろな情報が分かるので、
catchしない例外はそもそも発生しないように開発中にバグ出しする。

感想
実際問題、処理できないなとは思っているけど、
こういう教科書っぽい本にもそう書かれると少しショック。
全ての例外をcatchしましょうというのが正しい意識かと思っていた。


44. なんでもcatchしない
catch(Exception) は書いてはいけない。
何が起きたか分からなくなるから。
耳が痛い。分かってたけど。


45. catch文で別の例外を投げる
呼び出し元に正しく伝わるような意味合いの例外に変えて投げなおす。
場合によってはそれもありであるとのこと。
わけわかんなくなるからいやだなぁと思う。


46. 未処理例外のWindowsログ
問題の署名07 がクラスとメソッドを表している。
ILDasm.exeでこの値を見れば分かる。


47. コードコントラクト
System.Diagnostics.Contracts.Contract 静的クラスを使うと、
事前条件、事後条件、不変条件をコード中に埋め込める。
デバッグに有効。


48. ガベージコレクション
ヒープがある程度たまると実行される。
オブジェクトの参照先をひたすら辿ってスタンプを押していく。
終わったらスタンプがないオブジェクトを消して、ヒープ領域内でデータを詰める。
参照先アドレスも直す。
大きいデータ(現状85KB以上)はコピーに時間がかかるのでデータの移動はしない。
毎回全オブジェクトでやると時間がかかるので、
ジェネレーション0,1,2と分けて、それぞれでGC発動条件を確認する。
オブジェクトはGCを生き残ると古いジェネレーション(1,2)になっていく。
新しいジェネレーション(0)ほどデータが増えやすくGC発動しやすい。
最近できたオブジェクトの多くはすぐ使い終わるだろう、という発想。
オブジェクトの古さに関する局所性。

感想
2秒周期のTimerオブジェクトがGCされて1回しか呼ばれない例が怖すぎる。
心配しないでくださいと言われても。


49. GC起動要因
コードで明示的に呼ぶ、Windowsの空きメモリ低下、アプリケーションドメインアンロード、LCRシャットダウン。


50. GCのモード
ワークステーション(デフォルト):クライアントの遅延を最小化するようにGCが最適化される。
マシン上で他のアプリケーションも動作中であるという前提のもと、CPUをなるべく占有しない。

サーバー:サーバーのスループットとリソース使用効率に対してGCが最適化される。
他のアプリケーションは存在しないという前提で動作する。
複数のCPUで並列GCをやる。


51. ファイナライザ
ファイル閉じるなどの処理をするべき。
C#ではデストラクタと呼ぶが、C++のデストラクタと挙動違うので注意。
スコープから外れたときではなく、GCで不要と判断されたときに呼ばれる!!

感想
それは紛らわしい。


52. オブジェクトの生存期間
System.Runtime.InteropSevices.GCHandle型でオブジェクトの生成期間を設定できる。
NormalはGCで削除されないが移動はされる。PinnedはGCで削除も移動もされない。
C#のfixed文でもピニングできる。


53. アプリケーションドメイン
1つのアプリケーション内で使用されるアセンブリの集まり。
System.dll等はアプリケーションドメインごとにそれぞれロードされる。
MSCorLib.dll のようにドメイン中立の共有されるものもある。
アプリケーションドメインを越えてアクセスすることもできる(Marshal)が、
すごく大変。


54. アセンブリの埋め込み
DLLを全てプロジェクトに追加し、プロパティ->ビルドアクション->埋め込まれたリソース
をやると、exe1つになる。
それだけだと実行時にDLL見つからないエラーが出るので、
AsemblyResolveイベントにコールバックメソッドを登録し、うまいことやる必要がある。

感想
これやりたくなる。もう少し楽にならないのだろうか。


55. リフレクション
メタデータの解析や使用ができる。
コンパイル時に道である型やメンバーを発見したり使用したりできる。
他者が拡張可能なアドインをサポートするアプリケーションを作れる。
ただし、リフレクションは低速。
普通の動的な型使用であればインターフェースを利用するべき。

感想
やらないなー。


56. シリアル化
オブジェクトをただのバイト配列にする。
ファイル保存、ネットワーク通信などに使える。
逆シリアル化の際にフォーマットやアセンブリファイル名、CLRバージョンが合っている必要がある。
途中で失敗する可能性があるので、
いきなりネットワーク通信したりせずに、
まずはメモリ上にシリアル化すること。


57. スレッド
スレッドカーネルオブジェクト:スレッドのプロパティやCPUレジスタセット。
スレッド環境ブロック(TEB):スレッドの例外チェインの先頭を保持。
ユーザーモードスタック:規定では1MB
カーネルモードスタック:Windowsのシステムコールするときにユーザーモードスタックからコピーされてから引数チェックされる。
DLLのスレッドアタッチ通知とスレッドでタッチ通知:スレッド開始時と終了時にアンマネージDLLのDllMain関数が呼ばれる。


58. CLRの論理スレッド
.NET Frameworkの初期段階の構想。
Windowsのスレッドに対応づける必要のないスレッド。

感想
こういう歴史ものおもしろい。
けど、そのせいで微妙な設計が残っていることも多いのは寂しい。


59. スレッド優先度
0~31。
「プロセス優先度クラス」と「相対スレッド優先度」の組み合わせで決まる。


60. フォアグランドスレッドとバックグラウンドスレッド
全てのフォアグラウンドスレッドが終了すると、バックグラウンドスレッドも同時に全て終了する。
Thread tのt.IsBacground = true/falseで指定可。


61. スレッドプール
スレッドの作成にはオーバーヘッドがかかるので、
CLRごとに1セットあるスレッドのセットから持ってきて使うことができる。
最初は0個だが、使用して使い終わっても消えずに次の別のスレッドに再利用される。


62. タスク
作成側が、完了まで待機して戻り値を得ることができるスレッド。

63. 非同期関数
関数にasyncと書くとI/O処理に適した非同期関数になる。
処理の状況を表すTaskオブジェクトを戻り値に持つ。


64. volatile と interlock
volatileのイメージはCと同じ。最適化抑制。
interlockは処理をアトミックにして順序を守らせる。
intの読み書きがアトミックなのは当然だと思っていたので、
指定する必要があるとは思ってなかった。
マルチスレッドやっぱり大変だな。。



ちょうど64個になったのでここで終わり。
知らないことだらけだな。
むやみやたらと書いてしまった。
  1. 2016/07/24(日) 02:09:46|
  2. | トラックバック:0
  3. | コメント:0
<<クラウドを支える技術 データセンターサイズのマシン設計法入門 (技術評論社) | ホーム | マニュライフ生命わくわくチャリティラン2015 駅伝&ハーフマラソン in 味スタ>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバックURLはこちら
http://myumbrella.blog42.fc2.com/tb.php/317-9f8b6ff5
この記事にトラックバックする(FC2ブログユーザー)