講義メモ ・p.222 練習問題をクリアして、第9章に進みます p.222 練習問題1 ヒント ・p.209 prop02.csを単純化すると良い ・double型のデータメンバ1つのみになる ・Mainメソッドで負の数や0を与えて動作を確認すること 作成例 //p.222 練習問題1 using System; class BMI { double bw; public double bwprop { //プロパティ set { if (value > 0) { //代入値が正の数なら bw = value; //代入値を代入 } } get { return bw; //値を返す } } } class prop02 { public static void Main() { BMI mybmi = new BMI(); mybmi.bwprop = 5; //プロパティbwpropのsetアクセッサを呼ぶ Console.WriteLine(mybmi.bwprop); //プロパティbwpropのgetアクセッサを呼ぶ mybmi.bwprop = -5; //プロパティbwpropのsetアクセッサを呼ぶ(負の数なので無視される) Console.WriteLine(mybmi.bwprop); //値は変わらない } } p.222 練習問題2 ヒント ・p.217 indexer03.csをベースにすると良い ・コンストラクタで生徒数を入力する部分は、p.214 indexer02.csが参考になる ・生徒名を格納するstring型配列と、点数を格納するint型配列を宣言して用いると楽  ※ 生徒名と点数をメンバとするクラスオブジェクトの配列にできればさらに良い ・要素の生成はコンストラクタで行うと良い ・Mainメソッドで格納結果を表示すること 作成例 //p.222 練習問題2 using System; class MyIndexer { int[] point; //点数の配列を宣言 string[] name; //名前の配列を宣言 int num; //要素数 public MyIndexer(int n) { //要素数nを受け取るインデクサ point = new int[n]; //点数の配列を生成 name = new string[n]; //名前の配列を生成 num = n; } public int this[string Name] { //インデクサ①の定義(データはintで、インデックスはstring) get { for (int i = 0; i < num; i++) { //配列の全要素について繰返す if (Name == name[i]) //インデックスと同値?(名前が一致?) return point[i]; //点数を返す } return -1; //当てはまる名前がなければ-1を返す } set { for (int i = 0; i < num; i++) { //配列の全要素について繰返す if (Name == name[i]) //インデックスと同値?(名前が一致?) point[i] = value; //点数を格納 } } } public string this[int i] { //インデクサ②の定義(データはstringで、インデックスはint) get { return name[i]; } //i番の名前を返す set { name[i] = value; } //i番の名前を格納 } } class indexer03 { public static void Main() { MyIndexer mi = new MyIndexer(3); //コンストラクタで3人分を用意 mi[0] = "Amuro"; //インデクサ②のsetで名前を格納 mi[1] = "Shar"; //同上 mi[2] = "Bright"; //同上 mi["Amuro"] = 60; //インデクサ①のsetでその名前の点数を格納 mi["Shar"] = 70; //同上 mi["Bright"] = 80; //同上 for (int i = 0; i < 3; i++) { string myname = mi[i]; //インデクサ②のgetで●番の名前を得る int mypoint = mi[myname]; //インデクサ①のgetでその名前の点数を得る Console.WriteLine("{0}は{1}点", myname, mypoint); } } } 第9章 継承 p.223 9.1 クラスの継承の基礎 ・クラスは乱立しやすく、同じことが複数のクラスに記述されてしまったり、適切なクラスを探しづらい ・そこで、クラスに親子関係を持たせるのが継承 ・親になるクラスを基本クラス、子になるクラスを派生クラスといい、派生クラスは基本クラスを継承するという ・派生クラスの書式: class 派生クラス名 : 基本クラス名 {…} ・例: class HoimiSlime : Slime {…} ・C#では派生クラスの基本クラスは1つのみで、基本クラスの派生クラスはいくつでもOK(単一継承という) ・基本クラスのpublicメンバ定義はすべて派生クラスに引き継がれる(継承される) ・基本クラスを書き換えると、自動的に反映するので「同じことが複数のクラスに記述される」ことが防止できる ・例: class Slime { public int hp; } ・例: class HoimiSlime : Slime { public int mp; } //hpは記述しなくてもSlimeから継承される ・継承のことをインヘリタンスともいう p.224 inheritance01.cs //p.224 inheritance01.cs using System; //基本クラス class MyBase { public int a = 10; //継承可能なデータメンバ public void BaseMethod() { //継承可能なメソッド Console.WriteLine("ここは基本クラスです"); } } //派生クラス class MyDerived : MyBase { //MyBaseクラスを継承 //ここに public int a = 10; があるとみなされる //ここに public void BaseMethod() {…} があるとみなされる public int b = 20; //派生クラス独自のデータメンバ public void DerivedMethod() { //派生クラス独自のメソッド Console.WriteLine("ここは派生クラスです"); } } class inheritance01 { public static void Main() { MyDerived md = new MyDerived(); //派生クラスのインスタンスを生成すると md.DerivedMethod(); //派生クラス独自のメソッドが実行できる Console.WriteLine("md.a = {0}", md.a); //基本クラスから継承したデータメンバが使える Console.WriteLine("md.b = {0}", md.b); //派生クラス独自のデータメンバも使える MyBase mb = new MyBase(); //基本クラスのインスタンスも生成できる mb.BaseMethod(); //基本クラスのメソッドが実行できる Console.WriteLine("mb.a = {0}", mb.a); //基本クラスのデータメンバが使える } } p.226 protectedによるデータの保護 ・基本クラスにおけるメンバの継承可をアクセス修飾子のpublicで行うと、外部から自由に利用できてしまい良くない ・そこで、アクセス権限はprivateだが、継承は可であるメンバはprotected指定にすると良い アレンジ演習:p.226 inheritance02.cs ・Mainで「md.x = 30;」とするとエラーになることを確認しよう 作成例 //アレンジ演習:p.226 inheritance02.cs using System; class Base { //基本クラス protected int x = 20; //継承可能だが非公開のデータメンバ } class Derived : Base { //Baseクラスを継承する派生クラス //ここに protected int x = 20; があるとみなされる int y = 10; //派生クラス独自のデータメンバ public void ShowXY() { //派生クラス独自のメソッド // このクラスからはBaseクラスのxは見える Console.WriteLine("x = {0}, y = {1}", x, y); } } class inheritance02 { public static void Main() { Derived md = new Derived(); // md.x = 30; //protectedなので継承は可能だがアクセスは不可 md.ShowXY(); } } p.228 名前の隠蔽 ・派生クラスで基本クラスと同じ名前のデータメンバや、シグニチャ(名前と引数の型・数・並び)が同じメソッドを記述できる ・すると、名前の隠蔽が起こり、派生クラスにおける定義が優先される(見かけ上、上書きになる) ・名前の隠蔽が起こっていることを派生クラスのメンバの定義の先頭に「new」を記述することで明示できる(明示が推奨される) p.228 inheritance03 //p.228 inheritance03 using System; class Base { //基本クラス public int x = 10; //継承可能で公開のデータメンバ public void BaseMethod() { //継承可能で公開のメソッド Console.WriteLine("Baseクラスです"); } } class Derived : Base { //Baseクラスを継承する派生クラス //ここに public int x = 10; があるとみなされるが↓により隠蔽される new public int x = 20; //継承したデータメンバを隠蔽する //ここに public void BaseMethod() {…} があるとみなされるが↓により隠蔽される new public void BaseMethod() { //継承したメソッドを隠蔽する Console.WriteLine("Derivedクラスです"); } } class inheritance03 { public static void Main() { Derived d = new Derived(); Console.WriteLine("x = {0}", d.x); //Derivedクラスの隠蔽したデータメンバが使われる d.BaseMethod(); //Derivedクラスの隠蔽したメソッドが呼ばれる } } p.229 名前の隠蔽:baseキーワード ・「base.」を記述することで、隠蔽されたメンバを、派生クラスの中で利用することができる アレンジ演習:p.228 inheritance04.cs ・「base.」を記述することで、隠蔽されたメンバを、派生クラスの中で利用することができるが、隠蔽されたデータメンバの値を、派生クラスの中で書き換えるとどうなるか確認しよう 作成例 //アレンジ演習:p.229 inheritance04.cs using System; class Base { //基本クラス protected int x = 10; //継承可能で非公開のデータメンバ } class Derived : Base { //Baseクラスを継承する派生クラス //ここに protected int x = 10; があるとみなされるが↓により隠蔽される new int x = 20; //継承したデータメンバを隠蔽する public void Show() { //派生クラス独自のメソッド base.x = 30; //【追加】隠蔽されたメンバの値も変更できる Console.WriteLine("base.x = {0}, x = {1} ", base.x, x); //隠蔽されたメンバと隠蔽したメンバ } } class inheritance04 { public static void Main() { Derived d = new Derived(); //派生クラス d.Show(); //派生クラス独自のメソッドを呼ぶ } } p.230 メソッドのオーバーライド ・メソッドにおいては名前の隠蔽とオーバーライドの2パターンから選ぶことができる ・オーバーライドを選ぶと、p.232で後述する多態性(ポリモフィズム)が実現し、オブジェクトの扱い方を効率化できるので、一般にメソッドにおいては名前の隠蔽ではなくオーバーライドを用いることが多い ・ただし、オーバーライドにおいては、基本クラス側でオーバーライドを許可する必要がある ・基本クラス側でオーバーライドを許可するにはメソッドに「virtual」をつけて仮想メソッドにする ・派生クラス側でオーバーライドするメソッドには「override」をつけてオーバーライドメソッドにする p.231 override01.cs //p.231 override01.cs using System; class MyBase { //基本クラス public virtual void Method() { //オーバーライドを許可する仮想メソッド Console.WriteLine("MyBase"); } } class Derived1 : MyBase { //MyBaseクラスを継承する派生クラス① //ここに public virtual void Method(){…}があるとみなされるがオーバーライドされる public override void Method() { //オーバーライドメソッド① Console.WriteLine("Derived1"); } } class Derived2 : MyBase { //MyBaseクラスを継承する派生クラス② //ここに public virtual void Method(){…}があるとみなされるがオーバーライドされる public override void Method() { //オーバーライドメソッド② Console.WriteLine("Derived2"); } } class override01 { public static void Main() { Derived1 d1 = new Derived1(); //派生クラス①のインスタンス生成 Derived2 d2 = new Derived2(); //派生クラス②のインスタンス生成 d1.Method(); //オーバーライドメソッド①が優先される d2.Method(); //オーバーライドメソッド②が優先される } } p.232 多態性 ・クラスの継承は基本クラスを代表とする派生クラスのグルーピングにもなる ・例: Slimeクラスを継承するHoimiSlimeクラス、MetalSlimeクラスは「スライムの一族」 ・よって、派生クラスのオブジェクトは、基本クラスを型とする変数に代入できる ・例: HoimiSlimeクラスのホイミンは、Slimeクラスのホイミンとしても扱える(メンバを引き継いでいるので) ・このとき、メソッドにおいて名前の隠蔽が起こっていると、基本クラスを型とする変数でメソッドを呼ぶと、基本クラスのメソッドが実行される ・しかし、メソッドにおいてオーバーライドが起こっていると、基本クラスを型とする変数でメソッドを呼んでも、派生クラスのオーバーライドメソッドが実行される ・このことを多態性(ポリモフィズム)という ・例: Slimeクラスの仮想メソッドはHPのみ表示、継承するHoimiSlimeクラスのオーバライドメソッドはHPとMPを表示する場合:  HoimiSlimeクラスのホイミンは、Slimeクラスのホイミンとして扱っても、メソッドを呼ぶとオーバライドメソッドが動作してHPとMPを表示 ・この「どのメソッドが実行されるかが実行時に決まる仕組み」を動的メソッドディスパッチャという ・例:  class Slime { int hp; public virtual void show() { Console.Write(hp); }  class HoimiSlime : Slime { int mp; public override void show() { Console.Write(hp + mp);}  :  HoimiSlime hoimin = new HoimiSlime(); Slime slime1 = hoimin; //ホイミンを生成しスライム1号として扱う  slime1.show(); //スライム1号はSlimeだが、多態性により中身がホイミンと分かりオーバライドメソッドが呼ばれる p.233 override02.cs //p.233 override02.cs using System; class Mammal { //哺乳類クラス protected const int LegNo = 4; //哺乳類の足の数は4 protected string Koe; //哺乳類には声がある public virtual string Nakigoe() { //鳴き声を返す仮想メソッド Koe = "..."; //ここでメソッドの内容は抽象的 return Koe; } public int Leg() { //足の数を返す通常メソッド return LegNo; } } class Cat : Mammal { //哺乳類クラスを継承する猫クラス public override string Nakigoe() { //鳴き声を返すオーバライドメソッド Koe = "ニャー、ニャー"; //ここでメソッドの内容を具体化している return Koe; } } class Dog : Mammal { //哺乳類クラスを継承する犬クラス public override string Nakigoe() { //鳴き声を返すオーバライドメソッド Koe = "ワン、ワン"; //ここでメソッドの内容を具体化している return Koe; } } class override02 { public static void Main() { Mammal m; //哺乳類クラス型の参照変数を宣言 Cat cat = new Cat(); //猫クラスのオブジェクトを生成 Dog dog = new Dog(); //犬クラスのオブジェクトを生成 m = cat; //猫クラスのオブジェクトを哺乳類クラス型の変数に代入 Console.WriteLine("猫の脚は{0}本で鳴き声は「{1}」です", m.Leg(), m.Nakigoe()); //Nakigoeは猫のオーバーライドメソッドになる m = dog; //犬クラスのオブジェクトを哺乳類クラス型の変数に代入 Console.WriteLine("犬の脚は{0}本で鳴き声は「{1}」です", m.Leg(), m.Nakigoe()); //Nakigoeは犬のオーバーライドメソッドになる } } p.235 プロパティのオーバーロード ・シグニチャ(プロパティ名)が同じプロパティを派生クラスに定義してオーバーライドすることが可能 ・多態性(ポリモフィズム)が発生する p.236 override03.cs //p.236 override03.cs using System; class Mammal { //哺乳類クラス protected const int LegNo = 4; //哺乳類の足の数は4 public virtual string Nakigoe { //鳴き声を返す仮想プロパティ get { return "..."; //ここでプロパティの内容は抽象的 } } public int Leg() { //足の数を返す通常メソッド return LegNo; } } class Cat : Mammal { //哺乳類クラスを継承する猫クラス public override string Nakigoe { //鳴き声を返すオーバーライドプロパティ get { return "ニャー、ニャー"; //ここでプロパティの内容を具体化している } } } class Dog : Mammal { //哺乳類クラスを継承する犬クラス public override string Nakigoe { //鳴き声を返すオーバーライドプロパティ get { return "ワン、ワン"; //ここでプロパティの内容を具体化している } } } class override03 { public static void Main() { Mammal m; //哺乳類クラス型の参照変数を宣言 Cat cat = new Cat(); Dog dog = new Dog(); m = cat; Console.WriteLine("猫の脚の数は{0}本で、鳴き声は「{1}」です", m.Leg(), m.Nakigoe); //Nakigoeは猫のオーバーライドプロパティになる m = dog; Console.WriteLine("犬の脚の数は{0}本で、鳴き声は「{1}」です", m.Leg(), m.Nakigoe); //Nakigoeは猫のオーバーライドプロパティになる } } p.237 インデクサのオーバーロード ・シグニチャ(引数の型、数、並び)が同じインデクサを派生クラスに定義してオーバーライドすることが可能 ・多態性(ポリモフィズム)が発生する p.237 override04.cs //p.237 override04.cs using System; class Mammal { //哺乳類クラス protected const int LegNo = 4; //哺乳類の足の数は4 protected string Tail, Gei, Food, Koe; //哺乳類の特徴を示す4つの文字列(尾・芸・食・声) public virtual string this[string index] { //仮想インデクサ get { return "..."; //ここで内容は抽象的 } } public int Leg() { //足の数を返す通常メソッド return LegNo; } } class Cat : Mammal //哺乳類クラスを継承する猫クラス { public override string this[string index] { //オーバーライドインデクサ get { switch (index) { //文字列型のインデックスに応じて分岐 case "尾": Tail = "1本"; return Tail; case "芸": Gei = "できない"; return Gei; case "鳴き声": Koe = "ニャー、ニャー"; return Koe; case "食べ物": Food = "キャットフード"; return Food; default: return ""; } } } } class Dog : Mammal { public override string this[string index] { //オーバーライドインデクサ get { switch (index) { //文字列型のインデックスに応じて分岐 case "尾": Tail = "1本"; return Tail; case "芸": Gei = "できる"; return Gei; case "鳴き声": Koe = "ワン、ワン"; return Koe; case "食べ物": Food = "ドッグフード"; return Food; default: return ""; } } } } class override04 { public static void Main() { Mammal m; //哺乳類クラス型の参照変数を宣言 Cat cat = new Cat(); //猫クラスのオブジェクトを生成 Dog dog = new Dog(); //犬クラスのオブジェクトを生成 m = cat; //猫クラスのオブジェクトを哺乳類クラス型の変数に代入 Console.WriteLine("猫の脚は{0}本です。尾は{1}です。芸は{2}。食べ物は{3}。", //インデクサで得る m.Leg(), m["尾"], m["芸"], m["食べ物"]); m = dog; //犬クラスのオブジェクトを哺乳類クラス型の変数に代入 Console.WriteLine("犬の脚は{0}本です。尾は{1}です。芸は{2}。食べ物は{3}。", //インデクサで得る m.Leg(), m["尾"], m["芸"], m["食べ物"]); } } p.240 クラスの多層階層 ・派生クラスを基本クラスとして、さらに派生クラスを定義できる ・基本クラスのメンバは派生の派生クラスに継承される ・派生の派生クラスのオブジェクトも基本クラスの変数で扱える ・派生クラスで独自に記述したメソッドも、仮想メソッドにすることで、派生の派生クラスでオーバーライドできる ・派生クラスのオーバーライドメソッドを、そのまま、派生の派生クラスでオーバーライドできる p.240 inheritance05 //p.240 inheritance05 using System; class MyBase { //基本クラス protected int x = 10; //継承可能で非公開のデータメンバ public virtual void show() { //仮想メソッド Console.WriteLine("x = {0}", x); } } class Derived1 : MyBase { //派生クラス //ここに protected int x = 10; があるとみなされる protected int y = 20; //継承可能で非公開のデータメンバ //ここに public virtual void show() {…} があるとみなされる } class Derived2 : Derived1 { //派生の派生クラス //ここに protected int x = 10; があるとみなされる //ここに protected int y = 20; があるとみなされる int z = 30; //ここに public virtual void show() {…} があるとみなされオーバーライドされる public override void show() { //オーバーライドメソッド Console.WriteLine("z = {0}", z); } } class inheritance05 { public static void Main() { MyBase mb; //基本クラス型の変数 Derived1 d1 = new Derived1(); //派生クラスのオブジェクトを生成 Derived2 d2 = new Derived2(); //派生の派生クラスのオブジェクトを生成 mb = d1; //派生クラスのオブジェクトを基本クラス型の変数へ mb.show(); //派生クラスが継承したメソッドを実行 mb = d2; //派生の派生クラスのオブジェクトを基本クラス型の変数へ mb.show(); //派生の派生クラスのオーバーライドメソッドを実行 } } 次回予告:p.243「クラスの継承とコンストラクタ」から