講義メモ

テキスト編:p.374「型パラメータの制約」から
ゲーム開発演習:敵弾の発射、移動、衝突判定 他

p.374 型パラメータの制約

・ジェネリッククラスの定義において「どんな型が指定されてもOK」ということは考えづらい
・データメンバと単純なプロパティのみを持つクラスであれば可能だが、なんらかのメソッドを持つ場合、指定できる型を制限する必要が出てくる
・そこで、型パラメータに指定できるクラスを制約として明示できる
・書式: class ジェネリッククラス名<型パラメータ> where 型パラメータ : クラス名 {…}
・こうすると、指定したクラスおよびその派生クラスのみを型パラメータに指定できる
・例: class SlimeHouse<T> where T : Slime {…}
 ⇒ SlimeHouse<Slime> sh1; SlimeHouse<HoimiSlime> sh2; などが可能
・よって、指定したクラスに定義されているメソッドをジェネリッククラス内で利用可能になる
・また、「where T : new()」を指定すると、指定したクラスにデフォルトコンストラクタがあることを制約にできる
・よって、指定したクラスのインスタンスをジェネリッククラス内でnew演算子で生成可能になる
・上記は併用が可能で、クラス名の後ろに「, new()」を指定すると良い

p.375 generic06

//p.375 generic06
using System;
class MyClass {
    public int x;
    public string name;
    public MyClass() { //デフォルトコンストラクタ
        x = 0;
        name = "";
    }
}
class MyClass2<T> where T : MyClass, new() { //クラス名とデフォルトコンストラクタ制約
    T p = new T(); //new()制約があるのでデフォルトコンストラクタの呼出が可能
    public void show() {
        //クラス名制約があるのでMyClassのpublicデータメンバが利用可能
        Console.WriteLine("x = {0}, p.name = {1}", p.x, p.name); 
    }
    public void setxname(int n, string str) {
        p.x = n; //同上
        p.name = str; //同上
    }
}
class generic06 {
    public static void Main() {
        MyClass2<MyClass> mc2 = new MyClass2<MyClass>(); //制約に合致する
        mc2.setxname(100, "abc");
        mc2.show();
    }
}

アレンジ演習:p.375 generic06

・MyClassの派生クラスとしてYourClassを追記してコンストラクタ等を記述する
・MyClass2<YourClass> mc2 = new MyClass2<YourClass>(); とできることを確認しよう

作成例

//アレンジ演習:p.375 generic06
using System;
class MyClass {
    public int x;
    public string name;
    public MyClass() { //デフォルトコンストラクタ
        x = 0;
        name = "";
    }
}
class YourClass : MyClass { //【以下追加】派生クラス
    public YourClass() { //デフォルトコンストラクタ
        x = 10;
        name = "None";
    }
}
class MyClass2<T> where T : MyClass, new() { //クラス名とデフォルトコンストラクタ制約
    T p = new T(); //new()制約があるのでデフォルトコンストラクタの呼出が可能
    public void show() {
        //クラス名制約があるのでMyClassのpublicデータメンバが利用可能
        Console.WriteLine("x = {0}, p.name = {1}", p.x, p.name); 
    }
    public void setxname(int n, string str) {
        p.x = n; //同上
        p.name = str; //同上
    }
}
class generic06 {
    public static void Main() {
        MyClass2<YourClass> mc2 = new MyClass2<YourClass>(); //【変更】制約に合致する
        mc2.show();
        mc2.setxname(100, "abc");
        mc2.show();
    }
}

p.374 型パラメータの制約(続き)

・インターフェイスを制約として明示できる
・書式: class ジェネリッククラス名<型パラメータ> where 型パラメータ : インターフェイス名 {…}
・こうすると、指定したインターフェイスを実装したクラスのみを型パラメータに指定できる
・例: class Flyers<T> where T : Flyable {…}
・よって、指定したインターフェイスに定義されている抽象メソッド等をジェネリッククラス内で利用可能になる
・また、値型のみを可能にしたい場合は「where T : struct」、参照型のみを可能にしたい場合は「where T : class」と指定できる

アレンジ演習:p.375 generic06

・抽象メソッドshow()を持つインタフェースMyIFを追記しよう
・MyClass、YourClassでMyIFを実装しよう
・MyClass2ジェネリッククラスの制約をインタフェースMyIFにして、メソッドshow()が利用可能なことを確認しよう
・mainメソッドの内容を上記に合わせて改変しよう

作成例

//アレンジ演習:p.375 generic06
using System;
interface MyIF { //インタフェース
    void show(); //抽象メソッド
}
class MyClass : MyIF { //インタフェースを実装
    public int x;
    public string name;
    public MyClass() { //デフォルトコンストラクタ
        x = 0;
        name = "";
    }
    public void show() { //抽象メソッドをオーバーライド
        Console.WriteLine("MyClass");
    }
}
class YourClass : MyIF { //インタフェースを実装
    public void show() { //抽象メソッドをオーバーライド
        Console.WriteLine("YourClass");
    }
}
class MyClass2<T> where T : MyIF, new() { //インタフェースとデフォルトコンストラクタ制約
    T p = new T(); //new()制約があるのでデフォルトコンストラクタの呼出が可能
    public void showme() {
        //インタフェース名制約があるのでMyIFの抽象メソッドが利用可能
        p.show();
    }
}
class generic06 {
    public static void Main() {
        MyClass2<MyClass> mc1 = new MyClass2<MyClass>(); //制約に合致する
        mc1.showme();
        MyClass2<YourClass> mc2 = new MyClass2<YourClass>(); //制約に合致する
        mc2.showme();
    }
}

p.377 ジェネリックメソッド

・クラスと同様に、メソッド単体でも型パラメータの指定が可能で、これをジェネリックメソッドという
・書式: アクセス修飾子 戻り値型 メソッド名<型パラメータ,…> (引数リスト) {…}
・ジェネリックメソッドがあるクラスはジェネリッククラスでなくても良い
・ジェネリックメソッドは静的メソッドでもインスタンスメソッドでも良い

p.377 generic07.cs

// p.377 generic07.cs
using System;
class MyClass { //通常クラス
    static object obj;
    object ob;
    public static void myset<T>(T x) { //ジェネリック静的メソッド
        obj = (T)x; //型がTなのでキャスト可能
    }
    public static void show() {
        Console.WriteLine(obj.ToString());
    }
    public void myset2<T>(T x) { //ジェネリックインスタンスメソッド
        ob = (T)x; //型がTなのでキャスト可能
    }
    public void show2() {
        Console.WriteLine(ob.ToString());
    }
}
class generic07 {
    public static void Main() {
        MyClass.myset<int>(12); //型指定で静的メソッドを呼び出す
        MyClass.show();
        MyClass.myset<string>("abc"); //型指定で静的メソッドを呼び出す
        MyClass.show();
        MyClass mc2 = new MyClass();
        mc2.myset2<int>(100); //型指定でインスタンスメソッドを呼び出す
        mc2.show2();
    }
}

【補足】ジェネリックメソッドの戻り値型と型パラメータ

・ジェネリックメソッドでは引数型や内容の記述に加えて、戻り値型の中でも型パラメータが指定可能
・例: List<T> makelist<T>(T x) {…} //T型の引数を受け取り、T型用のリストを生成して格納、T型用のリスト返す

p.378(ジェネリックメソッドと制約)

・ジェネリッククラスの同様に制約をジェネリックメソッドにも指定可能
・書式: アクセス修飾子 戻り値型 メソッド名<型パラメータ,…> (引数リスト) where 型パラメータ : 制約 {…}

アレンジ演習:p.377 generic07.cs

・ジェネリックメソッドにstruct制約やclass制約をつけて動作を確認しよう

作成例

//アレンジ演習: p.377 generic07.cs
using System;
class MyClass { //通常クラス
    static object obj;
    object ob;
    public static void myset<T>(T x) where T : class { //ジェネリック静的メソッド+制約
        obj = (T)x; //型がTなのでキャスト可能
    }
    public static void show() {
        Console.WriteLine(obj.ToString());
    }
    public void myset2<T>(T x) { //ジェネリックインスタンスメソッド
        ob = (T)x; //型がTなのでキャスト可能
    }
    public void show2() {
        Console.WriteLine(ob.ToString());
    }
}
class generic07 {
    public static void Main() {
        //MyClass.myset<int>(12); //型指定で静的メソッドを呼び出す ※制約でエラー
        //MyClass.show();
        MyClass.myset<string>("abc"); //型指定で静的メソッドを呼び出す
        MyClass.show();
        MyClass mc2 = new MyClass();
        mc2.myset2<int>(100); //型指定でインスタンスメソッドを呼び出す
        mc2.show2();
    }
}

p.379(ジェネリックメソッドとオーバーロード)

・型パラメータはシグニチャの一部になるので、型パラメータの数が異なるジェネリックメソッドによるオーバーロードが可能
・なお、戻り値は通常のメソッドと同様にシグニチャには含まれない

p.379 ジェネリックインタフェース

・クラスと同様にインターフェイスにおいても型パラメータの指定が可能で、これをジェネリックインターフェイスという
・書式: interface インターフェイス名<型パラメータ,…> {…}
・例:
 interface FlyAble<T> { //「飛べる」ことを示すインターフェイス
  T howtofly(); //飛び方をT型で返す抽象メソッド
 }
 class Dragon : FlyAble<string> {…} //飛び方は文字列で返すことを示す

p.381 anonymous02.csについて

・p.380下3行において「anonymous01.cs を自動実装プロパティを用いて書き換えたのが anonymous02.cs」というような説明があるが誤り。
・自動実装プロパティは、主に簡易クラス機能(ここでは割愛)などに用いられるもので、プロパティ名が変数名になる
・よって、anonymous02.csの「int z;」は不要
・また「public int x {get; set;}」は「public int x;」とすればよく、自動実装プロパティを用いる意味はない

p.380 匿名型

・varキーワードを用いて匿名型の参照変数を定義し、オブジェクト初期化子で生成したオブジェクトへの参照を代入できる
・こうすると名前のないクラスが用意され、その中にオブジェクト初期化子で指定したプロパティが匿名型で定義され、
 匿名のデータメンバに初期値が与えられる。
・書式: var 参照変数 = new { プロパティ = 値; … }
・なお、プロパティの型を明示的に指定することや、値を変更することはできない
・データメンバの値は、参照変数.プロパティ で利用可能
・よって、これは複数の定数をグループ化する場合などに用いる
 例: var player1 = new {name = "Shar", age = 24, x = 10, y = 20}

p.381 anonymous03.cs

//p.381 anonymous03.cs
using System;
class anonymous03 {
    public static void Main() {
        var mc = new { x = 10 }; //匿名型のプロパティmc.xに10を与える
        Console.WriteLine("x = {0}", mc.x); //定数のように利用可能
    }
}

p.382(複数の匿名型)

・複数の匿名型では、プロパティの数、型、順序が同じ場合に同じ型とみなされる
・同じ型であれば、複数の匿名型の間で代入が可能
・なお、var型の変数に匿名型の参照変数を代入すると、var型の変数の型が匿名型の型になる

アレンジ演習:p.381 anonymous03.cs

・同じ匿名型の参照変数m2をxの値20で生成する
・mcの値とm2の値を交換してみよう

作成例

//アレンジ演習:p.381 anonymous03.cs
using System;
class anonymous03 {
    public static void Main() {
        var mc = new { x = 10 }; //匿名型のプロパティmc.xに10を与える
        var m2 = new { x = 20 }; //同じ匿名型のプロパティm2.xに20を与える
        var mw = mc; //var型の変数にmcを退避
        mc = m2; //mcとm2を交換
        m2 = mc; //同上
        Console.WriteLine("x = {0}", mc.x); //20になっている
    }
}

p.382「ジェネリックの共変性・反変性」は割愛します

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です