講義メモ 後半

p.243 クラスの継承とコンストラクタ(引数なしの場合)

・どんなクラスであっても、コンストラクタは省略可能
・省略すると引数のない&中身のないコンストラクタが自動的に用意される
・基本クラスのオブジェクトを引数のないnewで生成すると、その引数のないコンストラクタが呼ばれる
・派生クラスのオブジェクトを引数のないnewで生成すると:
 ①基本クラスの引数のないコンストラクタが呼ばれる
 ②派生クラスの引数のないコンストラクタが呼ばれる
・こうすることで、基本クラスで定義したデータメンバの初期化や基本クラス用の準備作業をコンストラクタに書いておくことにより、
 派生クラスのオブジェクト生成時に自動的に実行される
・例:
  class Slime { //基本クラス
  public int hp;
  public Slime { hp = 10; } //コンストラクタ①
 }
  class HoimiSlime : Slime { //派生クラス
  //ここに「public int hp」があるとみなされる
  public int mp;
  public HoimiSlime { mp = 20; } //コンストラクタ②
 }
 class Game {
  public play() {
   HoimiSlime hoimin = new HoimiSlime(); //①②が動作してhpもmpも初期化される
   :

p.243 inheritance06.cs

//p.243 inheritance06.cs
using System;
class MyBase { //基本クラス
    protected int x; //非公開で継承可能なデータメンバ
    public MyBase() { //コンストラクタ①
        Console.WriteLine("ここはMyBase");
        x = 10;
    }
}
class Derived1 : MyBase { //派生クラス
    //ここに「protected int x;」があるとみなされる
    public Derived1() { //コンストラクタ②
        Console.WriteLine("ここはDerived1");
        x = 20;
    }
}
class Derived2 : Derived1 { //派生の派生クラス
    //ここに「protected int x;」があるとみなされる
    public Derived2() { //コンストラクタ③
        Console.WriteLine("ここはDerived2");
        x = 30;
    }
    public void show() {
        Console.WriteLine("x = {0}", x);
    }
}
class inheritance06 {
    public static void Main() {
        Derived2 d2 = new Derived2(); //コンストラクタ①②③の順に動作する
        d2.show(); //③で代入した30が表示
    }
}

p.245 クラスの継承とコンストラクタ(引数ありの場合)

・p.243 inheritance06.csの基本クラスのコンストラクタ①にint型引数iを与えて、xの初期値に用いるとすると、
 派生クラスのコンストラクタ②がエラーになる
・これは派生クラスにおいて基本クラスのコンストラクタが動作できる情報を与える必要があるから
・これを解決するのがbaseキーワードで、派生クラスのコンストラクタ②に基本クラスと同じint型引数iを与えて
 「:base(引数)」を指定すると良い
・そして、派生クラスのコンストラクタ②においてint型引数iをxの初期値に用いても良い
・派生の派生クラスにおいても同様
・呼び出す側では、派生クラスのコンストラクタ②においてint型引数iをxの初期値に用いるかどうかにかかわらず、
 引数を与えること

アレンジ演習:p.243 inheritance06.cs

・上記を試そう

作成例

//アレンジ演習:p.243 inheritance06.cs
using System;
class MyBase { //基本クラス
    protected int x; //非公開で継承可能なデータメンバ
    public MyBase(int i) { //コンストラクタ①
        Console.WriteLine("ここはMyBase");
        x = i;
    }
}
class Derived1 : MyBase { //派生クラス
    //ここに「protected int x;」があるとみなされる
    public Derived1(int i) : base(i) { //コンストラクタ②
        Console.WriteLine("ここはDerived1");
        x = i;
    }
}
class Derived2 : Derived1 { //派生の派生クラス
    //ここに「protected int x;」があるとみなされる
    public Derived2(int i) : base(i){ //コンストラクタ③
        Console.WriteLine("ここはDerived2");
        x = i;
    }
    public void show() {
        Console.WriteLine("x = {0}", x);
    }
}
class inheritance06 {
    public static void Main() {
        Derived2 d2 = new Derived2(30); //コンストラクタ①②③の順に動作する
        d2.show(); //③で代入した30が表示
    }
}

p.245 inheritance07.cs

//p.245 inheritance07.cs
using System;
class MyBase { //基本クラス
    protected double d; //非公開で継承可能なデータメンバ
    public MyBase(double a, double b, double c) { //コンストラクタ①
        d = Math.Pow(b, 2.0) - 4.0 * a * c;
    }
}
class MyJudge : MyBase { //派生クラス
    //ここに「protected double d;」があるとみなされる
    public bool bJudge;
    public MyJudge(double p, double q, double r) : base(p, q, r) { //MyBaseクラスのコンストラクタに引数を渡す
        //ここで基本クラスのコンストラクタMyBase(p, q, r)が実行され、dの値が決まる
        Console.WriteLine("判別式 = {0}", d);
        if (d < 0.0) {
            bJudge = false;
        } else { 
            bJudge = true;
        }
    }
}
class inheritance07 {
    public static void Main() {
        MyJudge mj = new MyJudge(1.0, 2.0, 3.0); //
        Console.WriteLine(mj.bJudge);
        MyJudge mk = new MyJudge(1.0, 4.0, 0.0);
        Console.WriteLine(mk.bJudge);
    }
}

p.247(抽象メソッド)

・基本クラスにおいて派生クラスでオーバーライドして欲しいメソッドを定義できる
・例: Monsterクラスに「表示メソッド」を定義
・こうすると、基本クラスでは具体的な内容を決める必要性がなくなる
・この場合に用いる仕掛けが抽象メソッドで、この定義より、派生クラスでは抽象メソッドのオーバーライドが義務になる
・書式: public/protected abstract 戻り値型 メソッド名(引数リスト); //中身は記述しない
・これにより、派生クラスを利用する側も、基本クラスにおける抽象メソッドがオーバーライドされていることを前提して活用できるのもメリット
・なお、抽象メソッドは静的メソッドにはできない

p.247 抽象クラス

・抽象メソッドを1つでも持つクラスは抽象クラスとなるので、クラス定義の冒頭にabstractを前置すること
・書式: abstract class クラス名 {…}
・例:
 abstract class Monster { //抽象クラス
  protected int hp;
  protected abstract void show(); //抽象メソッド
 }

p.248 abstract01.cs

//p.248 abstract01.cs
using System;
abstract class MyAb { //抽象クラス
    public abstract double Hanbetsu(double a, double b, double c); //抽象メソッド
}
class MyHanbetsu : MyAb { //派生クラス
    public override double Hanbetsu(double a, double b, double c) { //抽象メソッドをオーバーライド
        return Math.Pow(b, 2.0) - 4.0 * a * c;
    }
}
class abstract01 {
    public static void Main() {
        MyHanbetsu h = new MyHanbetsu(); //派生クラスのインスタンスを生成
        double d = h.Hanbetsu(1.0, 2.0, 3.0); //オーバーライドメソッドを呼ぶ
        Console.WriteLine(d);
    }
}

p.249(sealedクラス)

・継承は基本クラスの全メンバを引き継げるので、トラブルの元になる可能性もある
・そこで、継承を防ぎたい場合は、sealedキーワードを前置すれば良い
・C#システムが提供しているクラスの中にもsealedクラスがある
・なお、sealedクラスは抽象クラスにできないので、抽象メソッドは記述できない

p.250 クラスを分割定義する

・partialキーワードをクラス定義に前置することで、クラスをファイルをまたがって記述できる
・これにより、ファイルが小さくなって見通しが良くなると共に、複数人での分担や共有(グループ開発)がしやすくなる
・分割した全てのソースにおいて、partialキーワードをつけたクラス定義とすること
・ビルド時に自動的に結合されるので、異なるソースにあるメンバも利用できる
・Visual Studioの場合、1プロジェクトの中に複数のソースファイルを置けば良い
・p.250 partial01.csのように1ソースファイルの中でクラスを分割定義することも可能だが、あまり意味はない

p.250 partial01.cs

//p.250 partial01.cs
using System;
partial class MyClass { //MyClassクラスの定義①
    public int x;
}
class partial01 { //partial01クラスの定義
    public static void Main() {
        MyClass mc = new MyClass();
        mc.x = 10;
        mc.Show();
    }
}
partial class MyClass { //MyClassクラスの定義②
    public void Show() {
        Console.WriteLine("x = {0}", x);
    }
}

アレンジ演習:p.250 partial01.cs

・MyClassクラスの定義②を、別ファイル(partial02.cs)としてソース分割しよう

提出:アレンジ演習:p.250 partial01.cs

・分割した別ファイル(partial02.cs)を。

※ p.251「メソッドを分割定義する」はメソッドの内容を分割クラスにまたがって記述できるわけではないので、実質的に不要であるため割愛。

講義メモ

・p.229(baseキーワード)から

アレンジ演習:p.228 inheritance03.cs

・基本クラスの両メンバをprotectedにしても、名前の隠ぺいが起こること(同じ結果になること)を確認しよう

作成例

//アレンジ演習:p.228 inheritance03.cs
using System;
class Base { //基本クラス
    protected int x = 10; //非公開だが継承可能なデータメンバ
    protected void BaseMethod() { //非公開だが継承可能なメソッド
        Console.WriteLine("Baseクラスです");
    }
}
class Derived : Base { //派生クラス
    new public int x = 20; //データメンバの名前の隠ぺい
    new public void BaseMethod() { //メソッドの名前の隠ぺい
        Console.WriteLine("Derivedクラスです");
    }
}
class inheritance03 {
    public static void Main() {
        Derived d = new Derived(); //派生クラスのインスタンスを生成
        Console.WriteLine("x = {0}", d.x); //隠ぺいしたデータメンバが用いられる
        d.BaseMethod(); //隠ぺいしたメソッドが実行される
    }
}

p.229(baseキーワード)

・名前の隠ぺいは基本クラスと同じ意味のメンバに同じ名前をつけることで、似た名前のメンバの乱立を防ぐことができる
・とはいえ「上書き」ではなく「隠ぺい」なので、対象となっている基本クラスのメンバを「base.」を前置することで呼び出し可能

p.229 inheritance04.cs

//p.229 inheritance04.cs
using System;
class Base { //基本クラス
    protected int x = 10; //非公開だが継承可能なデータメンバ
}
class Derived : Base { //派生クラス
    //ここに「protected int x = 10;」が継承されているが↓に隠ぺいされる
    new int x = 20; //名前の隠ぺいを行うデータメンバ
    public void Show() {
        Console.WriteLine("base.x = {0}, x = {1} ", base.x, x); //隠ぺいされたxと隠ぺいしたxを表示
    }
}
class inheritance04 {
    public static void Main() {
        Derived d = new Derived();
        d.Show();
    }
}

p.230 メソッドのオーバーライド

・メソッドにおける名前の隠ぺいの上位バージョンであり、後述の多態性を実現する
・よって、メソッドにおいては名前の隠ぺいではなく、オーバーライドを用いることが多い
・名前の隠ぺいとは異なり、基本クラス側の許可が必要であり、オーバーライドを可能とするメソッドは仮想メソッドにすること
・書式: public/protected virtual 戻り値型 メソッド名(引数リスト){内容}
・派生クラスでオーバーライドを行うメソッドはオーバライドメソッドにすること
・書式: アクセス修飾子 override 戻り値型 メソッド名(引数リスト){内容}
・なお、シグニチャとアクセス修飾子は仮想メソッドに合わせること
・静的メソッドは名前の隠ぺいは可能だが、オーバーライドは不可

p.231 override01.cs

//p.231 override01.cs
using System;
class MyBase { //基本クラス
    public virtual void Method() { //オーバーライドを可能とする仮想メソッド
        Console.WriteLine("MyBase");
    }
}
class Derived1 : MyBase { //派生クラス①
    public override void Method() { //オーバーライドメソッド①
        Console.WriteLine("Derived1");
    }
}
class Derived2 : MyBase { //派生クラス②
    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(多態性の前に、継承と参照変数)

・派生クラスのインスタンスは基本クラスの参照変数で扱うことができる
・これは「ホイミスライムのホイミンは、スライムとして扱える」ことと同じ。
例: 
 class Slime {} 
 class HoimiSlime : Slime {} 
 HoimiSlime hoimin = new HoimiSlime(); //ホイミスライムのホイミン
 Slime shoimin = hoimin; //スライムのSホイミンとして扱える
・このことを利用すると、複数の派生クラスのオブジェクトを基本クラスのオブジェクトと一緒にして、基本クラスを型としてまとめて扱える
例: Slime[] slimes = {suralin, hoimin, behoimin}; //suralinはSlime型、
   behoiminはHoimiSlimeを継承したBehoimiSlime型とする

p.232 多態性

・派生クラスのインスタンスを基本クラスの参照変数で扱うと、名前の隠ぺいの場合は、基本クラスのオブジェクトとして扱われる
・よって、派生クラスで独自に定義したメンバは利用できない
・これに対して、基本クラスの参照変数で扱っても、派生クラスの定義を用いるのが多態性
・オーバーライドメソッドにおいては多態性が発生し、派生クラスのインスタンスを基本クラスの参照変数で扱っても、
 オーバーライドメソッドがあれば、それが用いられる
・これにより、派生クラスのインスタンスを基本クラスの参照変数で扱う場合のデメリットを解消できる
・これにより、実際に実行されるメソッドの内容は、実行時に決まることになる(動的メソッドディスパッチという)

p.223 override02.cs

//p.223 override02.cs
using System;
class Mammal { //哺乳類クラス
    protected const int LegNo = 4; //非公開で継承可能な定数「脚の数」
    protected string Koe; //非公開で継承可能な変数「声」
    public virtual string Nakigoe() { //オーバライド可能な仮想メソッド
        Koe = "...";
        return Koe; //声を返す
    }
    public int Leg() { //公開で継承可能な通常メソッド
        return LegNo;
    }
}
class Cat : Mammal { //派生クラス「猫」
    //ここに「protected const int LegNo = 4;」があるとみなす
    //ここに「protected const string Koe;」があるとみなす
    //ここに「public int Leg()」があるとみなす
    public override string Nakigoe() { //仮想メソッドをオーバライド
        Koe = "ニャー、ニャー";
        return Koe; //猫の声を返す
    }
}
class Dog : Mammal { //派生クラス「犬」
    //ここに「protected const int LegNo = 4;」があるとみなす
    //ここに「protected const string Koe;」があるとみなす
    //ここに「public int Leg()」があるとみなす
    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()); //多態性が発揮され猫の声を返す
        m = dog;
        Console.WriteLine("犬の脚は{0}本で鳴き声は「{1}」です",
            m.Leg(), m.Nakigoe()); //多態性が発揮され犬の声を返す
    }
}

p.235 プロパティのオーバーライド

・プロパティ(p.207)はメソッドと同様にオーバライドが可能
・基本クラス側のプロパティは仮想プロパティに、派生クラス側のプロパティはオーバーライドプロパティにする
・書式: public/protected virtual 戻り値型 プロパティ名 {内容}
・書式: アクセス修飾子 override 戻り値型 プロパティ名 {内容}

p.236 override03.cs

//p.236 override03.cs
using System;
class Mammal { //哺乳類クラス
    protected const int LegNo = 4; //非公開で継承可能な定数「脚の数」
    public virtual string Nakigoe { //仮想プロパティ
        get { return "..."; } //getのみ
    }
    public int Leg() { //公開で継承可能な通常メソッド
        return LegNo;
    }
}
class Cat : Mammal { //派生クラス「猫」
    public override string Nakigoe { //オーバライドプロパティ
        get { return "ニャー、ニャー"; } //getのみ
    }
}
class Dog : Mammal { //派生クラス「犬」
    public override string Nakigoe { //オーバライドプロパティ
        get { return "ワン、ワン"; } //getのみ
    }
}
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); //多態性が発揮され猫の声を返す
        m = dog;
        Console.WriteLine("犬の脚の数は{0}本で、鳴き声は「{1}」です",
            m.Leg(), m.Nakigoe); //多態性が発揮され犬の声を返す
    }
}

p.237 インデクサのオーバーライド

・メソッドと同様
・基本クラス側のインデクサは仮想インデクサに、派生クラス側のインデクサはオーバーライドインデクサにする
・書式: public/protected virtual 戻り値型 this[インデックス型 インデクス] {内容}
・書式: アクセス修飾子  override 戻り値型 this[インデックス型 インデクス] {内容}

作成例

//p.237 override04.cs
using System;
class Mammal { //哺乳類クラス
    protected const int LegNo = 4; //非公開で継承可能な定数「脚の数」
    protected string Tail, Gei, Food, Koe;  //非公開で継承可能な変数群
    public virtual string this[string index] { //仮想インデクサ
        get { return "...";  } //getのみ
    }
    public int Leg() { //公開で継承可能な通常メソッド
        return LegNo;
    }
}
class Cat : Mammal { //派生クラス「猫」
    //ここに「protected const int LegNo = 4;」があると見なされる
    //ここに「protected string Tail, Gei, Food, Koe;」があると見なされる
    //ここに「public int Leg()」があると見なされる
    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 { //派生クラス「犬」
    //ここに「protected const int LegNo = 4;」があると見なされる
    //ここに「protected string Tail, Gei, Food, Koe;」があると見なされる
    //ここに「public int Leg()」があると見なされる
    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.cs

//p.240 inheritance05.cs
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 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(); //多態性により②が実行
    }
}

今週の話題

今回トップも「マリオvs.ドンキーコング(Switch)」GO!
『ダンクロ』不調で3期連続赤字のKLab、EAとの協業で逆転狙う【ゲーム企業の決算を読む】GO!
TGS2024でインディーゲームを無料出展―「Selected Indie 80」出展タイトルを募集中【TGS2024】GO!
『ラグナロク』好調も先細りが懸念材料?ガンホーは北米エリア開拓がカギ【ゲーム企業の決算を読む】GO!

Respawn開発の「スター・ウォーズ」FPSも開発中止に…EA、全従業員の約5%を削減へ GO!
Epic Gamesがハッキングされた疑い―犯人グループが約200GBの内部情報をおさえたと主張 GO!

休講でしたが今週の話題

今回トップは「マリオvs.ドンキーコング(Switch)」GO!
Aiming、コロプラとの資本業務提携で16.4億円の資金調達―新規オンラインゲーム共同開発など目指す GO!
「INDIE Live Expo」2024年5月25日に開催決定ー出展エントリーは3月12日まで GO!
「PlayStation VR2(PS VR2)」が2024年内にPCでも利用可能になるとソニーがひっそり発表 GO!

「子供がプロeスポーツチームにスカウトされた」との問い合わせ相次ぐ…チームは“詐欺行為”の可能性があると注意喚起 GO!

次回予告

今回はお二人ともお休みでしたので、次回はp.229(baseキーワード)からとなります。
なお、残り3回で4回分の講義を行いますので、ペースが速くなります。ご了承ください。

前回のコメント

・練習問題が難しかった
・8章の練習問題への理解が足りないので家で復習しようと思います

 難問でしたね。
 次回冒頭に少しフォローしますので、理解を深めてください。

講義メモ 第9章 継承

p.223 クラスの継承の基礎

・クラスは部品化に有効だが、きちんと管理しないと(特にチーム開発では)意味が近いクラスが乱立したり、
 同じ処理内容が複数のクラスに記述されて矛盾を起こしやすい。
・この対策として、クラスに親子関係を作り、親から子に情報が引き継がれるようにするのが継承
・親にあたるクラスを基本クラス、子にあたるクラスを派生クラスという
・よって、派生クラスは基本クラスを継承する
・派生クラスの定義時に基本クラス名を指定するだけで、基本クラスにあるメンバが(ほぼ)すべて継承される
・しかも、基本クラスを書き換えると、継承内容も書き変わるので、修正が自動反映する
・よって、派生クラスでは独自のメンバのみ記述すれば良い
 例:
 ①スライムクラスにHPと戦い方を定義
 ②スライムクラスを継承したホイミスライムクラスにMPと呪文を定義(HPと戦い方は継承されるので記述不要)
・派生クラスを継承する派生の派生クラスも記述でき、基本クラスと派生クラスの両方から継承する
・なお、C#等では2つ以上の基本クラスを持つこと(多重継承)は禁止
・逆に、2つ以上の派生クラスを持つことは可能
・継承をインヘリタンスともいう

p.223(クラスの継承)

・定義書式: class 派生クラス名 : 基本クラス名 {…}
・なお、基本クラスにおいてprivate指定のメンバは継承されない

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.BaseMethod(); //継承したメソッドが派生クラスのインスタンスにある
        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); //基本クラスのデータメンバも基本クラスのインスタンスにある
        //「mb.DerivedMethod();」ならエラーになる
    }
}

p.226 protectedによるデータの保護

・privateなメンバは継承されないが、継承のためだけにpublicにするのは良くない
・そこで、privateと同様に保護しつつ、継承だけは可能とするのがprotected
・そして、継承を禁止したいメンバはprivateにすると良い

p.226 inheritance02.cs

//p.226 inheritance02.cs
using System;
class Base { //基本クラス
    protected int x = 20; //非公開だが継承は可能なデータメンバ
}
class Derived : 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.ShowXY(); //派生クラスのメソッドを実行(内部で基本クラスのデータメンバを利用)
    }
}

p.228 名前の隠ぺい

・派生クラスで基本クラスと同じ名前のメンバを記述できる
・この時に前に「new」を付けることで、名前の隠ぺいとなり「同じ名前にすることで覆い隠す」ことになる
※ 上書きではないので、基本クラスのメソッドを実行する方法も提供されている

p.228 inheritance03.cs

//p.228 inheritance03.cs
using System;
class Base { //基本クラス
    public int x = 10; //継承可能なデータメンバ
    public void BaseMethod() { //継承可能なメソッド
        Console.WriteLine("Baseクラスです");
    }
}
class Derived : Base { //派生クラス
    new public int x = 20; //データメンバの名前の隠ぺい
    new public void BaseMethod() { //メソッドの名前の隠ぺい
        Console.WriteLine("Derivedクラスです");
    }
}
class inheritance03 {
    public static void Main() {
        Derived d = new Derived(); //派生クラスのインスタンスを生成
        Console.WriteLine("x = {0}", d.x); //隠ぺいしたデータメンバが用いられる
        d.BaseMethod(); //隠ぺいしたメソッドが実行される
    }
}

アレンジ演習:p.228 inheritance03.cs

・基本クラスの両メンバをprotectedにしても、名前の隠ぺいが起こること(同じ結果になること)を確認しよう

提出:アレンジ演習:p.228 inheritance03.cs

講義メモ

・p.212「インデクサ」の補足とp.213「index01.cs」から

p.212 インデクサ(再掲載)

・主にデータメンバである配列等をプロパティと同様に扱う仕掛けがインデクサ
・プロパティとは異なり、インデクサには名前がなく「インスタンスの参照変数[添字]」の形式で、配列の要素を扱える
・定義書式: public 型 this[インデックス型 インデックス] { get {…; return 値や式;} set {…;} }
 ※ 型は扱いたい配列等に合わせる
 ※ getの中において配列等の要素[インデックス]の値を返す
 ※ setの中では外部から与えられる値をvalueキーワードで得ることができ、通常、これを要素[インデックス]に代入
・基本的な定義書式: public 型 this[int i] { get {return 配列名[i];} set {配列名[i] = value;} }
・代入式の左辺に参照変数[添字]を指定すると、setの内容が実行される。例:参照変数[0] = 値;
・代入式の左辺以外に参照変数[添字]を指定すると、getの内容が実行される。例:Console.Write(参照変数[0]);

p.213 indexer01.cs

//p.213 indexer01.cs
using System;
class MyClass {
    string[] name = new string[5]; //インデクサで扱いたい配列
    public string this[int i] { //インデクサの定義(インデックスは整数のi)
        get { return name[i]; }
        set { name[i] = value; }
    }
}
class indexer01 {
    public static void Main() {
        MyClass mc = new MyClass();
        mc[0] = "一郎"; //インデクサ経由でname[0]に代入
        mc[1] = "次郎"; //インデクサ経由でname[1]に代入
        mc[2] = "三郎"; //インデクサ経由でname[2]に代入
        mc[3] = "四郎"; //インデクサ経由でname[3]に代入
        mc[4] = "五郎"; //インデクサ経由でname[4]に代入
        for (int i = 0; i < 5; i++) {
            Console.WriteLine(mc[i]); //インデクサ経由でname[0]~[4]を得て表示
        }
    }
}

p.214(インデクサで扱う構造を動的に生成したい場合)

・インデクサで扱いたいデータ構造(例:配列)の実体はクラス内に固定長で配置する必要はない。
・参照のみを定義しておいて、コンストラクタ等で要素数の分だけ生成すれば良い。
・ただし、要素数を保持する仕掛けが必要。

p.214 indexer02.cs

//p.214 indexer02.cs
using System;
class MyIndexer {
    int[] array; //インデクサで扱いたい配列の定義(未生成)
    int nMax; //要素数
    public int this[int n] { //インデクサの定義(インデックスは整数のi)
        get {
            if (n < nMax) { //要素数チェック
                return array[n];
            } else {
                return 0;
            }
        }
        set {
            if (n < nMax) { //要素数チェック
                array[n] = value;
            }
        }
    }
    public MyIndexer(int i) { //コンストラクタ(要素数)
        array = new int[i]; //要素数の分の要素を生成
        nMax = i; //要素数をセット
    }
}
class indexer02 {
    public static void Main() {
        MyIndexer mi = new MyIndexer(20); //インスタンスを生成しコンストラクタ経由で要素数を渡す
        for (int i = 0; i < 20; i++) { //全要素について繰返す
            mi[i] = i * i;
        }
        for (int i = 0; i < 20; i++) { //全要素について繰返す
            Console.WriteLine("{0} * {0} = {1}", i, mi[i]);
        }
        //わざとに配列の範囲を超える
        mi[30] = 30; //配列ならば実行時エラーだが、インデクサのsetに30が渡されるだけ
        Console.WriteLine("mi[30] = {0}", mi[30]);
    }
}

p.217 文字列をインデックスとするインデクサ

・インデクサのインデックスは整数値である必要はない
・インデクサの定義の「public int this[int n] {…}」のインデックスであるnの型はintでなくても良い

p.217 indexer03.cs

//p.217 indexer03.cs
using System;
class MyIndexer {
    string[] mon = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
        "Sep", "Oct", "Nov", "Dec"}; //インデクサで扱う文字列配列
    public int this[string MonthName] { //インデクスが文字列型であるインデクサ(getのみ)
        get {
            for (int i = 0; i < 12; i++) { //文字列配列の全要素について繰り返す
                if (MonthName == mon[i]) { //インデックスと一致したら
                    return i + 1; //添字に+1して月にして返す
                }
            }
            return 0; //当てはまるものがなければ0を返す
        }
    }
}
class indexer03 {
    public static void Main() {
        MyIndexer mi = new MyIndexer();
        Console.WriteLine("Mayは{0}番目の月です", mi["May"]); //5
        Console.WriteLine("Decは{0}番目の月です", mi["Dec"]); //12
        Console.WriteLine("xは{0}番目の月です",   mi["x"]);   //0
    }
}

p.218 多次元のインデクサ

・インデクスを複数持つ多次元のインデクサを定義できる
・多次元配列と同様に利用できる

p.219 indexer04.cs

//p.219 indexer04.cs
using System;
class MyClass {
    string[,] name; //インデクサで扱いたい2次元配列の定義(未生成)
    public string this[int i, int j] { //2次元インデクサ(setは無い)
        get {
            return name[i, j]; //2次元配列の要素を返す
        }
    }
    public MyClass() { //コンストラクタ
        name = new string[,]{{"田中", "佐藤", "吉田", "加藤", "粂井"},
                             {"工藤", "竹中", "斉藤", "太田", "杉本"}}; //2×5の二次元配列を生成
    }
}
class indexer04 {
    public static void Main() {
        MyClass mc = new MyClass();
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 5; j++) {
                Console.WriteLine("{0}組{1}番--{2}", i + 1, j + 1, mc[i, j]); //2次元のインデクサで呼ぶ
            }
        }
    }
}

p.220 インデクサのオーバーロード

・インデクサは名前が無いので、インデックスの数と型がシグニチャとなる
・よって、インデックスの数と型が異なるインデクサが複数定義できる
・これをインデクサのオーバーロードという
・メソッドの場合と同様に、戻り値型はシグニチャに含まれないので注意。

p.220 indexer05.cs

//p.220 indexer05.cs
using System;
class MyOverLoad {
    int[] a = new int[3] { 1, 2, 3 }; //インデクサ①で扱う1次元配列
    int[,] b = new int[2, 2] { { 100, 200 }, { 300, 400 } }; //インデクサ②で扱う2次元配列
    public int this[int i] { //インデクサ①
        get { return a[i]; } //1次元配列の要素[インデックス]を返す
    }
    public int this[int i, int j] { //インデクサ②
        get { return b[i, j]; } //2次元配列の要素[インデックスi,インデックスj]を返す
    }
}
class indexer05 {
    public static void Main() {
        MyOverLoad mo = new MyOverLoad();
        for (int i = 0; i < 3; i++) { //1次元配列の様子数の分、繰り返す
            Console.WriteLine("mo[{0}] = {1}", i, mo[i]);
        }
        for (int i = 0; i < 2; i++) { //2次元配列の外側の様子数の分、繰り返す
            for (int j = 0; j < 2; j++) { //2次元配列の内側の様子数の分、繰り返す
                Console.WriteLine("mo[{0}, {1}] = {2}", i, j, mo[i, j]);
            }
        }
    }
}

アレンジ演習:p.220 indexer05.cs

・インデックスが整数のインデクサと文字列のインデクサが共存できることを確認しよう

作成例

//アレンジ演習:p.220 indexer05.cs
using System;
class MyOverLoad {
    string[] a = new string[3] { "amuro", "shar", "ryu" }; //インデクサ①②で扱う1次元配列
    public string this[int i] { //int型のインデクサ①
        get { return a[i]; } //配列の要素[インデックス]を返す
    }
    public int this[string s] { //string型のインデクサ②
        get { //配列の要素[添字]と一致したら添字を返す
            for (int i = 0; i < a.Length; i++) {
                if(a[i] == s) return i; 
            }
            return -1; //一致するものがなければ-1を返す
        } 
    }
}
class indexer05 {
    public static void Main() {
        MyOverLoad mo = new MyOverLoad();
        Console.WriteLine("mo[1] = {0}", mo[1]); //int型のインデクサ①を用いる
        Console.WriteLine("mo[\"shar\"] = {0}", mo["shar"]); //string型のインデクサ②を用いる
    }
}

p.222 練習問題1 ヒント

・p.209 prop02.csを単純化すると良い
・double型のデータメンバを1個のみ持つクラスとし、2つ目のプロパティやBMI計算は割愛する
・double型のデータメンバに適当な初期値を与えておき、プロパティで負の数や0を与えて、値が変化しないことを確認しよう

作成例

//p.222 練習問題1
using System;
class BMI {
    double bl; //非公開のインスタンス変数:身長
    public double blprop { //身長用のプロパティ
        get { return bl; }
        set {
            if (value <= 0) { //代入値が0以下?
                return; //終わって戻る
            }
            bl = value; //身長に代入値を代入
        }
    }
}
class prop02 {
    public static void Main() {
        BMI mybmi = new BMI();
        mybmi.blprop = 175.2; //プロパティ経由でsetを実行
        Console.WriteLine("bl = {0}" ,mybmi.blprop); //プロパティで身長を得て表示
        mybmi.blprop = -90.3; //プロパティ経由でsetを実行(負の数なので設定されない)
        Console.WriteLine("bl = {0}" ,mybmi.blprop); //プロパティで身長を得て表示
    }
}

p.222 練習問題2 ヒント

・問題文に示されていない仕様は自由に決めて良い。下記は一例。
・生徒数=配列の要素数をコンストラクタ(int)で受け取る
・string型の生徒名の配列を初期化する
・同じ要素数のint型の点数の配列を生成する
・インデクサ[string]を定義する
 ・getでは生徒名の配列を調べて、あればその添字を用いて、点数の配列[添字]の値を返す
 ・なければ(生徒名がnullになったら)、-1を返す
 ・setでは生徒名の配列を調べて、あればその添字を用いて、点数の配列[添字]の値をvalueに変更
 ・なければ(生徒名がnullになったら)、その添字を用いて、生徒名の配列[添字]に名前を、点数の配列[添字]に点数を格納
 ※ なくて生徒名がnullになってなければ満員なので、その旨を表示する

作成例

//p.222 練習問題2
using System;
class Exam {
    string[] name; //インデクサで扱う名前の配列
    int[] point; //インデクサで扱う点数の配列
    int num = 0; //生徒数
    public int this[string s] { //名前をインデクスとして点数を扱うインデクサ
        get { 
            for (int i = 0; i < num; i++) { //全生徒について繰返す
                if(name[i] == s) { //名前が一致?
                    return point[i]; //その点数を返す
                } else if (name[i] == null) { //空?
                    break; //繰返し終了
                }
            }
            return -1; //名前が一致しなければ-1を返す
        }
        set {
            for (int i = 0; i < num; i++) { //全生徒について繰返す
                if (name[i] == null) { //一致せず空きが有る?
                    name[i] = s; //名前を格納
                    point[i] = value; //その点数を格納
                    return; //インデクサを抜ける
                } else if(name[i] == s) { //名前が一致?
                    point[i] = value; //その点数を更新
                    return; //インデクサを抜ける
                }
            }
            Console.WriteLine("満員!");
        }
    }
    public Exam(int num) { //生徒数を受け取るコンストラクタ
        this.num = num;
        name = new string[num]; //名前の配列を生成
        point = new int[num]; //点数の配列を生成
    }
    public void show() {
        for (int i = 0; i < num; i++) {
            if (name[i] != null) { //空でなければ
                Console.Write("{0}:{1}, ", name[i], point[i]);
            }
        }
        Console.WriteLine(); //改行
    }
}
class indexer05 {
    public static void Main() {
        Exam e = new Exam(4);
        e["amuro"] = 60;
        e.show(); //「amuro:60,」
        e["shar"] = 70;
        e.show(); //「amuro:60, shar:70,」 
        e["amuro"] = 80;
        e.show(); //「amuro:80, shar:70,」
        e["bright"] = 90;
        e.show(); //「amuro:80, shar:70, bright:90,」
        e["ryu"] = 50;
        e.show(); //「amuro:80, shar:70, bright:90, ryu:50,」
        e["kai"] = 40; //「満員!」
        e.show(); //「amuro:60, shar:70, bright:90, ryu:50,」
    }
}