|
||||
イベント イベントは、メンバー変数と同じ方法で宣言されるが、まったく用途が異なる。 例えばGUIであるボタンをクリックすると、イベントが発火してあるプログラムが動作する。 「イベント駆動型プログラム」と呼ばれる。 | p.4 |
デストラクタ(破壊子) ファイナライザ(始末屋)とも呼ばれる。コンストラクタと兄弟分であり、 プログラムが必要としなくなったオブジェクトは、後から作成される新しいオブジェクトが 再利用できるように解放しなければならない。 この解放処理を担う | p.4 |
プロパティ 別クラスのメンバー変数を、カプセル化の原則を守りながらアクセスできる | p.5 |
インデクサ 配列を表現するクラスで使うのが主な目的。 プロパティと同様に、インデクサを使うとクラスの中にある配列を、普通の配列のように便利にアクセスできる。 | p.5 |
配列を扱う時、配列の要素数情報を各オブジェクトにメンバー変数として実装するのは効率が悪い。 理由は2点あり、 @各オブジェクトが同じメンバー変数を持ち、メモリの無駄。 Aそのメンバー変数を変える場合、各オブジェクト1つ1つ全てにアクセスしなければならず、 要素数が多い場合、非常に大変。 なおかつ、オブジェクトが作成されていない場合、情報の引き出し元がどこにもない。 この問題を解決するのが static である。 staticを使えば、 @その情報を持つメンバー変数はただ1つだけ存在し、配列全てのオブジェクトに所属する。 A配列全てのオブジェクトによって共有される。 Bオブジェクトが1つも作成されていなくても、ある値を持つ。 Cクラスを通じてアクセスすることができ、どのオブジェクトの中からでもアクセスできる。 もちろん、オブジェクトの外からもアクセスできるが、メンバー変数がpublicで宣言されていないとダメ。 (次に外からアクセスする例を書く) class 口座 { ・・・ public static uint 作成口座総数; ・・・ } class 銀行 { 口座.作成口座総数・・・//プロパティみたいな呼び出し方だ } 次の様な外からのアクセスは不可能。 class 銀行 { 口座 myAccount = new 口座(); myAccount.作成口座総数・・・//無効 } staticでクラスの中にメンバー変数実装して、コンストラクタでメンバー変数をインクリ・デクリするのが便利。 ※上ソースではstaticをpublic宣言しているが、基本的にstaticはprivate宣言すべき。 | p.10 |
new new クラス名() のみで、オブジェクトの作成がなされる。 但し、 クラス名 識別子 = new クラス名(); と書かないと、新しく作ったオブジェクトの参照先が反映されない | p. |
定数メンバーであるconstで表したメンバーは、デフォルトでstatic変数になる。 つまり、staticを書かなくてもconstを書けばstaticになるので、 他のオブジェクトからこの値をアクセスするには、「クラス名.定数メンバー識別子」と書く。 逆に、staticキーワードは定数宣言には使えない。constはデフォルトで既にstaticになるからだ。 (例) class 光 { ・・・ public const double 速度 = 299792; ・・・ } class 計算 { ・・・ ・・・光.速度; //定数メンバーにアクセスする方法。 //staticの時と同様に、外部クラスからアクセスする場合、 publicで宣言されていることと、オブジェクト指定でなく、クラスを指定して書く。 } | p.17 |
アクセス修飾子 アクセス修飾子には次のものがある public private protected internal protected internal | p.18 |
定数メンバーと同じく、readonlyメンバーも、変化しない値を表現するために使われる。 readonlyメンバー変数は、定数データメンバーによく似ているが、1つ重要な違いがある。 定数データメンバーの値は、プログラムがコンパイルされる時に、値が代入されるが、 readonlyメンバーに値が代入されるのは、オブジェクトが作成される時 つまり分かり易くいうと、constだとソースコードの中で定数値を指定する必要があるのに対し、 readonlyメンバーの値は、プログラムが実行されるまで分からなくても良い。 ・readonlyメンバーの値は、コンストラクタによって初期化され、その後は変えることができない。 定数メンバーがコンパイルされたプログラムのライフタイムを通じて変化しないのに対して、 readonlyメンバーの値が変わらない期間は、オブジェクトのライフタイムにとどまる。 ・readonlyメンバー変数は、constの様にデフォルトでstaticにならないので、 別途明示的に宣言することが必要。 private static readonly int カウンタ = 0; ・アクセス修飾子は、他のものと一緒の働きをするが、readonlyメンバーは読み出し専用であり、 値を代入することはできない。 ・readonlyメンバーの型はどんな型でもかまわない。 ・readonly識別子のキャピタライゼーションには、Pascal流を使うのが習慣。 (コード例) using System; class 口座 { public readonly string AccountNumber; //オブジェクトが存在する限り値を記憶 public 口座(string 恒久的な口座番号) { AccountNumber = 恒久的な口座番号; } } class SimpleBank { public static void main() { 口座 yourAccount = new 口座("8487-9873-9938"); Console.WriteLine("あなたの口座番号は: " + yourAccount.AccountNumber); } } AccountNumberはpublicで扱われているが、readonlyで宣言した値は、直接アクセスできるけども、 値の変更はできないので、カプセル化の原則は壊していない。 なので、メソッドやプロパティを使わず、直接アクセスしている。まさにreadonlyならではの書き方。 | p.4 p.18 p.75 |
メソッドの種類 メソッドは主に2つのグループに分類できる @インスタンスメソッド staticなしで定義され、インスタンス関数グループに属するもの。 インスタンスメソッドは特定のオブジェクトと関係して実行されるので、 これを実行するにはオブジェクトが必要。 Astaticメソッド staticで定義され、static関数グループに属する。 staticメソッドはクラスと関係して実行され、その呼び出しは特定のオブジェクトを必要としない。 クラス名を介して呼び出される。 | p.19 |
呼び出し方法は2種類ある。 staticメンバー変数のアクセスの仕方は、クラス名.staticメンバー変数という書き方だったが、 @staticメソッドの呼び出し方も同様の呼び出し方。 クラス名.staticメソッド()という呼び出し方 Aもう1つの呼び出し方があり、それが次のやり方。 Rocket rocketA = new Rocket(); ... ...rocketA.Average(); というように、普通にメソッドを呼び出す方法と同じ。 但し、クラス名.staticメソッド()という呼び出し方の方が望ましい。 staticメソッドの呼び出しであることを明確に告げているからだ。 | p.22 |
staticを用いるべき状況 @特定のオブジェクトに属すると決められないメソッドは、staticにすべき そのメソッド単体で完結するメソッドは、特定のクラスに属していないと言える。 そんなメソッドは、それのためにオブジェクトをいちいち作らず、static宣言すれば、 オブジェクトいらずでメソッドを呼び出せるので、とても便利。 例えばConsoleクラスには、Read(),ReadLine(),Write(),WriteLine()等のstaticメソッドがある。 staticメソッドを入れた、あるクラスを作れば、そのクラスをオブジェクト化せずに、 メソッドを呼び出すことなんかも便利。 | p.23 |
staticを用いるべき状況 Astaticメンバー変数をアクセスするために、staticメソッドを使う場合もある。 基本的に、staticメンバー変数はprivate宣言すべき。 private宣言されている時、外部からアクセスすることはできないので、 staticメソッドを介して、staticメンバー変数の値を得る方法がある。 これなら、クラス名.staticメソッド()と書けば、オブジェクトが1つも実体化されていなくても、 staticメンバー変数をstaticメソッドを介して呼び出すことが可能だ。 | p.24 |
インスタンスメソッドの中から、staticメソッドを呼び出すことも可能 普通のメソッドの呼び出し方でOK | p.27 |
staticメソッドの中から、インスタンスメソッドを呼び出すことも可能 但し、オブジェクトを実体化してから、それ経由でのメソッド呼び出しになる ※staticメソッドの中から、インスタンスメソッドや、インスタンス変数を直接使おうとするのは、 プログラマがよくやるミス ※staticメソッドの中から、インスタンス変数を直接扱えない。 | p.27 |
メソッドで値型の値を2つ(またはそれ以上)、呼び出し側に返す必要がある場合どうすれば? 参照パラメータを使えば良い! 参照パラメータは、キーワードrefを使って宣言できる。 動きとしては、メソッドに引数として参照パラメータを渡してやり、メソッド内にて値を書き換える。 (例) public static void Swap (ref int a, ref int b) //呼び出され側 Swap(ref 値A, ref 値B) //呼び出し側 これにて、aとbを参照パラメータとして宣言する。 (例) using System; class Swapper { public static void main() { int 値A = 10; int 値B = 20; Console.WriteLine("値Aと値Bを交換する前 値A={0} 値B={1}",値A,値B); Swap(ref 値A, ref 値B); Console.WriteLine("値Aと値Bを交換しました!値A={0} 値B={1}",値A,値B); } public static void Swap(ref int a, ref int b) { int temp; temp = a; a = b; b = temp; } } | p.28 |
変数に初期値を設定させるめのメソッドが欲しいこともあるだろう。 参照パラメータと全く同じ機能が必要だが、 それに加えて、まだ初期化されていない変数を引数として使うことができなければならない。 その能力を持つのが、出力パラメータ。 出力パラメータを宣言するには、メソッドヘッダの仮パラメータの型の前に、outキーワードを置く。 出力パラメータは、。初期化されていない変数を受け取ることができる。 この一点を除けば、出力パラメータは参照パラメータと同じ。 public static void Initialize(out int 新しい距離) 恐らく同様に、呼び出し側の引数にもoutキーワードが必要。 出力パラメータは、初期化されていない(見割り当ての)変数を受け入れるという違いを除けば、 参照パラメータと同じ。 ※出力パラメータには、制御の流れがメソッドから呼び出し側に戻る前に、 必ず値を代入しなければならない。 | p.29 |
引数がいくつあっても、正しい計算を行うことができるメソッドを書くことができる。 書き方は、メソッドヘッダの仮パラメータに1次元配列を書き、1次元配列の前にparamsキーワードを書く。 メソッドの実行中は、その配列を普通の配列を同じ様にアクセスできる。 public static int 和(params int [ ] numbers) 呼び出し側は、普通に 和(10,20,30);という様に、好きに書ける。 但し、引数は仮パラメータで宣言したint型に暗黙的に変換できる値でないといけない。 引数は0でも構わない。単純にnumbersの中身が0になるだけ。 上コードでは、呼び出され側では、numbersという配列の中に、10,20,30の要素が入る。 別の1次元の配列を引数として渡してもOK! int [ ] myNumbers = {1,2,3,4,5,6,7,8,9}; 和(myNumbers); public static int 和(params int [ ] numbers) { int 総計=0; foreach(int temp in numbers) { 総計 += temp; } } ※パラメータ配列は1次元でなければならない。 従って、paramsキーワードを使って、int[ , ]という配列を渡すことはできない。 但し、int [ ]、あるいは int [ ][ ]、int [ ][ ][ ]という風には書ける。 何故ならこれらは全て1次元配列だから。 | p.30 |
refキーワード、outキーワード、paramsキーワードを併用することはできない | p.32 |
メソッドヘッダの仮パラメータに、パラメータ配列以外を使いたい場合 パラメータ配列以外の、他のパラメータを入れる必要がある場合は、 パラメータ配列を、仮パラメータリストの一番最後に置かないといけない。 つまり、 public static int 和(int a, int b, params int [ ] numbers); というように書く。 次のコードは無効 public static int 和(params int [ ] numbers, int a, int b); outや、refはこの範疇でない。 ※このルールによって、1つのメソッドヘッダに入れられるパラメータ配列の数は、1個に限られる | p.32 |
メソッドに渡す引数の型がintでもdoubleでもカスタムメイド型でも何であっても、 引数の個数がいくつあっても、同じメソッド名を使えたら楽だし、自然。 それを実現するのが、メソッドの多重定義(オーバーロード) 1つのクラスの中で、同じ名前だが仮パラメータ集合と実装方法が異なる、 複数のメソッドを定義することが可能になる。 コンパイラが、与えられた引数を分析し、正しいメソッドを自動的に選択する。 @public double 距離(int x1, int y1, int x2, int y2); Apublic double 距離(double x1, double y1, double x2, double y2); Bpublic double 距離(Location L1, Location L2); というように、多重定義されたメソッドがあるとき、 myMap.距離(10,10,20,30); と書くと、自動的に最適な@が選択される。 | p.32 |
メソッドの多重定義に直接関連する重要な概念。 シグネチャ(特徴)を構成するのは、メソッド名と仮パラメータの量と型と並び方 @メソッドの戻り値の型は、シグネチャには含まれない。 つまり、次のメソッド3つは同じシグネチャと言える。 public double 和 (double a, double b); public int 和 (double a, double b); public void 和 (double a, double b); Aメソッドのシグネチャでは、paramsキーワードは無視される。 従って次のメソッドヘッダは、全く同じシグネチャを持つ。 public double 和 (params double [ ] numbers); public double 和 (double [ ] numbers); B仮パラメータの名前は、メソッドのシグネチャには含まれない。 従って次のメソッドヘッダは、全く同じシグネチャを持つ。 public int 和 (double x, double y); public int 和 (double a, double b); メソッド名は、メソッドのシグネチャを定義する様々な属性の1つに過ぎない。 .NET Frameworkのクラスライブラリには、数多くの多重定義が使われている。 Stringクラスの、IndexOf()になると、9つもの多重定義されたメソッドがある。 | p.37 |
メソッド呼び出しで、対応する仮パラメータで指定されている型と、 完全に一致しない型の引数があると、 コンパイラはその引数の型から仮パラメータの型への暗黙的変換経路を探そうとする。 | p.38 |
多重定義されたメソッド郡と暗黙的な型変換 ルール1 暗黙的な型変換によって引数と一致する型を見つけ出すプロセスにおいて、 コンパイラは3つの重要なルールに従う。 まずはその内の1つ目。 引数の型から暗黙的に変換できる型を、仮パラメータで指定している多重定義メソッドが、 2つ以上存在していたら、コンパイラはその中から1つを選ばなければならない。 その選定基準は、型変換の階層構造の中から、引数の型に最も近い方を選ぶことに決まっている。 型変換の階層構造へ | p.38 |
多重定義されたメソッド郡と暗黙的な型変換 ルール2 型変換の階層構造の図を見て、同じ優先順位を持つ多重定義されたメソッド郡があった場合、 例えばメソッド呼び出しの引数の型がlong型で、 float型とdecimal型の仮パラメータを持つ多重定義メソッドがあった場合、 コンパイラはどちらのメソッドを選べばよいのか? 答えは、コンパイラはどちらも選べない。 結果、メソッド呼び出しは「あいまい」とみなされ、コンパイラエラーを起こす。 long型以前の引数を持って、long型以降の仮パラメータを持つメソッドを探すと、 必ずこうなる。 | p.41 |
多重定義されたメソッド郡と暗黙的な型変換 ルール3 型変換の階層構造図を見ると、char,byte,ushort,uintからも2本の線が出ている。 これでは、上に書いたlongのときと同じ問題を起こすのではないか? しかしこれは大丈夫。 符号の有無によって、コンパイラは最適な暗黙的型変換を行う。 | p.41 |
メソッドの多重定義の使い方を誤ると大変なことに... 本質的に同じ動作を行うメソッドは多重定義しても良いが、 異なる動作を実行するとしたら、引数の型だけで、コンパイラにどちらかの選択を任せるのは得策でない。 異なる動作、例えば異なるインスタンス変数にリテラルを代入するメソッドならば、 それぞれで異なる識別子を持ったメソッドを作成すべき | p.42 |
オブジェクトのメソッドには、どれにもthisという名前の参照が自動的に備えられる。 そのメソッド呼び出しに使われた、現在のオブジェクトを参照する。 thisにはprivateクラスメンバーも参照できる特徴がある。 | p.45 |
thisの一般的な使い方 その1 仮パラメータやローカル変数を、インスタンス変数と区別するために使う。 通常ならばC#は、同じスコープに属する2つの変数が、同じ名前を持つことを許さない。 但し、片方がインスタンス変数で、もう片方が仮パラメータまたはローカル変数ならば、OK。 これを利用する。 メソッドを利用する時、仮パラメータやローカル変数には、 インスタンス変数との名前衝突を恐れずに、最も適した名前を付けるのが良い。 だが同じ名前にしてしまって、ある名前の識別子を書いたとき、 コンパイラはそれがインスタンス変数の方なのか、それとも仮パラメータ・ローカル変数なのか、 どうやって判断するのだろう? これには this が一役買う。 まず、ローカル変数と仮パラメータは、インスタンス変数よりも優先されるので、 普通に重複する名前の識別子が書かれたとき、それはローカル変数か、仮パラメータであると認識される。 それでは同じ名前のインスタンス変数にはどうやってアクセスするのか? thisは、インスタンス変数を含むオブジェクトへの参照である。 従って、this.識別子と書けば、それはインスタンス変数だと、コンパイラは解釈する。 | p.48 |
thisの一般的な使い方 その2 現在のオブジェクトへの参照を、引数として他のメソッドに渡すため | p.49 |
thisの一般的な使い方 その3 現在のオブジェクトへの参照を、メソッドの呼び出し側に返すため class Book { private double weight; public Book GetHeavierBook(Book aBook) { if (weight > aBook.GetWeight()) return this; else return aBook; } } GetHeavierBook()に渡される引数が、他のオブジェクトであるとき、 比較の結果、自身のオブジェクト(this)か、他のオブジェクト(aBook)かの、どちらかを返す。 | p.49 |
thisの使い過ぎでコードを散らかさないこと | p.50 |
初期化の種類 なぜコンストラクタが必要なのか? オブジェクトが作成されるとき、そのプロセスの一部として、 そのオブジェクトのインスタンス変数を表現するためのメモリが割り当てられる。 そのメモリが最後に使われたとき残された不定の値、いわばゴミが入っている。 これを元に演算しては、狙った値が得られない。 そこで必要なのが、インスタンス変数の初期化。 インスタンス変数の初期化の種類には、次のものがある。 @自動的な初期化 ソースコード上に明示的に初期化されていないインスタンス変数があれば、 それらはランタイムによって自動的に初期化される。 デフォルトの値は、インスタンス変数の型に依存する ・short,int,float,decimalなどの数値型にはゼロが代入される。 ・char型には、Unicode文字'\u0000'が代入される。 ・bool型には、falseに初期化される。 ・参照型には、nullに初期化される。 自動的な初期化、つまりインスタンス変数の初期化をランタイムに自動的に行わせるのは、 次の3つの理由で推奨できない。 ・コードを読むプログラマの目に初期値が見えない。 なので、そのプログラマは、デフォルトの初期値を意識していないかもしれない。 ・デフォルト値はコンパイラの種類によって違うかも知れず、同じソフトウェア会社が 出荷するコンパイラでもバージョンによって違うかもしれない。 ・デフォルト値は自分が狙った値と異なるかもしれない。 Aインスタンス変数に、宣言の一部で値を代入する。 private int 残高 = 100; の様に、宣言時に一緒に代入する方法。 以上の2点を比べれば、Aの方が良いが、Aでも代入を忘れてしまう間違いがあるかもしれない。 ただ、オブジェクトを作成するだけで、一緒に初期化してくれる方法はないか? それを実現してくれるのが、インスタンスコンストラクタの能力。 変数の初期化だけでなく、オブジェクト作成にまつわる何か処理が必要である場合、 それも一緒にインスタンスコンストラクタに書いてしまえば良い。 | p.58 |
インスタンスコンストラクタは、newキーワードを使って、 そのクラスのオブジェクトを作成する時に呼び出される、特殊なメソッド。 ・インスタンスコンストラクタのコンストラクタ修飾子は、とりあえずpublic ・インスタンスコンストラクタの定義でも、カッコのペアが必要で、newによって作成されたとき、 これらのパラメータに引数の値が渡される。 ・コンストラクタの多重定義も可能。 コンストラクタに入れるステートメントは、 初期化とオブジェクト作成に直接関係するものだけに限定すべき。 | p.62 |
宣言しているインスタンスコンストラクタのステートメント郡よりも前に実行すべき、 同じクラスのインスタンスコンストラクタを指定することができる。 例えば次の例では、Dog()が必ずDog(string initialName)よりも前に実行される。 class Dog { private int age; private string name; public Dog (string initialName) : this() //←コンストラクタ初期子 { name = initialName; } public Dog() { age = 0; } } | p.62 |
コンストラクタ初期化子を使う理由 C#でこの機能を使い理由は何だろう? それは、いくつもインスタンスコンストラクタに、同じステートメント郡を入れずに済ますことができる!! 例えば、インスタンスコンストラクタの多重定義がいくつも存在し、 それぞれの中身に同じステートメントが存在していたとき、 プログラマはそれらをインスタンスコンストラクタを書く毎に、書いていたのではとても面倒だ。 これを解消するために、共通的なステートメント郡だけを、 デフォルトのインスタンスコンストラクタに集めておき、多重定義された各インスタンスコンストラクタは、 コンストラクタ初期化子を通じて、それらのステートメントを呼び出すことができる。 つまり、無駄な重複するコードを省くことができる。 class 口座 { private decimal 残高; private decimal 利率; private string 名義; public 口座(decimal 初期残高, decimal 初期利率, string 初期名義) { 残高 = 初期残高; 利率 = 初期利率; 名義 = 初期名義; 銀行統計家.口座が追加された(残高); Console.WriteLine("口座が新設されました"); } public 口座(string 初期名義) : this (0,0,初期名義) //thisの中の引数をデフォルトのコンストラクタへ { Console.WriteLine("付記:名義情報だけ提供されています"); } public 口座() : this(0,0,"未定です") //thisの中の引数をデフォルトのインスタンスコンストラクタへ { Console.WriteLine("付記:情報は何も提供されていません"); } } このコンストラクタ初期化子thisに続くカッコ内の引数には、 インスタンスコンストラクタによって作成されたオブジェクトの一部へアクセスすることができない。 (下のコードはダメな例) public 口座(string 初期名義) : this(残高, this.利率, 初期名義) { ... } × 残高はインスタンス変数なので、アクセスできない × プログラムのこの部分でthisを使うことはできない ○ 初期名義は、インスタンスコンストラクタの仮パラメータに定められているので、スコープ内となり、OK | p.71 |
コンストラクタ初期化子の引数リストは、 それを含むインスタンスコンストラクタ自身の仮パラメータと一致してはならない class House { ... public House (int initialAge) : this(10) //無効。コンストラクタの仮パラメータリストと一致だから { ... } } このコードでは、自分自身に再帰的に呼び出すことを要求しており、半永久的にループしてしまう。 | p.73 |
継承に関する | p.62 |
new クラス() のカッコの中身 カッコの中に入る、引数リストはインスタンスコンストラクタに渡される。 つまり、インスタンスコンストラクタの仮パラメータリストと対応していなければならない 空の仮パラメータリストを持つインスタンスコンストラクタは、 「デフォルトのインスタンスコンストラクタ」と呼ばれる。別名引数のないインスタンスコンストラクタ あるクラスのソースコードの中に、プログラマがコンストラクタを書かなかったとき、 C#が自動的にデフォルトのインスタンスコンストラクタを作成する。これは、何も動作はしない。 自作のインスタンスコンストラクタを書いた場合、自動的にはデフォルトのコンストラクタは作成されない。 それが必要な場合、手動で書く必要がある。 例えその時、デフォルトのコンストラクタが不要でも、後に再利用する際に、スムーズに使えるよう、 デフォルトのコンストラクタを作っておくのが得策。それが無いと、カッコの中に何も書かないとエラーが出る。 | p.64 |
インスタンスコンストラクタの引数の用途 引数の用途は、新しいオブジェクトのインスタンス変数を初期化するのに使用される。 Car yourCar = new Car("BMW",1000); しかし、オブジェクトによっては、引数を1つしか必要としないものであったり、 1つも引数がいらないケースもある。 そのときのために、インスタンスコンストラクタを多重定義しておく必要がある。 ※多重定義されたインスタンスコンストラクタの中から、コンパイラがそれらを区別する順は、 多重定義された通常のメソッドのシグネチャと同じ流れで判別していく | p.66 |
今までインスタンスコンストラクタのコンストラクタ修飾子はpublicだったが、 privateにすると、そのクラスは外部から実体化されることを防止する造りに変わる。 用途としては、そのクラスの中身が全てstaticメンバーであり、間違って実体化されることを防ぐのに使われる。 | p.74 |
staticコンストラクタは、ソースコード内で明示的に呼び出すことができない。 その代わり、そのクラスの最初のオブジェクトが実体化されるときまでに、 ランタイムによって自動的に呼び出される。 @staticコンストラクタをアクセスできるのはランタイムだけなので、アクセス修飾子を指定するのは無意味。 A仮パラメータを指定することもできない。 class fish { private static int 作成された魚の数; static fish() { 作成された魚の数 = 0; } } 用途としては、 @static変数を初期化すること Aクラスの最初のインスタンスが作成される前に、一度だけ行う必要がある、別の仕事を実行させる目的にも | p.74 |
インスタンス変数など、それぞれ何らかの量のメモリを占有する。 メモリは貴重な資源だから、プログラムで使われなくなったオブジェクトからメモリを回収し、 そのあとに実体化されるオブジェクトのための場所を確保して、再利用することが重要。 ガベージコレクションに触れる前に、 オブジェクトはどのようにして孤立化するのだろうか? ↓↓↓ それへの参照が1つも存在しなくなったオブジェクトのことを言う。 次にオブジェクトを孤立させる4つの方法 @オブジェクトへの全ての参照にnull参照を代入する AオブジェクトAへの唯一の参照を含んでいる参照変数に、他のオブジェクトへの参照を代入すると、 オブジェクトAは孤立する。 BオブジェクトAが、オブジェクトBへの唯一の参照を含んでいる時、 オブジェクトAが孤立すると、オブジェクトBも孤立する。 Cオブジェクトへの唯一の参照を含んでいたローカル変数が、スコープから外れると、 そのオブジェクトは孤立する。 メソッド呼び出し内で、オブジェクトを参照したとすると、 そのメソッドが呼び出し元に戻ったとき、オブジェクトを参照していた変数はスコープから外れ、 参照されていたオブジェクトは孤立する。 | p.80 |
ガベージコレクタの仕事 孤立してどこからも到達できなくなったオブジェクトのメモリを再生するのに、 C#は.NETのランタイムがサポートするガベージコレクタ(GC : garbage collector)機構を使っている。 ちなみに、ガベージコレクタが再生するのはメモリだけ。他のリソースは解放しない。 ランタイムが自動的にメモリを管理する方法は、ガベージコレクション(GC : garbage collection) GCの主な機能は、孤立したオブジェクトの検出と、そのメモリの再生。 次に注意点を挙げる ・.NETのGCは、到達可能なオブジェクトを再生しない。 ・C#プログラムのバックグラウンドで仕事をし、孤立したオブジェクトの全てを検出して再生する。 ・GCがCPUを占有する時間を最小限に留めながら、 オブジェクトのために十分な量のメモリをできるだけ確保する。 但し、いつ再生するかは保証できない(わからない) GCが全く反応しないケースもある ・GCのアルゴリズムは、「遅延」と呼ばれる技法に基づく。短時間のスパートで一気にまとめて行われる。 オブジェクト孤立 → GC実行(いつかは分からない) → デストラクタ実行 手動でGCの実行を促すこともできる。(タイミングによっては従わない場合も) ⇒ System.GC.Collect(); これの用途としては、 @数多くのオブジェクトが孤立した AGCが実行されて一時停止することで、影響を受けやすい部分を、実行する前に | p.81 |
メモリ以外の希少なリソースを解放する オブジェクトは、メモリ以外の希少なリソースを占有することがある。 例えば、 @ファイル オブジェクトは、ライフタイムの間に、ファイルをアクセスして内容を読み書きするかもしれない。 ファイルへのアクセスは、オブジェクトがファイルハンドルを獲得し、ファイルハンドルをオープンする。 通常混乱を避けるため、ファイルを同時にアクセスできるオブジェクトは1つに限られる。 従って、オブジェクトが使われなくなってから、できるだけ早くファイルハンドルを解放して、 他のオブジェクトがアクセスできるようにすることが重要。 Aネットワーク/データベース接続 通常、ネットワークやデータベースへアクセスするユーザーは1人に限られ、 ユーザーが使い終わったらすぐに解放して、他のユーザーがアクセスできるようにすることが重要。 オブジェクトが孤立しようとするとき、それが占有していた希少なリソースを、 必ず即座に解放させるには、どうしたらいいだろう? この問題に対し推奨されるのが、「Disposeデザインパターン」と呼ばれるテクニック。 | p.82 |
どのクラスにもデフォルトのデストラクタが付けられる。 但し、C#プログラム実行中にデストラクタを呼び出せるのはGCだけ。 GCがオブジェクトのデストラクタを呼び出すのは、そのオブジェクトが孤立しているのを GCがたまたま検出したとき。 そして、デストラクタはいつ呼び出されるかわからない。 もしかしたらプログラム実行中に一度も呼び出されないかもしれない。 従って、デストラクタはメモリ以外の希少なリソースを解放するのに適していない。 (デストラクタの書き方) class 口座 { ... 〜口座() { Console.WriteLine("一度も呼び出されないかもしれない"); } } ・デストラクタ識別子は、コンストラクタの場合と同じく、そのクラスの識別子と同じであること。 ・デストラクタには仮パラメータを指定できない。 ・.NETでは「ファイナライザ」と呼ばれている。Finalize() | p.83 |
デストラクタの裏に潜む、リソース食いの機構 GCがスパート動作の間に、孤立していてデストラクタを持たないオブジェクトを見つけると、 即座に再生されるが、 デストラクタ宣言を持つ孤立したオブジェクトを見つけると、特殊なリストに入れられ、 最終的にメモリが解放されるのは、次々回GCスパートになってしまう。⇒効率悪い デストラクタの使用を避けるべき理由は、他にもある。 @オブジェクトのデストラクタが実行される順序は保証されない。 Aデストラクタがいつ呼び出されるかは保証されず、本当に呼び出されるかも保証されない。 Bデストラクタを持つオブジェクトは、より長い時間、メモリを占有する。 そればかりか、そのオブジェクト内に別のオブジェクトへの参照が含まれていれば、 それらも余分に長く保持されることになる。 | p.84 |
デストラクタの用途 ここまで読むと、デストラクタが役立たないようだが、結局次の2点がデストラクタの用途になる @オブジェクトが占有している、あまり希少でないリソースを解放するため。 例えば1個のオブジェクトだけがアクセスするファイルへのアクセスなどが、 それに該当する。 Aガベージコレクションの処理を調査するため(GCが動くとデストラクタ内で演算したり etc) | p.85 |
メモリ以外の希少なリソースを解放するのに、デストラクタが使えないとしたら、代替案はあるのか? Microsoftは、Disposeデザインパターンを使うことを推奨している。 メモリ以外の希少なリソースを占有しているオブジェクトには、 必ずDisposeという名前のpublicメソッド(自作)を持たせ、 その中にオブジェクトのリソースを解放するためのステートメントを入れておく オブジェクトを終了したら、そのDisposeメソッドを手動で呼び出しリソースを解放することで、 デストラクタを使ったときに生じる時間差の問題を解消できる。 但し、Disposeメソッドを呼び出した後、GCが再びそのオブジェクトに対し リソースを解放しようとするとエラーになる。 そのため、GCが起動してもオブジェクトのデストラクタを呼び出さないように指示する為に、 System.GC.SuppressFinalize()を呼び出せば良い。 これについて書かれた、P.91のサンプルプログラムが面白い。 | p.91 |
リソースの正しい処分の仕方 オブジェクトを正しく処分するには、使い終わった直後に、そのDispose()メソッドを呼び出し、 Dispose()メソッドを呼び出したらすぐに、そのオブジェクトを孤立させなければならない。 | p.96 |
sample | p. |
sample | p. |
sample | p. |
sample | p. |