講義メモ 第10章 インターフェイス

p.255 インターフェイス

・元の意味は「接合部/面」で「異なる要素を関連付ける仕掛け」
・C#では、継承関係にないクラスであっても、関連付けられる仕組み
・例えば、空中シューティングゲームで、飛んでいるドラゴンとF16戦闘機を「飛べるもの」インターフェイスでまとめて扱うことができる
・インターフェイスは文法では抽象クラスの拡張形式で「抽象メンバのみをもった参照型」となる
・書式: interface インターフェイス名 { 抽象メンバ … }
・インターフェイスに記述できる抽象メンバとしては、抽象メソッド、抽象プロパティ、抽象インデクサがある
 ※ (Javaとは異なり)データメンバは記述できない
・例: interface Flyable { string HowToFly(); } //「飛べるもの」インターフェイスに飛び方を返すメソッドがある
・なお、インターフェイスの中に記述した抽象メンバは「abstract」は不要
・インターフェイスの中の抽象メソッドの書式: 戻り値型 メソッド名(引数型 引数,…);
・インターフェイスの中の抽象プロパティの書式: データ型 プロパティ名 { get; set; }
・インターフェイスの中の抽象インデクサの書式: データ型 this[インデックス型 インデックス名 { get; set; }

p.256 インターフェイスの実装

・クラスの継承と同じ形式で、インターフェイスの実装が可能
・なお、クラスの継承とことなり、インターフェイスの実装は複数行って良い
・書式: class クラス名 : インターフェイス名,… {…}
・例: class F16 : Flyable {…} //F16戦闘機は「飛べるもの」
・例: class Dragon : Flyable, Swimable {…} //ドラゴンは「飛べるもの」で「泳げるもの」
・インターフェイスを実装したクラスでは、インターフェイスにある抽象メンバをオーバーライドする必要がある
・しかし、これは継承におけるオーバーライドとは区別され、override指定は不要で、通常、publicにする
・例: class F16 : Flyable { public string HowToFly() { return "エンジンで";} } //F16戦闘機は「飛べるもの」

作成例

//p.257 interface01.cs
using System;
interface IMyInterface { //インターフェイスの定義
    void show(string str); //抽象メソッド
    int xprop { get; set; } //抽象プロパティ
    int this[int i] { get; set; } //抽象インデクサ
}
class MyClass : IMyInterface { //インターフェイスを実装するクラスの定義
    int i; //データメンバ
    int[] arr = new int[10]; //データメンバである配列
    public void show(string str) { //抽象メソッドのオーバーライド
        Console.WriteLine(str);
    }
    public int xprop { //抽象プロパティのオーバーライド
        get { return i; } set { i = value; }
    }
    public int this[int index] { //抽象インデクサのオーバーライド
        get { return arr[index]; } set { arr[index] = value; }
    }       
}
class interface01 {
    public static void Main() {
        MyClass mc = new MyClass(); //インターフェイスを実装するクラスのインスタンス生成
        mc.show("Test Interface"); //抽象メソッドのオーバーライドを呼ぶ
        mc.xprop = 100; //抽象プロパティのオーバーライド(set)を呼ぶ
        Console.WriteLine("mc.xprop = {0}", mc.xprop); //抽象プロパティのオーバーライド(get)を呼ぶ
        for (int i = 0; i < 10; i++) {
            mc[i] = i * 2; //抽象インデクサのオーバーライド(set)を呼ぶ
        }
        for (int i = 0; i < 10; i++) {
            Console.WriteLine("mc[{0}] = {1}", i, mc[i]); //抽象インデクサのオーバーライド(get)を呼ぶ
        }        
    }
}

p.259 1つのインターフェイスを複数のクラスで実装する

・継承関係にないクラスであっても、同じインターフェイスを実装していれば、関連付けられる

p.259 interface02.cs

//p.259 interface02.cs
using System;
interface IMyInterface { //インターフェイスの定義
    void show(string str); //抽象メソッド
}
class MyClass : IMyInterface { //インターフェイスを実装するクラス①
    public void show(string s) { //抽象メソッドのオーバーライド①
        Console.WriteLine(s);
    }
}
class YourClass : IMyInterface { //インターフェイスを実装するクラス②
    public void show(string x) { //抽象メソッドのオーバーライド②
        Console.WriteLine("{0}が入力されました", x);
    }
}
class interface02 {
    public static void Main() {
        MyClass mc = new MyClass(); //インターフェイスを実装するクラス①のインスタンス生成
        YourClass yc = new YourClass(); //〃②のインスタンス生成
        mc.show("abc"); //抽象メソッドのオーバーライド①を呼ぶ
        yc.show("abc"); //抽象メソッドのオーバーライド②を呼ぶ
    }
}

p.260 1つのインターフェイスを複数のクラスで実装する つづき

・クラスを型とする変数が定義できるように、インターフェイスを型とする変数が定義できる
・ただし、インターフェイスは抽象なのでインスタンスを生成できない
・インターフェイスを実装したクラスのオブジェクトを、インターフェイスを型とする変数で扱える
・例:
 Dragon Veldra = new Dragon(); //ドラゴンのヴェルドラを生成
 Flyable obj1 = Veldra; //ヴェルドラを飛行物体1とする
 F16 Masotan = new F16(); //F16戦闘機のマソタンを生成
 Flyable obj2 = Masotan; //マソタンを飛行物体2とする

p.261 interface03.cs

//p.261 interface03.cs
using System;
interface IMyInterface { //インターフェイスの定義
    int calc(int x, int y); //抽象メソッド「xとyで計算した結果を返す」
}
class Plus : IMyInterface { //インターフェイスを実装したクラス①
    public int calc(int a, int b) { //抽象メソッドをオーバーライドして①
        return a + b; //「aとbで計算した結果を返す」→「aとbの和を返す」とした
    }
}
class Minus : IMyInterface { //インターフェイスを実装したクラス②
    public int calc(int a, int b) { //抽象メソッドをオーバーライドして②
        return a - b; //「aとbで計算した結果を返す」→「a引くbを返す」とした
    }
}
class interface03 {
    public static void Main() {
        IMyInterface im; //インターフェイス型の変数
        Plus p = new Plus(); //インターフェイスを実装したクラス①のオブジェクト①
        Minus m = new Minus(); //インターフェイスを実装したクラス②のオブジェクト②
        im = p; //オブジェクト①をインターフェイス型の変数に代入
        Console.WriteLine("im.calc = {0}", im.calc(3, 5)); //インターフェイス型の変数経由でメソッド①を
        im = m; //オブジェクト②をインターフェイス型の変数に代入
        Console.WriteLine("im.calc = {0}", im.calc(3, 5)); //インターフェイス型の変数経由でメソッド②を
    }
}

アレンジ演習:p.261 interface03.cs

・インターフェイス型の配列の要素にできることを確認しよう

作成例

//アレンジ演習:p.261 interface03.cs
using System;
interface IMyInterface { //インターフェイスの定義
    int calc(int x, int y); //抽象メソッド「xとyで計算した結果を返す」
}
class Plus : IMyInterface { //インターフェイスを実装したクラス①
    public int calc(int a, int b) { //抽象メソッドをオーバーライドして①
        return a + b; //「aとbで計算した結果を返す」→「aとbの和を返す」とした
    }
}
class Minus : IMyInterface { //インターフェイスを実装したクラス②
    public int calc(int a, int b) { //抽象メソッドをオーバーライドして②
        return a - b; //「aとbで計算した結果を返す」→「a引くbを返す」とした
    }
}
class interface03 {
    public static void Main() {
        IMyInterface[] im = new IMyInterface[2]; //インターフェイス型の配列
        Plus p = new Plus(); //インターフェイスを実装したクラス①のオブジェクト①
        Minus m = new Minus(); //インターフェイスを実装したクラス②のオブジェクト②
        im[0] = p; //オブジェクト①をインターフェイス型の配列[0]に代入
        im[1] = m; //オブジェクト②をインターフェイス型の配列[1]に代入
        foreach (var i in im) {
            Console.WriteLine("im.calc = {0}", i.calc(3, 5)); //インターフェイス型の変数経由でメソッド①②を
        }
    }
}

p.263 複数のインターフェイスを実装する

・クラスの継承と異なり、インターフェイスの実装は複数行って良い

正誤:p.263 interface04.cs 010行目

・「: IFirst」は無用(インターフェイスの継承(p.266)になってしまう)

p.263 interface04.cs

//p.263 interface04.cs
using System;
interface IFirst { //インターフェイス①の定義
    void show(int x); //抽象メソッド①
}
interface ISecond { //インターフェイス②の定義
    void show(int x, int y); //抽象メソッド②
}
class MyClass : IFirst, ISecond { //インターフェイス①②を実装するクラス
    public void show(int x) { //抽象メソッド①のオーバーライド
        Console.WriteLine("x = {0}", x);
    }
    public void show(int x, int y) { //抽象メソッド②のオーバーライドで、オーバーロードでもある
        Console.WriteLine("x = {0}, y = {1}", x, y);
    }
}
class interface04 {
    public static void Main() {
        MyClass mc = new MyClass();
        mc.show(2); //抽象メソッド①のオーバーライドを呼ぶ
        mc.show(1, 3); //抽象メソッド②のオーバーライドを呼ぶ
    }
}

p.264 複数のインターフェイスを実装する・つづき

・複数のインターフェイスを実装する場合に、双方に同じシグニチャのメソッドがあるとエラーにはならないが、
 オーバーライドにおいて区別する必要がある
・定義のメソッド名の前に「インターフェイス名.」を付記すれば良い
・書式: 戻り値型 インターフェイス名.メソッド名(引数型 引数名,…) {…}
・なお、この場合「public」は不要

正誤:p.264 interface05.cs 005行目,010行目

・どちらも先頭の「public」は無用

p.264 interface05.cs

//p.264 interface05.cs
using System;
public interface IMas { //インターフェース①「ます」
    void show(int i); //抽象メソッド①
}
public interface IDa { //インターフェース②「だ」
    void show(int i); //抽象メソッド②(※シグニチャが①と同じ)
}
class MyClass : IMas, IDa { //インターフェイス①②を実装するクラス
    void IMas.show(int i) { //抽象メソッド①のオーバーライド
        Console.WriteLine("iは{0}です", i);
    }
    void IDa.show(int i) { //抽象メソッド②のオーバーライド
        Console.WriteLine("iは{0}だ", i);
    }
}
class interface05 {
    public static void Main() {
        IMas im; //インターフェース①「ます」を型とする変数①
        IDa id;  //インターフェース②「だ」を型とする変数②
        MyClass mc = new MyClass();
        im = mc; //変数①に代入すると
        im.show(5); //インターフェース①のオーバーライドメソッドが呼ばれ「ます」になる
        id = mc; //変数②に代入すると
        id.show(5); //インターフェース②のオーバーライドメソッドが呼ばれ「だ」になる
    }
}

p.266 インターフェイスの継承

・クラスと同様にインターフェースの継承が可能で、基本インターフェース、派生インターフェースとなる
・書式: interface 派生インターフェイス名 : 基本インターフェイス名 {…}

p.266 interface06.cs

//p.266 interface06.cs
using System;
interface IInterface1 { //基本インターフェース
    void setdatano(int n); //抽象メソッド①
    void setdata(double data, int i); //抽象メソッド②
    double calcsum(); //抽象メソッド③
}
interface IInterface2 : IInterface1 { //派生インターフェース
    double calcaverage(); //抽象メソッド④
}
class MyClass : IInterface2 { //派生インターフェースを実装するクラス
    double[] data;
    bool bOK = false;
    public void setdatano(int n) { //抽象メソッド①のオーバーライド
        data = new double[n];
        bOK = true;
    }
    public void setdata(double d, int i) { //抽象メソッド②のオーバーライド
        if (!bOK) {
            Console.WriteLine("配列の準備ができていません");
            return;
        }
        data[i] = d;
    }
    public double calcsum() { //抽象メソッド③のオーバーライド
        if (!bOK) {
            Console.WriteLine("配列の準備ができていません");
            return -1.0;
        }
        double sum = 0.0;
        for (int i = 0; i < data.Length; i++)
            sum += data[i];
        return sum;
    }
    public double calcaverage() { //抽象メソッド④のオーバーライド
        double sum = calcsum();
        return sum / data.Length;
    }
}
class interface06 {
    public static void Main() {
        MyClass mc = new MyClass(); //派生インターフェースを実装するクラスのインスタンスを生成
        int nNo;
        while (true) { //無限ループ
            Console.Write("データ数---");
            string strno = Console.ReadLine();
            nNo = Int32.Parse(strno);
            mc.setdatano(nNo);
            for (int i = 0; i < nNo; i++) {
                Console.Write("data[{0}] = ", i);
                string strdata = Console.ReadLine();
                mc.setdata(double.Parse(strdata), i);
            }
            Console.WriteLine("合計 = {0}", mc.calcsum());
            Console.WriteLine("平均 = {0}", mc.calcaverage());
            Console.WriteLine();
            Console.Write("続けますか(Y/N)---");
            string yn = Console.ReadLine();
            if (yn == "N" || yn == "n")
                break;
        }
    }
}

p.266 インターフェイスの継承・つづき

・基本インタフェースの抽象メソッドと同じシグニチャの抽象メソッドが派生インタフェースにある場合、名前の隠蔽が起こる
・よって、newキーワードにより名前の隠蔽であることを明示すれば良い

p.270 interface07.cs

//p.270 interface07.cs
using System;
interface IMyInterface { //基本インターフェース
    void show1(); //抽象メソッド①
    void show2(); //抽象メソッド②
}
interface IMyInterface2 : IMyInterface { //派生インターフェース
    new void show1(); //抽象メソッド③=①と同じシグニチャなので名前の隠蔽になる
    void show3(); //抽象メソッド④
}
class MyClass : IMyInterface2 { //派生インターフェースを実装するクラス
    public void show1() { //抽象メソッド③のオーバーライド
        Console.WriteLine("show1");
    }
    public void show2() { //抽象メソッド②のオーバーライド
        Console.WriteLine("show2");
    }
    public void show3() { //抽象メソッド④のオーバーライド
        Console.WriteLine("show3");
    }
}
class interface07 {
    public static void Main() {
        MyClass mc = new MyClass();
        mc.show1(); //抽象メソッド③のオーバーライドを呼ぶ
        mc.show2(); //抽象メソッド②のオーバーライドを呼ぶ
        mc.show3(); //抽象メソッド④のオーバーライドを呼ぶ
    }
}

p.266 インターフェイスの継承・つづき

・基本インタフェースの抽象メソッドと同じシグニチャの抽象メソッドが派生インタフェースにある場合、名前の隠蔽が起こる
・この場合、「インターファイス名.」をつける(明示的実装という)ことで双方をオーバーライドすることもできる

p.272 interface08.cs

//p.272 interface08.cs
using System;
interface I1 { //基本インターフェース
    void show1(); //抽象メソッド①
    void show2(); //抽象メソッド②
}
interface I2 : I1 { //派生インターフェース
    new void show1(); //抽象メソッド③=①と同じシグニチャなので名前の隠蔽になる
}
class MyClass : I2 { //派生インターフェースを実装するクラス
    void I1.show1() { //抽象メソッド①のオーバーライド
        Console.WriteLine("I1.show1");
    }
    void I2.show1() { //抽象メソッド③のオーバーライド
        Console.WriteLine("I2.show1");
    }
    public void show2() { //抽象メソッド②のオーバーライド
        Console.WriteLine("show2");
    }
}
class interface08 {
    public static void Main() {
        MyClass mc = new MyClass();
        I1 i1; //基本インターフェースを型とする変数①
        I2 i2; //派生インターフェースを型とする変数②
        mc.show2(); //抽象メソッド②のオーバーライドを呼ぶ
        i1 = mc; //変数①に代入すると
        i1.show1(); //インターフェース①のオーバーライドメソッドが呼ばれる
        i2 = mc; //変数②に代入すると
        i2.show1(); //抽象メソッド③のオーバーライドメソッドが呼ばれる
        i2.show2(); //抽象メソッド②のオーバーライドメソッドが呼ばれる
    }
}

講義メモ

・p.243「クラスの継承とコンストラクタ」から

p.243 クラスの継承とコンストラクタ

・どのクラスにおいても、コンストラクタを記述しないと、自動的に引数のないコンストラクタが用意される
・これは派生クラスでも同様
・しかし、派生クラスのコンストラクタの前には自動的に基本クラスのコンストラクタが実行される
・これにより、基本クラスから継承したデータメンバの初期化を基本クラスのコンストラクタに任させることができる
・基本クラスに引数を持つコンストラクタがあり、派生クラスにも引数を持つコンストラクタがある場合、引数を受け渡す必要があるので、baseキーワードを用いる
・書式: public 派生クラス名(型 引数,…) : base (受け取りたい引数,…) {…}

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() { //コンストラクタ(引数なし)
        //ここでMyBaseのコンストラクタを実行
        Console.WriteLine("ここはDerived1");
        x = 20; //データメンバの初期化
    }
}
class Derived2 : Derived1 { //派生の派生クラス
    //ここに「protected int x;」があるとみなされる
    public Derived2() { //コンストラクタ(引数なし)
        //ここでDerived1のコンストラクタを実行(中で)
        Console.WriteLine("ここはDerived2");
        x = 30; //データメンバの初期化
    }
    public void show() { //独自のメソッド
        Console.WriteLine("x = {0}", x);
    }
}
class inheritance06 {
    public static void Main() {
        Derived2 d2 = new Derived2(); //派生の派生クラスのコンストラクタを実行(3つのコンストラクタが動作)
        d2.show(); //x = 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 { //派生クラス
    public bool bJudge;
    public MyJudge(double p, double q, double r) //コンストラクタ(引数あり) 
        : base(p, q, r) { //MyBaseクラスのコンストラクタに引数を渡す(p→a,q→b,r→c)
        //ここに「protected double d;」があるとみなされる
        //ここでMyBaseのコンストラクタ(p, q, r)を実行
        //つまり、ここに d = Math.Pow(q, 2.0) - 4.0 * p * r; があるとみなされる
        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、派生クラスはSlime、Dragon、その派生クラスはHoimiSlime など
・そして、基本クラスには具体的な記述をせず、派生クラスに任せる方が効率的な構造になる
・この時に用いる「中身は派生クラスで」というメソッドが抽象メソッド
・書式: abstruct 戻り値型 メソッド名(引数型 引数,…); //抽象メソッドは{}がなく;で閉じる
・抽象メソッドを持つクラスを抽象クラスといい、クラス名の前にもabstructを付記する
・書式: abstruct class クラス名 {…}
・抽象クラスはインスタンスを生成できない
・基本クラスが抽象クラスの場合、派生クラスでは全ての抽象メソッドをオーバーライドする必要がある
・あるいは、一部または全部の抽象メソッドをオーバーライドしないことで、派生クラスを抽象クラスにできる
・例: 基本クラスはMonster(抽象)、派生クラスはSkyMonster(抽象)、その派生クラスはDragonなど

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クラスにすれば良い
・書式: sealed class クラス名 {…}
・なお、C#が提供するクラスの中にも、sealedクラスが含まれている

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

・1クラスを複数のソースリストで記述できる
・これにより、ソースの長大化を防いだり、チーム開発を効率化できる
・テキストではクラス定義を2分割して1ソースに置いているが、通常、ソースの分割に用いる
・クラスを分割定義するには、classの前に partial を付記する(これを部分クラスともいう)
・部分クラスのどこかで定義したメンバは、他のソースにある部分クラスからも利用可能

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

・018行目までをpartial01.csに、020行目以降をpartial01a.csに分離しよう
・なお、「using System;」は両方またはどちらか片方で必要に応じて配置すること

作成例 partial01.cs

//アレンジ演習:p.250 partial01.cs
using System;
partial class MyClass { //部分クラス
    public int x;
}
class partial01 { //通常のクラス
    public static void Main() {
        MyClass mc = new MyClass(); //部分クラスを利用可能
        mc.x = 10; //部分クラスにあるデータメンバ
        mc.Show(); //部分クラスの続きにあるメソッド
    }
}

作成例 partial01a.cs

//アレンジ演習:p.250 partial01a.cs
using System;
partial class MyClass { //部分クラスの続き
    public void Show() { //メソッドはこちらへ
        Console.WriteLine("x = {0}", x); //他の部分クラスにあるデータメンバを利用可
    }
}

p.251 メソッドを分割定義する

・分割したクラスにおいて、メソッドの定義とメソッドの内容を別々の部分クラスに記述できる
・これを部分メソッドという
・ただし、戻り値型がvoidで、privateなメソッドに限る
※ この仕掛は未完成のメソッドがある場合に便利で、 シグニチャだけを部分メソッドとして記述しておくと良い
※ 1メソッドを複数ソースファイルで記述できるわけではない

p.251 partialmethod01.cs

//p.251 partialmethod01.cs
using System;
partial class Partialmethod01 { //部分クラス①
  public static void Main() {
    Console.WriteLine("Mainメソッドです");
    Partialmethod01 pm = new Partialmethod01();
    pm.Func1(); //部分メソッド①を呼べる
    pm.Func2("テスト"); //部分メソッド②を呼べる
  }
  //ここではメソッドの中身を記述していない
  //戻り値はvoid型、privateでなくてはだめ
  partial void Func1(); //部分メソッド①の宣言のみ
  partial void Func2(string s); //部分メソッド②の宣言のみ
}
partial class Partialmethod01 { //部分クラス②
  partial void Func1(){ //部分メソッド①の内容を記述
    Console.WriteLine("パーシャルメソッド1です");
  }
}
partial class Partialmethod01 { //部分クラス③
  partial void Func2(string s) { //部分メソッド②の内容を記述
    Console.WriteLine("引数は「{0}」です", s);
  }
}

p.254 練習問題1,2:ヒント

・p.231 override01.csをベースにすると良い
・Mainメソッドの作例:
  A2 a2 = new A2(); //派生クラスのインスタンスを生成
  Console.WriteLine(a2.Calc(5, 8)); //差を表示?
  A1 a1 = a2; //基本クラス型の変数にインスタンスを代入
  Console.WriteLine(a1.Calc(5, 8)); //和を表示? 差を表示?

今週の話題

ゲームソフト販売本数ランキング:今週1位も「モンスターハンターワイルズ(PS5)」GO!
【緊急告知!】Game*SparkとAUTOMATONでイベント!阿佐ヶ谷ロフトAでゲームメディアの現状語り合うリアルイベント開催(オンライン配信もあるよ) GO!
『Pokémon GO』『ピクミンブルーム』『モンスターハンターNow』サウジアラビア系企業へ売却―継続してサービスは提供 GO!

当初3万人以上集うも…2ヶ月後には30分の1―新作F2Pシューターたった半年でサービス終了へ。開発スタジオもまもなく閉鎖 GO!
『GTA V』のテイクツーが「改造アカウント」販売中国企業を提訴―“リアルな裏社会”で1億ドル以上の利益か GO!

前回のコメント

・今回もありがとうございました。
・今回もありがとうございました。
 次でとうとう最終回ですね。少々寂しいですが頑張ります。
 次回もよろしくお願いします。

 基礎コースラストまでしっかり進めましょう。
 こちらこそ、よろしくお願いいたします。

・本日もありがとうございました。
 お送り致します。ITパスポート問題集1週したのみですが、
 1夜漬けになりますができる限り、頑張ります!

 資格は武器ですので、取得や使い方のコツがわかると強いですね。
 合格を祈念しています。

講義メモ 第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.cs

//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.228 inheritance04.cs

講義メモ

・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); 
        }
    }
}

今週の話題

ゲームソフト販売本数ランキング:今週1位は「モンスターハンターワイルズ(PS5)」GO!
『モンハンワイルズ』3月10日以降に配信予定の修正パッチが公開。特定の手順で一部アイテムや報酬をくり返し入手できる問題、捕獲ネットを放っても魚が退場しない不具合などが修正 GO!
ポータブルゲーミングPC“AYANEO 3”が6月中旬発売! 7インチモニター搭載で携帯ゲーム機のようにSteamのゲームが快適に遊べる GO!
ガンホーは3期連続で最終利益が2桁減益、アクティビストの台頭で今期の株主総会は波乱含みに?【ゲーム企業の決算を読む】 GO!

闇バイトなど詐欺被害を学ぶ「投資詐欺体験チャット」公開 GO!
中国国内向けの日本アニメ海賊版サイト&アプリ運営者が著作権法違反の疑いで逮捕 河北省で初の刑事摘発 GO!
リマスター版『The Last of Us』シリーズなどに携わったVisual Artsでレイオフ実施―PlayStation Studios Malaysiaのチームも対象とスタッフ報告も GO!
2021年からの海賊版を巡る長い戦いは任天堂完全勝利―任天堂、海賊版サイトとの訴訟にフランス最高裁判所で勝利と海外メディア報じる GO!
任天堂が偽造amiibo業者への訴訟で約10億円以上を勝ち取る!将来の偽造行為に対する抑止力として期待が高まる GO!

前回のコメント

・本日もありがとうございました!
・ありがとうございました。
・今回もありがとうございました。
 駆け足で追いつくのが大変でしたが頑張ります。
 次回もよろしくお願いします。

 残り少なくなってきましたね。
 こちらこそ、よろしくお願いいたします。

・本日の授業は下記内容で合ってますでしょうか。
 インデックスは添え字。箱の番号
 プロパティはチーム運営でルール付けや制御ができる。
 インデクサは配列の型を関係なく、添え字として使用して、引っ張ってくることができるという事なんですね。

 合っている部分とそうではない部分があります。
 インデックスは複数のデータを持つ構造から1要素を指す方法で、
 配列の場合は添字(0以上の整数)です。インデクサの場合は型を問いませんので、インデックスと呼んでいます。
 プロパティはデータメンバを保護する仕掛けで、クラス側でルールを設定することで、
 チーム開発で起こりやすいミスを防ぐ効果があります。
 また、不都合なデータが格納されないので、データのチェックを省きシンプルにすることができます。
 インデクサはオブジェクト名にインデックスを指定することで、
 クラス内部で用意されたデータ構造から1要素を指すことができます。
 要素をどう返すかはクラス内部に定義できますので、インデックスの型が自由なのもメリットです。