前回のコメント

・名前の隠ぺいがありますがどういった場面でよく使われますでしょうか?

 オーバロードは多態性が実現できて便利ですが、仮想メソッド/プロパティでなければ行えません
 (あるいは次回学習する抽象メソッド/プロパティでも可能ですが、抽象クラスになります)。
 よって、自前のクラスであれば、仮想メソッド/プロパティに設定してオーバロードします。
 しかし、C#システムなどの他者が提供するクラスや、プロジェクト共有クラスでは、変更できない場合があります。
 この場合は、名前の隠ぺいを行います。
 なお、多態性を活用しないことを明示するために、オーバロードではなく、名前の隠ぺいを行う場合もあります。

・ゲームの最適化問題において、ゲーム製作エンジンのみに原因があるとは限らないとのことですが、他に予想される要因は何でしょうか。

 この場合の「ゲームの最適化」は、ハイスペックなハードウェアでも、非力なハードウェアでも、
 ゲームが同様に動作し、プレイ感を損なわないようにすることです。
 UEなどのゲーム製作エンジンには、様々なデバイス向けの自動化された最適化機能を提供する、サポート機能があります。
 しかし、この機能は完全ではなく、開発者による設計・開発に問題があると、不完全な結果になります。
 システムに依存しすぎる設計や、ハイスペックなハードウェアを前提とする開発などが原因です。
 ゲーム自体が格段に複雑さを増しており、ゲーム製作エンジンだけで最適化を図るのが非常に難しいのが、現状です。

講義メモ 後半

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 hp; //基本クラスのインスタンス変数メンバ」があるとみなされる
    int mp;
    // ここに「public virtual void show() {…} //基本クラスの仮想メソッド」があるがオーバーライドされる
    public override void show() { //オーバーライドメソッド
        Console.Write(hp + ", " + mp);
    }
}
HoimiSlime hoimin = new HoimiSlime();  //ホイミンをホイミスライムとして生成
Slime slime1 = hoimin; //ホイミンをスライムクラスのスライム1号として扱う
slime1.show(); //スライム1号はSlimeだが、多態性により中身がホイミンと分かりオーバライドメソッドが呼ばれる

アレンジ演習:p.233 override02.cs

・名前の隠ぺいでは(オーバーライドは異なり)多態性が起こらないことを確認しよう
・Dogクラスに名前を隠ぺいするLeg()メソッドを追加し「return 0;」としてみよう

作成例

//アレンジ演習:p.233 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 { //派生クラス「猫」
    public override string Nakigoe() { //オーバライドメソッド
        Koe = "ニャー、ニャー"; //鳴き声が確定
        return Koe;
    }
}
class Dog : Mammal { //派生クラス「犬」
    public override string Nakigoe() { //オーバライドメソッド
        Koe = "ワン、ワン"; //鳴き声が確定
        return Koe;
    }
    new public int Leg() { //【以下追加】足の数を返す通常メソッドの名前の隠ぺい
        return 0; //仮に0を返す ※多態性により用いられない
    }
}
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()); //鳴き声は多態性により「犬」になるが、足の数は変わらず4のまま
    }
}

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

・シグニチャ(プロパティ名)が同じプロパティを派生クラスに定義してオーバーライドすることが可能
・多態性(ポリモフィズム)が発生する

アレンジ演習:p.236 override03.cs

・プロパティにおいても、名前の隠ぺいでは(オーバーライドは異なり)多態性が起こらないことを確認しよう
・MammalクラスのLeg()メソッドをプロパティ(getのみ)に変更する。
・Dogクラスに名前を隠ぺいするLeg()プロパティ(getのみ)を追加し「return 0;」としてみよう

作成例

//アレンジ演習:p.236 override03.cs
using System;
class Mammal { //基本クラス「哺乳類」
    protected const int LegNo = 4; //継承可能なインスタンス変数(定数)
    public virtual string Nakigoe { //オーバーライド可能な仮想プロパティ
        get {
            return "...";
        }
    }
    public int Leg { //【変更】足の数を返す通常プロパティ
        get { //【追加】
            return LegNo; //定数値を返す
        }
    }
}
class Cat : Mammal { //派生クラス「猫」
    public override string Nakigoe { //オーバーライドプロパティ
        get {
            return "ニャー、ニャー";
        }
    }
}
class Dog : Mammal { //派生クラス「犬」
    public override string Nakigoe { //オーバーライドプロパティ
        get {
            return "ワン、ワン";
        }
    }
    new public int Leg { //【以下追加】足の数を返すプロパティの名前の隠ぺい
        get {
            return 0; //定数値を返す ※多態性により用いられない
        }
    }
}
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); //【変更】鳴き声は多態性により「犬」になる、足の数は変わらず4のまま
        //【以下参考までに追加】
        Console.WriteLine("dog.Leg = {0}", dog.Leg); //名前の隠ぺいをするプロパティが呼ばれる
    }
}

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

・シグニチャ(引数の型、数、並び)が同じインデクサを派生クラスに定義してオーバーライドすることが可能
・多態性(ポリモフィズム)が発生する

アレンジ演習:p.237 override04.cs

・インデクサにおいても、名前の隠ぺいでは(オーバーライドは異なり)多態性が起こらないことを確認しよう
・MammalクラスのLeg()メソッドをthis[int]インデクサに変更する。
・Dogクラスに名前を隠ぺいするthis[int]インデクサを追加し「return 0;」としてみよう
・なお、このプログラムのswitch構文では、必須なはずのbreakがreturnがあれば不要(記述不可)なことが分かる

作成例

//アレンジ演習: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] { //仮想インデクサ[string]
        get {
            return "...";
        }
    }
    public int this[int index] { //【変更】脚の数を返す通常インデクサ[int]
        get { //【追加】
            return LegNo; //定数値を返す ※多態性により用いられない
        }
    }
}
class Cat : Mammal { //派生クラス「猫」
    public override string this[string index] { //オーバライドインデクサ[string]
        get {
            switch (index) {
                case "尾":
                    Tail = "1本";
                    return Tail; //retrunがあればbreakは不要
                case "芸":
                    Gei = "できない";
                    return Gei; //retrunがあればbreakは不要
                case "鳴き声":
                    Koe = "ニャー、ニャー";
                    return Koe; //retrunがあればbreakは不要
                case "食べ物":
                    Food = "キャットフード";
                    return Food; //retrunがあればbreakは不要
                default:
                    return ""; //retrunがあればbreakは不要
            }
        }
    }
}
class Dog : Mammal{ //派生クラス「犬」
    public override string this[string index] { //オーバライドインデクサ[string]
        get {
            switch (index) {
                case "尾":
                    Tail = "1本";
                    return Tail;
                case "芸":
                    Gei = "できる";
                    return Gei;
                case "鳴き声":
                    Koe = "ワン、ワン";
                    return Koe;
                case "食べ物":
                    Food = "ドッグフード";
                    return Food;
                default:
                    return "";
            }
        }
    }
    new public int this[int index] { //【以下追加】脚の数を返す通常インデクサ[int]の名前の隠ぺい
        get {
            return 0;
        }
    }
}
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[0], m["尾"], m["芸"], m["食べ物"]); //【変更】脚以外は多態性により「猫」になる
        m = dog; //「犬」のインスタンスを「哺乳類」型の変数で扱う
        Console.WriteLine("犬の脚は{0}本です。尾は{1}です。芸は{2}。食べ物は{3}。",
            m[0], m["尾"], m["芸"], m["食べ物"]); //【変更】脚以外は多態性により「犬」になる
        //【以下参考までに追加】
        Console.WriteLine("dog.Leg = {0}", dog[0]); //名前の隠ぺいをするインデクサが呼ばれる
    }
}

p.240 クラスの多層階層

・派生クラスを基本クラスとして、さらに派生クラスを定義できる
・基本クラスのメンバは派生の派生クラスに継承される
・派生の派生クラスのオブジェクトも基本クラスの変数で扱える
・派生クラスで独自に記述したメソッドも、仮想メソッドにすることで、派生の派生クラスでオーバーライドできる
・派生クラスのオーバーライドメソッドを、そのまま、派生の派生クラスでオーバーライドできる
・つまり、オーバーライドメソッドの対象は上位のクラスの仮想メソッドまたはオーバーライドメソッド

アレンジ演習:p.240 inheritance05.cs

・派生クラスのオーバーライドメソッドを、そのまま、派生の派生クラスでオーバーライドできることを確認しよう
・Derived2クラスを継承するDerived3クラスを定義して、Show()メソッドをオーバーライドし、Main()で呼び出てみよう

提出:アレンジ演習:p.240 inheritance05.cs

講義メモ

・p.223「第9章 継承」から

提出フォロー p.222 練習問題2 ヒント ex0802.cs

・p.217 indexer03.csをベースにすると良い
・コンストラクタで生徒数を入力する部分は、p.214 indexer02.csが参考になる
・生徒名を格納するstring型配列と、点数を格納するint型配列を宣言して用いると楽
 ※ 生徒名と点数をメンバとするクラスオブジェクトの配列にできればさらに良い
・要素の生成はコンストラクタで行うと良い
・Mainメソッドで格納結果を表示すること

作成例

//p.222 練習問題2 ex0802.cs
using System;
class MyIndexer {
    int[] point; //点数の配列の宣言
    string[] name; //名前の配列の宣言
    int num; //要素数(メソッドで設定)
    public int this[string Name] { //文字列をインデックスとするインデクサ①
        get {
            for (int i = 0; i < num; i++) { //配列の全要素について
                if (Name == name[i]) { //名前と一致?
                    return point[i]; //点数を返す
                }
            }
            return -1; //一つも一致しなかったら-1を返す(0点だとあり得るので)
        }
        set {
            for (int i = 0; i < num; i++) { //配列の全要素について
                if (Name == name[i]) { //名前と一致?
                    point[i] = value; //点数を格納
                }
            }
        }
    }
    public string this[int i] { //添字をインデックスとするインデクサ②
        get {
            return name[i]; //名前[i]を返す
        }
        set {
            name[i] = value; //名前[i]に格納
        }
    }
    public MyIndexer(int i) { //要素数を受け取るコンストラクタ
        point = new int[i]; //引数で受け取った要素数で点数の配列本体を生成
        name = new string[i]; //引数で受け取った要素数で名前の配列本体を生成
        num = i; //引数で受け取った要素数をデータメンバに格納
    }
}
class indexer03 {
    public static void Main() {
        const int N = 3; //要素数
        MyIndexer mi = new MyIndexer(N); //要素数Nで生成
        mi[0] = "マチュ"; //インデクサ②のsetを呼ぶ
        mi[1] = "ニャアン"; //インデクサ②のsetを呼ぶ
        mi[2] = "ハロ"; //インデクサ②のsetを呼ぶ
        mi["マチュ"] = 60; //インデクサ①のsetを呼ぶ
        mi["ニャアン"] = 70; //インデクサ①のsetを呼ぶ
        mi["ハロ"] = 80; //インデクサ①のsetを呼ぶ
        for (int i = 0; i < N; i++) { //全要素について繰返す
            string name = mi[i]; //インデクサ②のgetを呼ぶ
            int point = mi[name]; //インデクサ①のgetを呼ぶ
            Console.WriteLine("{0} : {1}点", name, point); //インデクサ①②の組合せで名前と点数を得る
        }
    }
}

アレンジ演習: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 int b = 20; //インスタンス変数
    public void DerivedMethod() { //インスタンスメソッド
        Console.WriteLine("ここは派生クラスです");
    }
    // public void BaseMethod() { //インスタンスメソッドが継承される
    //    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); //基本クラスのデータ
    }
}

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 { //派生クラス
    //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」を記述することで明示できる(明示が推奨される)

アレンジ演習:inheritance03.cs

・Mainメソッドで基本クラスのオブジェクトを生成し、名前の隠蔽がされているメンバが利用可能なことを確認しよう

作成例

//アレンジ演習:p.228 inheritance03.cs
using System;
class Base { //基本クラス
    public int x = 10; //継承により隠ぺいされるインスタンス変数
    public void BaseMethod()    { //継承により隠ぺいされるインスタンスメソッド
        Console.WriteLine("Baseクラスです");
    }
}
class Derived : Base
{
    // ここに「public int x = 10;」が継承されるが、次の定義で名前の隠ぺいになる
    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(); //同上のインスタンスメソッド
        //【以下追加】
        Base b = new Base(); //基本クラス
        Console.WriteLine("x = {0}", b.x); //名前を隠ぺいされている側のインスタンス変数
        b.BaseMethod(); //同上のインスタンスメソッド   
    }
}

p.229 名前の隠蔽:baseキーワード

・「base.」を記述することで、隠蔽されたメンバを、派生クラスの中で利用することができる

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

・「base.」を記述することで、隠蔽されたメンバを、派生クラスの中で利用することができるが、
 隠蔽されたデータメンバの値を、派生クラスの中で書き換えるとどうなるか確認しよう

作成例

//アレンジ演習: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() { //派生クラスの自前のメソッド
        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.213 override01.cs

・Mainメソッドで基本クラスのオブジェクトを生成し、オーバーライドされているvirtualメソッドが利用可能かどうか確認しよう

作成例

//アレンジ演習:p.231 override01.cs
using System;
class MyBase { //基本クラス
    public virtual void Method() { //オーバーライドを許可する仮想メソッド
        Console.WriteLine("MyBase");
    }
}
class Derived1 : MyBase { //派生クラス①
    //ここに「public virtual void Method(){…}」があるが、下記にオーバーライドされる
    public override void Method() { //オーバーライドメソッド①
        Console.WriteLine("Derived1");
    }
}
class Derived2 : 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(); //オーバーライドメソッド②を呼ぶ
        //【以下追加】
        MyBase m1 = new MyBase(); //基本クラス
        m1.Method(); //Virtualメソッドを呼ぶ
    }
}

今週の話題

ゲームソフト販売本数ランキング:今週1位も「マリオカート ワールド(Switch2)」 GO!
東京都、生成AI活用「モバイルアプリコンテスト」中高生対象 GO!
Epic GamesのCEO語る「UE5最適化問題」の原因と改善策。ハイスペック基準の開発工程が落とし穴と分析 GO!

Windows 11最新アプデでSSDが壊れる不具合「Windows側の問題」とPhison調査結果公表も社員再現できず GO!
ユービーアイが『ザ・ローグ:プリンス オブ ペルシャ』を“インディー規模”と宣伝して物議醸す―「開発はインディー」vs「大企業の資金が入っている」GO!

前回のコメント

・プロパティ作成においてgetとsetは必ず一対をなすのでしょうか。

 とは限りません。どちらかのみでもOKです。
 よくあるパターンとしては、データメンバを外部からの書き換え不可(Read Only)にするために、
 データメンバをprivateにして、setを省略したプロパティを提供することが多用されています。
 例えば、モンスターのHPの初期値をコンストラクタで与え、増減のみをメソッドで行う場合です。

・プロパティとインデクサのアクセッサであるgetとsetは両方が必ず必要と言うわけではないとのことですが、setのみを使う場合もあるということでしょうか?

 その通りです。getが不要な場合は記述不要です。
 よくあるパターンとしては、データメンバに外部から変数値を与えることができるが、
 クラス内部で値を書き換えることは無いので、外部の処理ではその変数値を使用することがあります。
 例えば、各モンスターの名前を配列の要素値から代入し、表示には要素値を用いる場合です。

次回予告と今回の講義メモ

次回予告:p.223「第9章 継承」から
講義メモ:memo20250824.txt

p.222 練習問題2 ヒント:インデクサの定義部分

class MyIndexer {
    int[] point; //点数の配列の宣言
    string[] name; //名前の配列の宣言
    int num; //要素数(メソッドで設定)
    public int this[string Name] { //文字列をインデックスとするインデクサ①
        get {
            for (int i = 0; i < num; i++) { //配列の全要素について
                if (Name == name[i]) { //名前と一致?
                    return point[i]; //点数を返す
                }
            }
            return -1; //一つも一致しなかったら-1を返す(0点だとあり得るので)
        }
        set {
            for (int i = 0; i < num; i++) { //配列の全要素について
                if (Name == name[i]) { //名前と一致?
                    point[i] = value; //点数を格納
                }
            }
        }
    }
    public string this[int i] { //添字をインデックスとするインデクサ②
        get {
            return name[i]; //名前[i]を返す
        }
        set {
            name[i] = value; //名前[i]に格納
        }
    }
    public MyIndexer(int i) { //要素数を受け取るコンストラクタ
        point = new int[i]; //引数で受け取った要素数で点数の配列本体を生成
        name = new string[i]; //引数で受け取った要素数で名前の配列本体を生成
        num = i; //引数で受け取った要素数をデータメンバに格納
    }
}

講義メモ 後半

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

・整数ではないインデックスを受け取ってインデクサの中で配列の要素にアクセスする処理を記述することができる
・よって、文字列をインデックスとするインデクサや、実数をインデックスとするインデクサも可能になる

アレンジ演習: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 {
            for (int i = 0; i < 12; i++) { //配列の全要素について
                if (MonthName == mon[i]) { //月名と一致?
                    return i + 1; //添字+1を返す
                }
            }
            return 0; //一つも一致しなかったら0を返す
        }
    }
    public string this[double n] { //【以下追加】実数をインデックスとするインデクサ
        get {
            if (n >= 1.0 && n < 13.0) { //1以上13未満であれば
                return mon[(int)n - 1]; //整数化して添字にして月名を返す
            }
            return "";
        }
    }
}
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
        Console.WriteLine("mi[1.01] = {0}", mi[1.01]); //【追加】"Jan"
        Console.WriteLine("mi[12.9] = {0}", mi[12.9]); //【追加】"Dec"
        Console.WriteLine("mi[33.3] = {0}", mi[33.3]); //【追加】""
    }
}

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

・2つ以上のインデクスを持つインデクサを記述できる
・2次元の書式: データ型 this[インデックス型① インデックス①, インデックス型② インデックス②] { get {…} set {…} }

アレンジ演習:p.219 indexer04.cs

・インデクサのgetにインデックスの範囲チェックを加えよう
・インデックスが不適切な時は""を返す

作成例

//p.219 indexer04.cs
using System;
class MyClass {
    string[,] name; //クラス内部でのデータ保持用の2次元配列(参照のみ)
    public string this[int i, int j] { //2次元インデクサ
        get {
            if (i >= 0 && i < 2 && j >= 0 && j < 5) { //【追加】添字が範囲内?
                return name[i, j]; //名前を返す
            }
            return ""; //【追加】範囲外なら空文字列を返す
        }
    }
    public MyClass() { //コンストラクタ
        name = new string[,]{{"田中", "佐藤", "吉田", "加藤", "粂井"},  //1組1~5番
                             {"工藤", "竹中", "斉藤", "太田", "杉本"}}; //2組1~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]);
            }
        }
        Console.WriteLine("{0}組{1}番--{2}", 3, 3, mc[3, 3]); //【追加】
    }
}

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

・インデクサのシグニチャはインデックスのデータ型と数と並びであり、シグニチャの異なるインデクサを複数記述できる
・これをインデクサのオーバーロードという

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

・インデクサのgetにインデックスの範囲チェックを加えよう
・インデックスが不適切な時は0を返す
・データはintで、インデックスはdoubleであるインデクサを追記しよう
・インデクスの値をintにキャストして、this[int]を呼ぶようにしよう

作成例

//アレンジ演習:p.220 indexer05.cs
using System;
class MyOverLoad {
    int[] a = new int[3] { 1, 2, 3 }; //インデクサ①で扱う配列
    int[,] b = new int[2, 2] { { 100, 200 }, { 300, 400 } }; //インデクサ②で扱う配列
    public int this[int i] { //インデクサ①
        get {
            if (i >= 0 && i < a.Length) { //【追加】添字として適切かチェック
                return a[i]; //インデクサ①で扱う配列の要素値を返す
            }
            return 0;
        }
    }
    public int this[int i, int j] { //インデクサ②
        get {
            if (i >= 0 && i < 2 && j >= 0 && j < 2) { //【追加】添字として適切かチェック
                return b[i, j]; //インデクサ②で扱う配列の要素値を返す
            }
            return 0;
        }
    }
    public int this[double i] { //【以下追加】インデクサ③:インデックスが実数
        get {
            return this[(int)i]; //インデックスを整数化することで、インデクサ①を呼ぶ
        }
    }
}
class indexer05 {
    public static void Main() {
        MyOverLoad mo = new MyOverLoad();
        for (int i = 0; i < 3; i++) {
            Console.WriteLine("mo[{0}] = {1}", i, mo[i]); //インデクサ①を呼ぶ
        }
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                Console.WriteLine("mo[{0}, {1}] = {2}", i, j, mo[i, j]); //インデクサ②を呼ぶ
            }
        }
        //【以下追加】
        Console.WriteLine("mo[9] = {0}", mo[9]); //インデクサ①を呼ぶ(範囲外なので0)
        Console.WriteLine("mo[9,8] = {0}", mo[9, 8]); //インデクサ②を呼ぶ(範囲外なので0)
        Console.WriteLine("mo[1.8] = {0}", mo[1.8]); //インデクサ③を呼ぶ
        Console.WriteLine("mo[9.8] = {0}", mo[9.8]); //インデクサ③を呼ぶ(範囲外なので0)
    }
}

p.222 練習問題1 ヒント

・p.209 prop02.csを単純化すると良い
・double型のデータメンバ1つのみになる
・Mainメソッドで負の数や0を与えて動作を確認すること

作成例

//p.222 練習問題1 ex0801.cs
using System;
class BMI {
    double bw; //体重(publicがないのでprivate扱い)
    public double bwprop { //体重のプロパティ
        get { //プロパティを利用すると実行される内容(無制限)
            return bw;
        }
        set { //プロパティに代入すると実行される内容(制限有り)
            if (value <= 0) {
                Console.WriteLine("0または負の値は設定できません");
                return; //プロパティの途中で呼ばれた相手に戻る
            }
            bw = value; //代入した値がvalueキーワード経由で渡される
        }
    }
}
class prop02 {
    public static void Main() {
        BMI mybmi = new BMI();
        mybmi.bwprop = 73.2; //プロパティのsetを用いて代入
        Console.WriteLine("bw = {0}", mybmi.bwprop); //プロパティのget実行
        mybmi.bwprop = 0.0; //プロパティのsetを用いて代入(無視される)
        Console.WriteLine("bw = {0}", mybmi.bwprop); //プロパティのget実行(73.2のまま)
        mybmi.bwprop = -1.1; //プロパティのsetを用いて代入(無視される)
        Console.WriteLine("bw = {0}", mybmi.bwprop); //プロパティのget実行(73.2のまま)
    }
}

p.222 練習問題2 ヒント ex0802.cs

・p.217 indexer03.csをベースにすると良い
・コンストラクタで生徒数を入力する部分は、p.214 indexer02.csが参考になる
・生徒名を格納するstring型配列と、点数を格納するint型配列を宣言して用いると楽
 ※ 生徒名と点数をメンバとするクラスオブジェクトの配列にできればさらに良い
・要素の生成はコンストラクタで行うと良い
・Mainメソッドで格納結果を表示すること

提出:p.222 練習問題2 ex0802.cs(未完成でもOK)

講義メモ

・p.207「プロパティ」から

提出フォロー:アレンジ演習:p.205 static03.cs

・静的メソッドSetCatTail()の中の「// name = "マイケル";」をコメントアウトしないと、どういうエラーになるか確認しよう
・静的メソッドSetCatTail()の中で、インスタンスメソッドShowCat()を呼ぶとエラーになることを確認しよう
・静的メソッドSetCatTail()の中で、thisを用いるとエラーになることを確認しよう
・以上は、確認後、コメントアウトすること

作成例

//アレンジ演習:p.205 static03.cs
using System;
class Cat {
    static int NoOfTail; //静的データメンバ(猫の尾の数は猫に所属)
    string Name; //インスタンス変数(名前はインスタンスに所属)
    public void SetName(string strName) { //インスタンスメソッド
        Name = strName; //インスタンス変数を用いるので
    }
    public void ShowCat() { //インスタンスメソッド
        if (Name == null) { //インスタンス変数を用いるので
            Console.WriteLine("名前が設定されていません");
            return;
        }
        Console.WriteLine("猫の名前は{0}で尾の数は{1}本です",
            Name, NoOfTail); //静的データメンバを用いることも可能
    }
    public static void setCatTail(int no) { //静的メソッド
        //ここでインスタンスフィールドにアクセス不可
        // Name = "マイケル"; //インスタンス変数にはアクセスできない
        NoOfTail = no; //静的データメンバにはアクセスできる
        // ShowCat(); //インスタンスメソッドは呼び出せない
        // this.Name = ""; //thisは使えない
    }
}
class static03 {
    public static void Main() {
        Cat.setCatTail(1); //静的メソッドを呼ぶ
        Cat mycat = new Cat();
        Cat yourcat = new Cat();
        mycat.ShowCat(); //インスタンスメソッドを呼ぶ
        mycat.SetName("マイケル"); //同
        yourcat.SetName("パトリシア"); //同
        mycat.ShowCat(); //同
        yourcat.ShowCat(); //同
    }
}

p.207プロパティ

・メソッドに近い記述により、外部からは変数のように扱える仕掛がプロパティ。
 ※ データベースなどでは「属性」のことをプロパティというが、C#のプロパティは意味が異なる
・変数などを扱うプロパティを定義して、外部に利用を許可することで、外部からはプロパティがデータ
 そのものに見える
・プロパティの中でデータの利用を制限したり、更新を不可にしたりできるので、データの安全性を高める
 ことが可能
 例: 身長を表す変数に負の数を代入できるが、身長を扱うプロパティの中で負の数の代入を却下できる
・プロパティで扱う変数をprivate指定にし、プロパティをpublicにすると良い
・プロパティは1つのデータを受け渡すので、そのデータ型がプロパティの型になる
・プロパティにはgetとsetの2つのアクセッサ(アクセス記述)があり、どちらかは省略可能
・外部で、プロパティを利用するとgetの内容が、プロパティに代入するとsetの内容が実行される
・getの基本構文は「get { return 式や値; }」で、returnが必ず実行される必要がある
・setの基本構文は「set { 変数など = value; }」で、代入した値がvalueキーワード経由で渡される
 定義例(負の身長を排除できる): 
 private double height; //身長を直接操作することを禁止
 pubic double high { get { return height; } set { height = (value > 0) ? value : 0; } }

アレンジ演習:p.208 prop01.cs

・各アクセッサにコンソール出力を追記し、自動的に呼び出されていることを確認しよう

作成例

//アレンジ演習:p.208 prop01.cs
using System;
class MyClass {
    double bl; //プロパティで扱う変数(publicがないのでprivate扱い)
    public double blprop {
        get { //プロパティを利用すると実行される内容
            Console.WriteLine("getを実行"); //【追加】
            return bl; //returnが必ず実行される必要がある
        }
        set { //プロパティに代入すると実行される内容
            Console.WriteLine("setを実行"); //【追加】
            bl = value; //代入した値がvalueキーワード経由で渡される
        }
    }
}
class prop01 {
    public static void Main() {
        MyClass mc = new MyClass();
        mc.blprop = 165.2; //プロパティのsetが実行され、valueに165.2が渡される
        Console.WriteLine("bl = {0}", mc.blprop); //プロパティのgetが実行され、インスタンス変数blの値が戻る
    }
}

p.207 プロパティ:setアクセッサにおける不適切な代入の防止

・setアクセッサに条件を記述することで、代入できる値や相手を制限できる
・なお、setアクセッサを省略すると読み込み専用に、getアクセッサを省略すると閲覧不可にできる

アレンジ演習:p.209 prop02.cs

・setアクセッサを省略した試験的なプロパティを追加し、このプロパティを代入に利用するとエラーになることを確認しよう

作成例

//p.209 prop02.cs
using System;
class BMI {
    double bw, bl; //体重、身長(publicがないのでprivate扱い)
    public double blprop { //身長のプロパティ
        get { //プロパティを利用すると実行される内容(無制限)
            return bl;
        }
        set { //プロパティに代入すると実行される内容(制限有り)
            if (value <= 0) {
                Console.WriteLine("身長に0または負の値は設定できません");
                return; //プロパティの途中で呼ばれた相手に戻る
            }
            bl = value; //代入した値がvalueキーワード経由で渡される
        }
    }
    public double bwprop { //体重のプロパティ
        get { //プロパティを利用すると実行される内容(無制限)
            return bw;
        }
        set { //プロパティに代入すると実行される内容(制限有り)
            if (value <= 0) {
                Console.WriteLine("体重に0または負の値は設定できません");
                return; //プロパティの途中で呼ばれた相手に戻る
            }
            bw = value; //代入した値がvalueキーワード経由で渡される
        }
    }
    public double CalcBMI() { //通常のメソッド
        if (bl == 0.0 || bw == 0.0) {
            Console.WriteLine("データがセットされていません");
            return 0.0; //無効でも値を返す必要がある
        }
        return bw / Math.Pow(bl, 2.0); //BMI値を計算して返す
    }
    public string answer { //【以下追加】読み込み専用のプロパティ
        get { return (bl > 0.0) ? "準備完了" : "未設定"; }
    }
}
class prop02 {
    public static void Main() {
        BMI mybmi = new BMI();
        double bl, bw;
        do { //繰返し開始
            Console.Write("身長(m)--- ");
            string strBl = Console.ReadLine();
            bl = double.Parse(strBl); 
            mybmi.blprop = bl; //プロパティのsetを用いて代入
        } while (bl <= 0.0); //プロパティのgetが実行されて身長を得て比較
        do { //繰返し開始
            Console.Write("体重(kg)--- ");
            string strBw = Console.ReadLine();
            bw = double.Parse(strBw);
            mybmi.bwprop = bw; //プロパティのsetを用いて代入
        } while (bw <= 0.0); //プロパティのgetが実行されて体重を得て比較
        Console.WriteLine("bl = {0}, bw = {1}", mybmi.blprop, mybmi.bwprop); //プロパティのget実行
        Console.WriteLine("BMI = {0:#.#}", mybmi.CalcBMI());
        //mybmi.answer = "OK"; //【追加・削除】setがないプロパティなので代入はエラー
        Console.WriteLine(mybmi.answer); //【追加】プロパティのgetが実行されて得た文字列を表示
    }
}

p.211 プロパティ:静的プロパティ

・静的メソッドと同様に、staticを前置することで、静的プロパティを記述できる
・利用方法は他の静的メンバと同様で「クラス名.プロパティ名」となる
・静的データメンバを扱うプロパティは静的プロパティにすると良い

アレンジ演習:p.211 prop03.cs

・Testクラスは静的クラスにできることを確認しよう

作成例

//アレンジ演習:p.211 prop03.cs
using System;
static class Test { //【変更】静的クラスにできる
    static int x; //静的データメンバ
    public static int myprop { //静的データメンバを扱う静的プロパティ
        get {
            return x;
        }
        set {
            x = value;
        }
    }
}
class prop03 {
    public static void Main() {
        Test.myprop = 10; //インスタンスを生成せずに「クラス名.プロパティ名」で呼び出せる
        Console.WriteLine("Test.myprop = {0}", Test.myprop); //同上
    }
}

p.212 インデクサ

・主に、配列をデータメンバとして持つクラスにおいて「オブジェクト名[添字]」とすることで要素を扱える仕組みがインデクサ
・アクセッサと似た記述が可能なので、データの保護にも役立つ
・定義書式: データ型 this[インデックス型 インデックス] { get {…} set {…} }
・getの基本構文は「get { return 配列名[インデックス]; }」
・setの基本構文は「set { 配列名[インデックス] = value; }」

アレンジ演習:p.213 indexer01.cs

・インデクサが動作していることをコンソール出力を追加して確認しよう
・空文字列「""」が代入されたら、インデクサにおいて要素に代入しないようにしよう

作成例

//アレンジ演習:p.213 indexer01.cs
using System;
class MyClass {
    string[] name = new string[5]; //クラス内部でのデータ保持用の配列
    public string this[int i] { //この配列を用いるインデクサ
        get { //インデクサを利用すると実行される内容(無制限)
            Console.WriteLine("インデクサ(get)実行 i = {0}, name = {1}", i, name[i]); //【追加】
            return name[i];
        }
        set { //インデクサに代入すると実行される内容
            Console.WriteLine("インデクサ(set)実行 i = {0}, value = {1}", i, value); //【追加】
            if (value != "") { //【追加】空文字列でなければ
                name[i] = value; //代入した値がvalueキーワード経由で渡される
            }
        }
    }
}
class indexer01 {
    public static void Main() {
        MyClass mc = new MyClass();
        mc[0] = "一郎"; //インデクサのsetを呼んで配列に格納
        mc[1] = "次郎"; //同上
        mc[2] = "三郎"; //同上
        mc[3] = "四郎"; //同上
        mc[4] = "五郎"; //同上
        mc[4] = ""; //【追加】空文字列なので、インデクサで却下される
        for (int i = 0; i < 5; i++) { 
            Console.WriteLine(mc[i]); //インデクサのgetが実行されて文字列を得て表示
        }
    }
}

p.212 インデクサ:インデクサによるインデックスのチェック

・インデクサのsetおよびgetでインデックスの値をチェックできるので、範囲外の添字を指定したアクセスを
 安全に排除できる

アレンジ演習:p.214 indexer02.cs

・インデックスに負の数が与えられても異常終了しないようにインデクサで対処しよう

作成例

//アレンジ演習:p.214 indexer02.cs
using System;
class MyIndexer {
    int[] array; //クラス内部でのデータ保持用の配列(参照のみ)
    int nMax; //要素数(メソッドで設定)
    public int this[int n] { //インデクサ
        get { //インデクサを利用すると実行される内容
            if (n < nMax && n >= 0) { //【変更】添字が要素数未満、かつ0以上なら
                return array[n]; //要素値を返す
            } else { //でなければ
                return 0; //0を返す
            }
        }
        set { //インデクサに代入すると実行される内容
            if (n < nMax && n >= 0){ //【変更】添字が要素数未満、かつ0以上なら
                array[n] = value; //引数から要素[n]に格納
            }
        }
    }
    public MyIndexer(int i) { //コンストラクタ
        array = new int[i]; //引数で受け取った要素数でデータ保持用の配列本体を生成
        nMax = i; //引数で受け取った要素数をデータメンバに格納
    }
}
class indexer02 {
    public static void Main() {
        MyIndexer mi = new MyIndexer(20); //コンストラクタに20が渡されて、要素数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; //インデクサ経由なのでチェックされて却下
        Console.WriteLine("mi[30] = {0}", mi[30]); //0のまま
        //【以下追加】わざとに添字を負の数にする
        mi[-3] = 30; //インデクサ経由なのでチェックされて却下
        Console.WriteLine("mi[-3] = {0}", mi[-3]); //0のまま
    }
}

今週の話題

ゲームソフト販売本数ランキング:今週1位は再び「マリオカート ワールド(Switch2)」 GO!
【イベント告知】アカマイが「Akamai Cloud Day 2025」を9月24日に開催。特別セッションに安野貴博氏が登壇、ソニーグループや時雨堂の事例も GO!
赤字スタートのセガ、逆襲の鍵は『ソニックレーシング』と待望の『Football Manager』か【ゲーム企業の決算を読む】GO!
業績は減益、株価は高値。スクエニに迫る「物言う株主」の影【ゲーム企業の決算を読む】GO!
グリーの2025年6月期決算は売上6.8%減―新作『まどドラ』投入もゲーム事業はQoQで減収【ゲーム企業の決算を読む】GO!
【キャリアクエスト】「これ、あったら面白い」を実際に作り出せる。セガ入社3年目の若手が語るプランナーという職種、そして自身の展望とは GO!

マイクロソフト「Windows 11高負荷時にSSDを破損する不具合再現できず」と声明。一方Phisonは「Windows側の問題」と調査結果公表 GO!
DDoS・Bot攻撃との“いたちごっこ”に終止符を──AkamaiとCygamesが語る、費用対効果と自動化を両立するセキュリティ戦略【CEDEC2025レポート】GO!